blob: f27dc8b88c36f02a95cf6d83e7da1b0d3afb0fb4 [file] [log] [blame]
// Copyright 2021 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-host.h"
#include <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/result.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <fbl/alloc_checker.h>
#include "src/graphics/display/drivers/amlogic-display/board-resources.h"
#include "src/graphics/display/drivers/amlogic-display/clock-regs.h"
#include "src/graphics/display/drivers/amlogic-display/common.h"
#include "src/graphics/display/drivers/amlogic-display/encoder-regs.h"
#include "src/graphics/display/drivers/amlogic-display/gpio-mux-regs.h"
#include "src/graphics/display/drivers/amlogic-display/hhi-regs.h"
#include "src/graphics/display/drivers/amlogic-display/power-regs.h"
#include "src/graphics/display/drivers/amlogic-display/vpu-regs.h"
#include "src/graphics/display/lib/api-types-cpp/display-timing.h"
#include "src/graphics/display/lib/designware-hdmi/hdmi-transmitter-controller-impl.h"
#include "src/graphics/display/lib/designware-hdmi/hdmi-transmitter-controller.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/logging/zxlogf.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/namespace/namespace.h"
namespace amlogic_display {
namespace {
// Range of valid frequencies of the DCO (digitally controlled oscillator) of a
// certain PLL (phase-locked loop).
struct ValidDcoFrequencyRange {
int64_t minimum_frequency_hz;
int64_t maximum_frequency_hz;
};
ValidDcoFrequencyRange GetHdmiPllValidDcoFrequencyRange(int64_t pixel_clock_hz) {
// Amlogic datasheets (A311D, S905D2 and S905D3) specify that the frequency
// of the DCO in the HDMI PLL must be between 3 GHz and 6 GHz.
//
// However, [1] Amlogic-provided code uses 2.97 GHz for some common display
// resolutions; [2] Experiments on Khadas VIM3 (Amlogic A311D) also shows
// that 2.9 GHz is a valid DCO frequency for all the display timings we have
// tested and has fewer display glitches than using 5.8 GHz. So, we use
// 2.9 GHz rather than 3 GHz as the minimum valid DCO frequency for default
// cases.
static constexpr int64_t kDefaultMinimumValidHdmiPllDcoFrequencyHz = 2'900'000'000;
static constexpr int64_t kDefaultMaximumValidHdmiPllDcoFrequencyHz = 6'000'000'000;
// For display timings with a very low pixel clock rate (for example, on
// Surenoo SUR480480Y021A, it has a pixel clock of 16.96 MHz), in our
// experiments, we had to lower the minimum allowed DCO frequency to 2.7 GHz
// in order to keep the correct display aspect ratio.
//
// Since this is not a valid frequency documented in the datasheets, this
// should only be used as an exception when the pixel clock rate is very
// low. Thus, we only set the minimum allowed DCO frequency to 2.7 GHz, if
// the pixel clock is lower than 20 MHz (which is lower than the pixel clock
// of DMT timing of 640x480p@60Hz) so that it won't affect "normal" display
// modes.
static constexpr int64_t kLowPixelClockMinimumValidHdmiPllDcoFrequencyHz = 2'700'000'000;
static constexpr int64_t kLowPixelClockThresholdHz = 20'000'000;
if (pixel_clock_hz <= kLowPixelClockThresholdHz) {
return {
.minimum_frequency_hz = kLowPixelClockMinimumValidHdmiPllDcoFrequencyHz,
.maximum_frequency_hz = kDefaultMaximumValidHdmiPllDcoFrequencyHz,
};
}
return {
.minimum_frequency_hz = kDefaultMinimumValidHdmiPllDcoFrequencyHz,
.maximum_frequency_hz = kDefaultMaximumValidHdmiPllDcoFrequencyHz,
};
}
// `timing` must be a timing supported by `HdmiHost`.
pll_param CalculateClockParameters(const display::DisplayTiming& timing) {
pll_param params;
// TODO: We probably need a more sophisticated method for calculating
// clocks. This will do for now.
params.viu_channel = 1;
params.viu_type = VIU_ENCP;
params.hdmi_clock_tree_vid_pll_divider = 5;
params.video_clock1_divider = 2;
params.hdmi_transmitter_pixel_clock_divider = 1;
params.encp_clock_divider = 1;
params.output_divider1 = 1;
params.output_divider2 = 1;
params.output_divider3 = 1;
params.hdmi_pll_vco_output_frequency_hz = timing.pixel_clock_frequency_hz * 10;
const ValidDcoFrequencyRange valid_dco_frequency_range =
GetHdmiPllValidDcoFrequencyRange(timing.pixel_clock_frequency_hz);
while (params.hdmi_pll_vco_output_frequency_hz < valid_dco_frequency_range.minimum_frequency_hz) {
if (params.output_divider1 < 4) {
params.output_divider1 *= 2;
params.hdmi_pll_vco_output_frequency_hz *= 2;
} else if (params.output_divider2 < 4) {
params.output_divider2 *= 2;
params.hdmi_pll_vco_output_frequency_hz *= 2;
} else if (params.output_divider3 < 4) {
params.output_divider3 *= 2;
params.hdmi_pll_vco_output_frequency_hz *= 2;
} else {
ZX_DEBUG_ASSERT_MSG(
false,
"Failed to set HDMI PLL to a valid VCO frequency range for pixel clock %" PRId64
" Hz. This should never happen since IsDisplayTimingSupported() "
"returned true.",
timing.pixel_clock_frequency_hz);
}
}
ZX_DEBUG_ASSERT_MSG(
params.hdmi_pll_vco_output_frequency_hz <= valid_dco_frequency_range.maximum_frequency_hz,
"Calculated HDMI PLL VCO frequency (%" PRId64 " Hz) exceeds the VCO frequency limit %" PRId64
" Hz. This should never happen since IsDisplayTimingSupported() returned true.",
params.hdmi_pll_vco_output_frequency_hz, valid_dco_frequency_range.maximum_frequency_hz);
return params;
}
zx::result<std::unique_ptr<HdmiTransmitter>> CreateHdmiTransmitter(
fidl::UnownedClientEnd<fuchsia_hardware_platform_device::Device> platform_device) {
if (!platform_device.is_valid()) {
zxlogf(ERROR, "PDev protocol is invalid");
return zx::error(ZX_ERR_NO_RESOURCES);
}
zx::result<fdf::MmioBuffer> hdmi_tx_mmio_result =
MapMmio(MmioResourceIndex::kHdmiTxController, platform_device);
if (hdmi_tx_mmio_result.is_error()) {
return hdmi_tx_mmio_result.take_error();
}
fbl::AllocChecker alloc_checker;
std::unique_ptr<designware_hdmi::HdmiTransmitterController> designware_controller =
fbl::make_unique_checked<designware_hdmi::HdmiTransmitterControllerImpl>(
&alloc_checker, std::move(hdmi_tx_mmio_result).value());
if (!alloc_checker.check()) {
zxlogf(ERROR, "Could not allocate memory for DesignWare HdmiTransmitterControllerImpl");
return zx::error(ZX_ERR_NO_MEMORY);
}
zx::result<fdf::MmioBuffer> hdmi_top_mmio_result =
MapMmio(MmioResourceIndex::kHdmiTxTop, platform_device);
if (hdmi_top_mmio_result.is_error()) {
return hdmi_top_mmio_result.take_error();
}
zx::result<zx::resource> smc_result =
GetSecureMonitorCall(SecureMonitorCallResourceIndex::kSiliconProvider, platform_device);
if (smc_result.is_error()) {
return smc_result.take_error();
}
std::unique_ptr<HdmiTransmitter> hdmi_transmitter = fbl::make_unique_checked<HdmiTransmitter>(
&alloc_checker, std::move(designware_controller), std::move(hdmi_top_mmio_result).value(),
std::move(smc_result).value());
if (!alloc_checker.check()) {
zxlogf(ERROR, "Could not allocate memory for HdmiTransmitter");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(hdmi_transmitter));
}
} // namespace
HdmiHost::HdmiHost(std::unique_ptr<HdmiTransmitter> hdmi_transmitter, fdf::MmioBuffer vpu_mmio,
fdf::MmioBuffer hhi_mmio, fdf::MmioBuffer gpio_mux_mmio)
: hdmi_transmitter_(std::move(hdmi_transmitter)),
vpu_mmio_(std::move(vpu_mmio)),
hhi_mmio_(std::move(hhi_mmio)),
gpio_mux_mmio_(std::move(gpio_mux_mmio)) {
ZX_DEBUG_ASSERT(hdmi_transmitter_ != nullptr);
}
// static
zx::result<std::unique_ptr<HdmiHost>> HdmiHost::Create(display::Namespace& incoming) {
static constexpr char kPdevFragmentName[] = "pdev";
zx::result<fidl::ClientEnd<fuchsia_hardware_platform_device::Device>> pdev_result =
incoming.Connect<fuchsia_hardware_platform_device::Service::Device>(kPdevFragmentName);
if (pdev_result.is_error()) {
zxlogf(ERROR, "Failed to get the pdev client: %s", pdev_result.status_string());
return pdev_result.take_error();
}
fidl::ClientEnd<fuchsia_hardware_platform_device::Device> platform_device =
std::move(pdev_result).value();
if (!platform_device.is_valid()) {
zxlogf(ERROR, "Could not get the platform device client.");
return zx::error(ZX_ERR_INTERNAL);
}
zx::result<fdf::MmioBuffer> vpu_mmio_result = MapMmio(MmioResourceIndex::kVpu, platform_device);
if (vpu_mmio_result.is_error()) {
return vpu_mmio_result.take_error();
}
zx::result<fdf::MmioBuffer> hhi_mmio_result = MapMmio(MmioResourceIndex::kHhi, platform_device);
if (hhi_mmio_result.is_error()) {
return hhi_mmio_result.take_error();
}
zx::result<fdf::MmioBuffer> gpio_mux_mmio_result =
MapMmio(MmioResourceIndex::kGpioMux, platform_device);
if (gpio_mux_mmio_result.is_error()) {
return gpio_mux_mmio_result.take_error();
}
zx::result<std::unique_ptr<HdmiTransmitter>> hdmi_transmitter =
CreateHdmiTransmitter(platform_device);
if (hdmi_transmitter.is_error()) {
zxlogf(ERROR, "Could not create HDMI transmitter: %s", hdmi_transmitter.status_string());
return hdmi_transmitter.take_error();
}
ZX_ASSERT(hdmi_transmitter.value() != nullptr);
fbl::AllocChecker alloc_checker;
std::unique_ptr<HdmiHost> hdmi_host = fbl::make_unique_checked<HdmiHost>(
&alloc_checker, std::move(hdmi_transmitter).value(), std::move(vpu_mmio_result).value(),
std::move(hhi_mmio_result).value(), std::move(gpio_mux_mmio_result).value());
if (!alloc_checker.check()) {
zxlogf(ERROR, "Could not allocate memory for the HdmiHost instance.");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(hdmi_host));
}
zx_status_t HdmiHost::HostOn() {
/* Step 1: Initialize various clocks related to the HDMI Interface*/
gpio_mux_mmio_.Write32(
SetFieldValue32(gpio_mux_mmio_.Read32(PAD_PULL_UP_EN_REG3), /*field_begin_bit=*/0,
/*field_size_bits=*/2, /*field_value=*/0),
PAD_PULL_UP_EN_REG3);
gpio_mux_mmio_.Write32(
SetFieldValue32(gpio_mux_mmio_.Read32(PAD_PULL_UP_REG3), /*field_begin_bit=*/0,
/*field_size_bits=*/2, /*field_value=*/0),
PAD_PULL_UP_REG3);
gpio_mux_mmio_.Write32(
SetFieldValue32(gpio_mux_mmio_.Read32(P_PREG_PAD_GPIO3_EN_N), /*field_begin_bit=*/0,
/*field_size_bits=*/2, /*field_value=*/3),
P_PREG_PAD_GPIO3_EN_N);
gpio_mux_mmio_.Write32(
SetFieldValue32(gpio_mux_mmio_.Read32(PERIPHS_PIN_MUX_B), /*field_begin_bit=*/0,
/*field_size_bits=*/8, /*field_value=*/0x11),
PERIPHS_PIN_MUX_B);
// enable clocks
HdmiClockControl::Get()
.ReadFrom(&hhi_mmio_)
.SetHdmiTxSystemClockDivider(1)
.set_hdmi_tx_system_clock_enabled(true)
.set_hdmi_tx_system_clock_selection(
HdmiClockControl::HdmiTxSystemClockSource::kExternalOscillator24Mhz)
.WriteTo(&hhi_mmio_);
// enable clk81 (needed for HDMI module and a bunch of other modules)
HhiGclkMpeg2Reg::Get().ReadFrom(&hhi_mmio_).set_clk81_en(1).WriteTo(&hhi_mmio_);
// TODO(fxbug.com/132123): HDMI memory was supposed to be powered on during
// the VPU power sequence. The AMLogic-supplied bringup code pauses for 5us
// between each bit flip.
auto memory_power0 = MemoryPower0::Get().ReadFrom(&hhi_mmio_);
memory_power0.set_hdmi_memory0_powered_off(false);
memory_power0.set_hdmi_memory1_powered_off(false);
memory_power0.set_hdmi_memory2_powered_off(false);
memory_power0.set_hdmi_memory3_powered_off(false);
memory_power0.set_hdmi_memory4_powered_off(false);
memory_power0.set_hdmi_memory5_powered_off(false);
memory_power0.set_hdmi_memory6_powered_off(false);
memory_power0.set_hdmi_memory7_powered_off(false);
memory_power0.WriteTo(&hhi_mmio_);
zx::result<> reset_result = hdmi_transmitter_->Reset(); // only supports 1 display for now
if (reset_result.is_error()) {
zxlogf(ERROR, "Failed to reset the HDMI transmitter: %s", reset_result.status_string());
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void HdmiHost::HostOff() {
/* Close HDMITX PHY */
hhi_mmio_.Write32(0, HHI_HDMI_PHY_CNTL0);
hhi_mmio_.Write32(0, HHI_HDMI_PHY_CNTL3);
/* Disable HPLL */
hhi_mmio_.Write32(0, HHI_HDMI_PLL_CNTL0);
}
zx_status_t HdmiHost::ModeSet(const display::DisplayTiming& timing) {
if (!IsDisplayTimingSupported(timing)) {
zxlogf(
ERROR,
"Display timing (%" PRIu32 " x %" PRIu32 " @ pixel rate %" PRId64 " Hz) is not supported.",
timing.horizontal_active_px, timing.vertical_active_lines, timing.pixel_clock_frequency_hz);
return ZX_ERR_NOT_SUPPORTED;
}
pll_param clock_params = CalculateClockParameters(timing);
ConfigurePll(clock_params);
vpu_mmio_.Write32(0, VPU_ENCP_VIDEO_EN);
vpu_mmio_.Write32(0, VPU_ENCI_VIDEO_EN);
// Connect both VIUs (Video Input Units) to the Progressive Encoder (ENCP),
// assuming the display is progressive.
VideoInputUnitEncoderMuxControl::Get()
.ReadFrom(&vpu_mmio_)
.set_vsync_shared_by_viu_blocks(false)
.set_viu1_encoder_selection(VideoInputUnitEncoderMuxControl::Encoder::kProgressive)
.set_viu2_encoder_selection(VideoInputUnitEncoderMuxControl::Encoder::kProgressive)
.WriteTo(&vpu_mmio_);
// Configure Encoder with detailed timing info (based on resolution)
ConfigEncoder(timing);
// Configure VDAC
hhi_mmio_.Write32(0, HHI_VDAC_CNTL0_G12A);
hhi_mmio_.Write32(8, HHI_VDAC_CNTL1_G12A); // set Cdac_pwd [whatever that is]
static constexpr designware_hdmi::ColorParam kColorParams{
.input_color_format = designware_hdmi::ColorFormat::kCf444,
// We choose the RGB 4:4:4 encoding unconditionally for the HDMI output
// signals. This implies that we avoid YCbCr encodings, even if they are
// unsupported.
//
// The HDMI specificiaton v1.4b, Section 6.2.3 "Pixel Encoding
// Requirements" (page 106) requires that all HDMI sources and sinks
// support RGB 4:4:4 encoding. Thus we think this approach will work with
// all of our devices.
//
// Also, we encountered hardware (Yongxing HDMI to MIPI-DSI converters
// board v1.2, using the Toshiba TC358870XBG converter chip, provided with
// the Amelin AML028-30MB-A1 assembly) that claims support for the YCbCr
// 4:4:4 pixel encoding in EDID, but does not display colors correctly
// when we use that encoding. That hardware should be considered when
// changing this strategy.
.output_color_format = designware_hdmi::ColorFormat::kCfRgb,
.color_depth = designware_hdmi::ColorDepth::kCd24B,
};
zx::result<> modeset_result = hdmi_transmitter_->ModeSet(timing, kColorParams);
if (modeset_result.is_error()) {
zxlogf(ERROR, "Failed to set display mode: %s", modeset_result.status_string());
return modeset_result.status_value();
}
// Setup HDMI related registers in VPU
// not really needed since we are not converting from 420/422. but set anyways
VpuHdmiFmtCtrlReg::Get()
.FromValue(0)
.set_cntl_chroma_dnsmp(2)
.set_cntl_hdmi_dith_en(0)
.set_rounding_enable(1)
.WriteTo(&vpu_mmio_);
// setup some magic registers
VpuHdmiDithCntlReg::Get()
.ReadFrom(&vpu_mmio_)
.set_cntl_hdmi_dith_en(1)
.set_hsync_invert(0)
.set_vsync_invert(0)
.WriteTo(&vpu_mmio_);
// reset vpu bridge
uint32_t wr_rate = VpuHdmiSettingReg::Get().ReadFrom(&vpu_mmio_).wr_rate();
vpu_mmio_.Write32(0, VPU_ENCP_VIDEO_EN);
VpuHdmiSettingReg::Get().ReadFrom(&vpu_mmio_).set_src_sel(0).set_wr_rate(0).WriteTo(&vpu_mmio_);
usleep(1);
vpu_mmio_.Write32(1, VPU_ENCP_VIDEO_EN);
usleep(1);
VpuHdmiSettingReg::Get().ReadFrom(&vpu_mmio_).set_wr_rate(wr_rate).WriteTo(&vpu_mmio_);
usleep(1);
VpuHdmiSettingReg::Get().ReadFrom(&vpu_mmio_).set_src_sel(2).WriteTo(&vpu_mmio_);
// setup hdmi phy
ConfigPhy();
zxlogf(INFO, "done!!");
return ZX_OK;
}
zx_status_t HdmiHost::EdidTransfer(const i2c_impl_op_t* op_list, size_t op_count) {
zx::result<> i2c_transact_result = hdmi_transmitter_->I2cTransact(op_list, op_count);
if (i2c_transact_result.is_error()) {
zxlogf(ERROR, "Failed to transfer EDID: %s", i2c_transact_result.status_string());
return i2c_transact_result.status_value();
}
return ZX_OK;
}
namespace {
// Returns true iff the display PLL and clock trees can be programmed to
// generate a pixel clock of `pixel_clock_hz`.
bool IsPixelClockSupported(int64_t pixel_clock_hz) {
const ValidDcoFrequencyRange valid_dco_frequency_range =
GetHdmiPllValidDcoFrequencyRange(pixel_clock_hz);
// Fixed divisor values.
//
// HDMI clock tree divisor `vid_pll_div` == 5,
// Video tree divisor /N0 `vid_clk_div` == 2,
// Video tree ENCP clock selector `encp_div` == 1.
//
// TODO(https://fxbug.dev/42083149): Factor this out for pixel clock checking and
// calculation logics.
constexpr int kFixedPllDivisionFactor = 5 * 2 * 1;
// TODO(https://fxbug.dev/42083149): Factor out ranges for each output frequency
// divider so that they can be used for both clock checking and calculation.
// OD1 = OD2 = OD3 = 1.
constexpr int kMinimumPllDivisionFactor = 1 * 1 * 1;
// OD1 = OD2 = OD3 = 4.
constexpr int kMaximumPllDivisionFactor = 4 * 4 * 4;
// The adjustable dividers OD1 / OD2 / OD3 cannot be calculated if the output
// frequency using `kMinimumPllDivisionFactor` still exceeds the maximum
// allowed value.
const int64_t maximum_allowed_pixel_clock_hz =
valid_dco_frequency_range.maximum_frequency_hz /
(kFixedPllDivisionFactor * kMinimumPllDivisionFactor);
if (pixel_clock_hz > maximum_allowed_pixel_clock_hz) {
return false;
}
// The adjustable dividers OD1 / OD2 / OD3 cannot be calculated if the output
// frequency using `kMaximumPllDivisionFactor` is still less than the minimum
// allowed value.
// ceil(kMinimumValidHdmiPllVcoFrequencyHz / (kFixedPllDivisionFactor *
// kMaximumPllDivisionFactor))
const int64_t minimum_allowed_pixel_clock_hz =
(valid_dco_frequency_range.minimum_frequency_hz +
kFixedPllDivisionFactor * kMaximumPllDivisionFactor - 1) /
(kFixedPllDivisionFactor * kMaximumPllDivisionFactor);
if (pixel_clock_hz < minimum_allowed_pixel_clock_hz) {
return false;
}
return true;
}
} // namespace
bool HdmiHost::IsDisplayTimingSupported(const display::DisplayTiming& timing) const {
// TODO(https://fxbug.dev/42075808): High-resolution display modes (4K or more) are not
// supported.
const int kMaximumAllowedWidthPixels = 2560;
const int kMaximumAllowedHeightPixels = 1600;
if (timing.horizontal_active_px > kMaximumAllowedWidthPixels ||
timing.vertical_active_lines > kMaximumAllowedHeightPixels) {
return false;
}
// TODO(https://fxbug.dev/42083230): Interlaced modes are not supported.
if (timing.fields_per_frame == display::FieldsPerFrame::kInterlaced) {
return false;
}
// TODO(https://fxbug.dev/42083230): Interlaced modes with alternating vblanks are not
// supported.
if (timing.vblank_alternates) {
return false;
}
// TODO(https://fxbug.dev/42084414): Modes with pixel repetition are not supported.
if (timing.pixel_repetition != 0) {
return false;
}
if (!IsPixelClockSupported(timing.pixel_clock_frequency_hz)) {
return false;
}
return true;
}
void HdmiHost::ReplaceEncoderPixelColorWithBlack(bool enabled) {
if (enabled) {
EncoderBuiltInSelfTestModeSelection::Get()
.FromValue(0)
.set_mode(EncoderBuiltInSelfTestMode::kFixedColor)
.WriteTo(&vpu_mmio_);
// Sets the 10-bit YCbCr color value that replaces the Video Input Unit
// output.
//
// Black (R = 0, G = 0, B = 0) is (Y = 0, Cb = 512, Cr = 512) in YCbCr.
EncoderBuiltInSelfTestFixedColorLuminance::Get().FromValue(0).set_luminance(0).WriteTo(
&vpu_mmio_);
EncoderBuiltInSelfTestFixedColorChrominanceBlue::Get()
.FromValue(0)
.set_chrominance_blue(0x200)
.WriteTo(&vpu_mmio_);
EncoderBuiltInSelfTestFixedColorChrominanceRed::Get()
.FromValue(0)
.set_chrominance_red(0x200)
.WriteTo(&vpu_mmio_);
}
HdmiEncoderAdvancedModeConfig::Get()
.ReadFrom(&vpu_mmio_)
.set_viu_fifo_enabled(!enabled)
.WriteTo(&vpu_mmio_);
EncoderBuiltInSelfTestEnabled::Get().FromValue(0).set_enabled(enabled).WriteTo(&vpu_mmio_);
}
void HdmiHost::ConfigEncoder(const display::DisplayTiming& timing) {
// TODO(https://fxbug.dev/42084909): For timings that have repetitive pixels
// (for example, 1440x480p60 and 1440x480i60), the Amlogic-provided code has
// contradictory and (in most cases) incomplete configurations. Thus, we'll
// reject all such formats.
ZX_DEBUG_ASSERT(timing.pixel_repetition == 0);
// TODO(https://fxbug.dev/42084909): The current code assumes the timing is for
// progressive fields.
ZX_DEBUG_ASSERT(timing.fields_per_frame == display::FieldsPerFrame::kProgressive);
vpu_mmio_.Write32(timing.horizontal_total_px() - 1, VPU_ENCP_VIDEO_MAX_PXCNT);
vpu_mmio_.Write32(timing.vertical_total_lines() - 1, VPU_ENCP_VIDEO_MAX_LNCNT);
vpu_mmio_.Write32(0, VPU_ENCI_VIDEO_EN);
vpu_mmio_.Write32(1, VPU_ENCP_VIDEO_EN);
vpu_mmio_.Write32(0x4040, VPU_ENCP_VIDEO_MODE);
vpu_mmio_.Write32(0x18, VPU_ENCP_VIDEO_MODE_ADV);
vpu_mmio_.Write32(SetFieldValue32(vpu_mmio_.Read32(VPU_ENCP_VIDEO_MODE), /*field_begin_bit=*/14,
/*field_size_bits=*/1, /*field_value=*/1),
VPU_ENCP_VIDEO_MODE); // DE Signal polarity
const int video_horizontal_sync_begin_px = 0;
const int video_horizontal_sync_end_px =
video_horizontal_sync_begin_px + timing.horizontal_sync_width_px - 1;
const int video_horizontal_active_begin_px =
video_horizontal_sync_end_px + 1 + timing.horizontal_back_porch_px;
const int video_horizontal_active_end_px =
video_horizontal_active_begin_px + timing.horizontal_active_px - 1;
const int video_horizontal_period_px = timing.horizontal_total_px();
// Experiments on Khadas VIM3 (using Amlogic A311D) show that HAVON_*
// registers must be set before HSO_* registers, otherwise the encoder
// won't work.
vpu_mmio_.Write32(video_horizontal_active_begin_px, VPU_ENCP_VIDEO_HAVON_BEGIN);
vpu_mmio_.Write32(video_horizontal_active_end_px, VPU_ENCP_VIDEO_HAVON_END);
vpu_mmio_.Write32(video_horizontal_sync_begin_px, VPU_ENCP_VIDEO_HSO_BEGIN);
vpu_mmio_.Write32(video_horizontal_sync_end_px + 1, VPU_ENCP_VIDEO_HSO_END);
const int video_vertical_sync_begin_line = 0;
const int video_vertical_sync_end_line =
video_vertical_sync_begin_line + timing.vertical_sync_width_lines - 1;
const int video_vertical_active_begin_line =
video_vertical_sync_end_line + 1 + timing.vertical_back_porch_lines;
const int video_vertical_active_end_line =
video_vertical_active_begin_line + timing.vertical_active_lines - 1;
// Experiments on Khadas VIM3 (using Amlogic A311D) show that VAVON_*
// registers must be set before VSO_* registers, otherwise the encoder
// won't work.
vpu_mmio_.Write32(video_vertical_active_begin_line, VPU_ENCP_VIDEO_VAVON_BLINE);
vpu_mmio_.Write32(video_vertical_active_end_line, VPU_ENCP_VIDEO_VAVON_ELINE);
vpu_mmio_.Write32(video_vertical_sync_begin_line, VPU_ENCP_VIDEO_VSO_BLINE);
vpu_mmio_.Write32(video_vertical_sync_end_line + 1, VPU_ENCP_VIDEO_VSO_ELINE);
vpu_mmio_.Write32(16, VPU_ENCP_VIDEO_VSO_BEGIN);
vpu_mmio_.Write32(32, VPU_ENCP_VIDEO_VSO_END);
// The latency between HDMI timing signals (DE, VSYNC, HSYNC) and the video
// signal (from VFIFO).
static constexpr int hdmi_signal_horizontal_offset = 2;
const int hdmi_horizontal_sync_begin_px =
(video_horizontal_sync_begin_px + hdmi_signal_horizontal_offset) % video_horizontal_period_px;
const int hdmi_horizontal_sync_end_px =
(video_horizontal_sync_end_px + hdmi_signal_horizontal_offset) % video_horizontal_period_px;
const int hdmi_horizontal_active_begin_px =
(video_horizontal_active_begin_px + hdmi_signal_horizontal_offset) %
video_horizontal_period_px;
const int hdmi_horizontal_active_end_px =
(video_horizontal_active_end_px + hdmi_signal_horizontal_offset) % video_horizontal_period_px;
vpu_mmio_.Write32(hdmi_horizontal_active_begin_px, VPU_ENCP_DE_H_BEGIN);
vpu_mmio_.Write32(hdmi_horizontal_active_end_px + 1, VPU_ENCP_DE_H_END);
vpu_mmio_.Write32(hdmi_horizontal_sync_begin_px, VPU_ENCP_DVI_HSO_BEGIN);
vpu_mmio_.Write32(hdmi_horizontal_sync_end_px + 1, VPU_ENCP_DVI_HSO_END);
const int hdmi_vertical_sync_begin_line = video_vertical_sync_begin_line;
const int hdmi_vertical_sync_end_line = video_vertical_sync_end_line;
const int hdmi_vertical_active_begin_line = video_vertical_active_begin_line;
const int hdmi_vertical_active_end_line = video_vertical_active_end_line;
vpu_mmio_.Write32(hdmi_vertical_active_begin_line, VPU_ENCP_DE_V_BEGIN_EVEN);
vpu_mmio_.Write32(hdmi_vertical_active_end_line + 1, VPU_ENCP_DE_V_END_EVEN);
vpu_mmio_.Write32(hdmi_vertical_sync_begin_line, VPU_ENCP_DVI_VSO_BLINE_EVN);
vpu_mmio_.Write32(hdmi_vertical_sync_end_line + 1, VPU_ENCP_DVI_VSO_ELINE_EVN);
vpu_mmio_.Write32(hdmi_horizontal_sync_begin_px, VPU_ENCP_DVI_VSO_BEGIN_EVN);
vpu_mmio_.Write32(hdmi_horizontal_sync_begin_px, VPU_ENCP_DVI_VSO_END_EVN);
// hsync, vsync active high. output CbYCr (GRB)
// TODO: output desired format is hardcoded here to CbYCr (GRB)
uint32_t vpu_hdmi_setting = 0b100 << 5;
if (timing.hsync_polarity == display::SyncPolarity::kPositive) {
vpu_hdmi_setting |= (1 << 2);
}
if (timing.vsync_polarity == display::SyncPolarity::kPositive) {
vpu_hdmi_setting |= (1 << 3);
}
vpu_mmio_.Write32(vpu_hdmi_setting, VPU_HDMI_SETTING);
// Select ENCP data to HDMI
VpuHdmiSettingReg::Get().ReadFrom(&vpu_mmio_).set_src_sel(2).WriteTo(&vpu_mmio_);
zxlogf(INFO, "done");
}
void HdmiHost::ConfigPhy() {
HhiHdmiPhyCntl0Reg::Get().FromValue(0).WriteTo(&hhi_mmio_);
HhiHdmiPhyCntl1Reg::Get()
.ReadFrom(&hhi_mmio_)
.set_hdmi_tx_phy_soft_reset(0)
.set_hdmi_tx_phy_clk_en(0)
.set_hdmi_fifo_enable(0)
.set_hdmi_fifo_wr_enable(0)
.set_msb_lsb_swap(0)
.set_bit_invert(0)
.set_ch0_swap(0)
.set_ch1_swap(1)
.set_ch2_swap(2)
.set_ch3_swap(3)
.set_new_prbs_en(0)
.set_new_prbs_sel(0)
.set_new_prbs_prbsmode(0)
.set_new_prbs_mode(0)
.WriteTo(&hhi_mmio_);
HhiHdmiPhyCntl1Reg::Get()
.ReadFrom(&hhi_mmio_)
.set_hdmi_tx_phy_soft_reset(1)
.set_hdmi_tx_phy_clk_en(1)
.set_hdmi_fifo_enable(1)
.set_hdmi_fifo_wr_enable(1)
.WriteTo(&hhi_mmio_);
usleep(2);
HhiHdmiPhyCntl1Reg::Get()
.ReadFrom(&hhi_mmio_)
.set_hdmi_tx_phy_soft_reset(0)
.set_hdmi_tx_phy_clk_en(1)
.set_hdmi_fifo_enable(1)
.set_hdmi_fifo_wr_enable(1)
.WriteTo(&hhi_mmio_);
usleep(2);
HhiHdmiPhyCntl1Reg::Get()
.ReadFrom(&hhi_mmio_)
.set_hdmi_tx_phy_soft_reset(1)
.set_hdmi_tx_phy_clk_en(1)
.set_hdmi_fifo_enable(1)
.set_hdmi_fifo_wr_enable(1)
.WriteTo(&hhi_mmio_);
usleep(2);
HhiHdmiPhyCntl1Reg::Get()
.ReadFrom(&hhi_mmio_)
.set_hdmi_tx_phy_soft_reset(0)
.set_hdmi_tx_phy_clk_en(1)
.set_hdmi_fifo_enable(1)
.set_hdmi_fifo_wr_enable(1)
.WriteTo(&hhi_mmio_);
usleep(2);
// The following configuration for HDMI PHY control register 0, 3 and 5 only
// works for display modes where the display resolution is lower than
// 3840 x 2160. The configuration currently works for all display modes
// supported by this driver.
//
// TODO(https://fxbug.dev/42075808): Set the PHY control registers properly if the
// display uses a 4k resolution (3840 x 2160 or higher).
HhiHdmiPhyCntl0Reg::Get().FromValue(0).set_hdmi_ctl1(0x33eb).set_hdmi_ctl2(0x4242).WriteTo(
&hhi_mmio_);
HhiHdmiPhyCntl3Reg::Get().FromValue(0x2ab0ff3b).WriteTo(&hhi_mmio_);
HhiHdmiPhyCntl5Reg::Get().FromValue(0x00000003).WriteTo(&hhi_mmio_);
usleep(20);
zxlogf(INFO, "done!");
}
} // namespace amlogic_display