blob: e9d6a34d66e64e3393617661b2a88718575a75a6 [file] [log] [blame]
// Copyright 2023 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/amlogic-display/hdmi-transmitter.h"
#include <fuchsia/hardware/i2cimpl/c/banjo.h>
#include <lib/zx/resource.h>
#include <lib/zx/result.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/syscalls/smc.h>
#include <zircon/types.h>
#include <memory>
#include <fbl/auto_lock.h>
#include "src/graphics/display/drivers/amlogic-display/hdmi-transmitter-top-regs.h"
#include "src/graphics/display/lib/api-types-cpp/display-timing.h"
#include "src/graphics/display/lib/designware-hdmi/color-param.h"
#include "src/graphics/display/lib/designware-hdmi/hdmi-transmitter-controller.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/logging/zxlogf.h"
// References
//
// The code contains references to the following documents.
//
// - ANSI/CTA-861-I: A DTV Profile for Uncompressed High Speed Digital
// Interfaces, Consumer Technology Association (CTA), dated February 2023.
// Referenced as "CTA-861 standard" in the CTA-861 standard.
// Available at
// https://shop.cta.tech/collections/standards/products/a-dtv-profile-for-uncompressed-high-speed-digital-interfaces-ansi-cta-861-i
namespace amlogic_display {
HdmiTransmitter::HdmiTransmitter(
std::unique_ptr<designware_hdmi::HdmiTransmitterController> designware_controller,
fdf::MmioBuffer hdmitx_top_level_mmio, zx::resource silicon_provider_service_smc)
: designware_controller_(std::move(designware_controller)),
hdmitx_top_level_mmio_(std::move(hdmitx_top_level_mmio)),
silicon_provider_service_smc_(std::move(silicon_provider_service_smc)) {
ZX_DEBUG_ASSERT(designware_controller_ != nullptr);
// TODO(https://fxbug.dev/42074342): `silicon_provider_service_smc` may be invalid in
// tests where fake SMC resource objects are not yet supported. Once fake SMC
// is supported, we should add an assertion to enforce
// `silicon_provider_service_smc` to be always valid.
}
zx::result<> HdmiTransmitter::Reset() {
// TODO(https://fxbug.dev/42148838): Add in Resets
// reset hdmi related blocks (HIU, HDMI SYS, HDMI_TX)
// auto reset0_result = display->reset_register_.WriteRegister32(PRESET0_REGISTER, 1 << 19, 1 <<
// 19); if ((reset0_result.status() != ZX_OK) || reset0_result->is_error()) {
// zxlogf(ERROR, "Reset0 Write failed\n");
// }
/* FIXME: This will reset the entire HDMI subsystem including the HDCP engine.
* At this point, we have no way of initializing HDCP block, so we need to
* skip this for now.
*/
// auto reset2_result = display->reset_register_.WriteRegister32(PRESET2_REGISTER, 1 << 15, 1 <<
// 15); // Will mess up hdcp stuff if ((reset2_result.status() != ZX_OK) ||
// reset2_result->is_error()) {
// zxlogf(ERROR, "Reset2 Write failed\n");
// }
// auto reset2_result = display->reset_register_.WriteRegister32(PRESET2_REGISTER, 1 << 2, 1 <<
// 2); if ((reset2_result.status() != ZX_OK) || reset2_result->is_error()) {
// zxlogf(ERROR, "Reset2 Write failed\n");
// }
// Bring HDMI out of reset
WriteTopLevelReg(HDMITX_TOP_SW_RESET, 0);
usleep(200);
WriteTopLevelReg(HDMITX_TOP_CLK_CNTL, 0x000000ff);
fbl::AutoLock lock(&dw_lock_);
zx_status_t status = designware_controller_->InitHw();
return zx::make_result(status);
}
namespace {
void CalculateTxParam(const display::DisplayTiming& display_timing,
designware_hdmi::hdmi_param_tx* p) {
p->is4K = display_timing.pixel_clock_frequency_hz > 500'000'000;
// The aspect ratio field in the Auxiliary Video Information (AVI) InfoFrame.
// Values are defined in the CTA-861 standard, Section 6.4.1 "Video Format,
// Picture Aspect Ratio and Pixel Repetition", Table 14 "AVI InfoFrame
// Picture Aspect Ratio Field, Data Byte 2", page 76.
//
// TODO(https://fxbug.dev/42085777): Revise the AVI InfoFrame naming and move the
// values to a dedicated header.
static constexpr uint8_t kHdmiAspectRatio4x3 = 1;
static constexpr uint8_t kHdmiAspectRatio16x9 = 2;
static constexpr uint8_t kHdmiAspectRatioNone = 0;
if (display_timing.horizontal_active_px * 3 == display_timing.vertical_active_lines * 4) {
p->aspect_ratio = kHdmiAspectRatio4x3;
} else if (display_timing.horizontal_active_px * 9 == display_timing.vertical_active_lines * 16) {
p->aspect_ratio = kHdmiAspectRatio16x9;
} else {
p->aspect_ratio = kHdmiAspectRatioNone;
}
// The colorimetry field in the AVI InfoFrame.
// Values are defined in the CTA-861 standard, Section 6.4.2 "Color Component
// Sample Format and Colorimetry", Table 19 "AVI InfoFrame Colorimetry
// Fields", page 81.
//
// The colorimetry of SMPTE ST 170, commonly known as the "NTSC standard".
static constexpr uint8_t kHdmiColorimetryNtsc = 1;
// TODO(https://fxbug.dev/42085819): Revise AVI InfoFrame values set by the driver.
p->colorimetry = kHdmiColorimetryNtsc;
}
} // namespace
zx::result<> HdmiTransmitter::ModeSet(const display::DisplayTiming& timing,
const designware_hdmi::ColorParam& color) {
designware_hdmi::hdmi_param_tx p;
CalculateTxParam(timing, &p);
// Output normal TMDS Data
WriteTopLevelReg(HDMITX_TOP_BIST_CNTL, 1 << 12);
// Configure HDMI TX IP
fbl::AutoLock lock(&dw_lock_);
designware_controller_->ConfigHdmitx(color, timing, p);
// Initialize HDCP 1.4.
//
// AMLogic-provided bringup code initializes HDCP before clearing
// interrupts on the DesignWare HDMI IP's. Following the same sequence
// would be difficult given our current layering, as we clear interrupts
// in HdmiDw::ConfigHdmitx().
//
// Fortunately, experiments on VIM3 (using A311D) show that the HDCP
// initialization SMC still works if invoked after the interrupts are
// cleared.
if (silicon_provider_service_smc_.is_valid()) {
zx::result<> hdcp_initialization_result = InitializeHdcp14();
if (hdcp_initialization_result.is_error()) {
zxlogf(ERROR, "Failed to initialize HDCP 1.4: %s",
hdcp_initialization_result.status_string());
return hdcp_initialization_result;
}
} else {
// TODO(https://fxbug.dev/42074342): This could occur in tests where fake SMC
// resource objects are not yet supported. Once fake SMC is supported, we
// should enforce `smc_` to be always valid and always issue a
// `zx_smc_call()` syscall.
zxlogf(WARNING,
"Secure monitor call (SMC) resource is not available. "
"Skipping initializing HDCP 1.4.");
}
WriteTopLevelReg(HDMITX_TOP_INTR_STAT_CLR, 0x0000001f);
designware_controller_->SetupInterrupts();
WriteTopLevelReg(HDMITX_TOP_INTR_MASKN, 0x9f);
designware_controller_->Reset();
if (p.is4K) {
// Setup TMDS Clocks (taken from recommended test pattern in DVI spec)
WriteTopLevelReg(HDMITX_TOP_TMDS_CLK_PTTN_01, 0);
WriteTopLevelReg(HDMITX_TOP_TMDS_CLK_PTTN_23, 0x03ff03ff);
} else {
WriteTopLevelReg(HDMITX_TOP_TMDS_CLK_PTTN_01, 0x001f001f);
WriteTopLevelReg(HDMITX_TOP_TMDS_CLK_PTTN_23, 0x001f001f);
}
designware_controller_->SetFcScramblerCtrl(p.is4K);
WriteTopLevelReg(HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1);
usleep(2);
WriteTopLevelReg(HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2);
designware_controller_->SetupScdc(p.is4K);
designware_controller_->ResetFc();
return zx::ok();
}
zx::result<> HdmiTransmitter::I2cTransact(const i2c_impl_op_t* i2c_ops, size_t i2c_op_count) {
fbl::AutoLock lock(&dw_lock_);
zx_status_t status = designware_controller_->EdidTransfer(i2c_ops, i2c_op_count);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to transfer EDID: %s", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok();
}
void HdmiTransmitter::WriteTopLevelReg(uint32_t addr, uint32_t val) {
hdmitx_top_level_mmio_.Write32(val, addr);
}
uint32_t HdmiTransmitter::ReadTopLevelReg(uint32_t addr) {
return hdmitx_top_level_mmio_.Read32(addr);
}
void HdmiTransmitter::PrintRegister(const char* register_name, uint32_t register_address) {
zxlogf(INFO, "%s (0x%04" PRIx32 "): %" PRIu32, register_name, register_address,
ReadTopLevelReg(register_address));
}
void HdmiTransmitter::PrintTopLevelRegisters() {
zxlogf(INFO, "------------Top Registers------------");
PrintRegister("HDMITX_TOP_SW_RESET", HDMITX_TOP_SW_RESET);
PrintRegister("HDMITX_TOP_CLK_CNTL", HDMITX_TOP_CLK_CNTL);
PrintRegister("HDMITX_TOP_INTR_MASKN", HDMITX_TOP_INTR_MASKN);
PrintRegister("HDMITX_TOP_INTR_STAT_CLR", HDMITX_TOP_INTR_STAT_CLR);
PrintRegister("HDMITX_TOP_BIST_CNTL", HDMITX_TOP_BIST_CNTL);
PrintRegister("HDMITX_TOP_TMDS_CLK_PTTN_01", HDMITX_TOP_TMDS_CLK_PTTN_01);
PrintRegister("HDMITX_TOP_TMDS_CLK_PTTN_23", HDMITX_TOP_TMDS_CLK_PTTN_23);
PrintRegister("HDMITX_TOP_TMDS_CLK_PTTN_CNTL", HDMITX_TOP_TMDS_CLK_PTTN_CNTL);
}
void HdmiTransmitter::PrintRegisters() {
PrintTopLevelRegisters();
fbl::AutoLock lock(&dw_lock_);
designware_controller_->PrintRegisters();
}
zx::result<> HdmiTransmitter::InitializeHdcp14() {
static constexpr zx_smc_parameters_t params = {
// Silicon Provider secure monitor call: "HDCP14_INIT".
.func_id = 0x82000012,
};
zx_smc_result_t result = {};
zx_status_t status = zx_smc_call(silicon_provider_service_smc_.get(), &params, &result);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to initialize HDCP 1.4: %s", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok();
}
} // namespace amlogic_display