blob: b2b893a31455c1c3eb3adb9bbcb18c848ab4e309 [file] [log] [blame]
// Copyright 2018 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/clock.h"
#include <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <cstdint>
#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/dsi.h"
#include "src/graphics/display/drivers/amlogic-display/fixed-point-util.h"
#include "src/graphics/display/drivers/amlogic-display/hhi-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/driver-framework-migration-utils/logging/zxlogf.h"
namespace amlogic_display {
Clock::Clock(fdf::MmioBuffer vpu_mmio, fdf::MmioBuffer hhi_mmio, bool clock_enabled)
: vpu_mmio_(std::move(vpu_mmio)),
hhi_mmio_(std::move(hhi_mmio)),
clock_enabled_(clock_enabled) {}
// static
LcdTiming Clock::CalculateLcdTiming(const display::DisplayTiming& d) {
LcdTiming out;
// Calculate and store DataEnable horizontal and vertical start/stop times
const uint32_t de_hstart = d.horizontal_total_px() - d.horizontal_active_px - 1;
const uint32_t de_vstart = d.vertical_total_lines() - d.vertical_active_lines;
out.vid_pixel_on = de_hstart;
out.vid_line_on = de_vstart;
// Calculate and Store HSync horizontal and vertical start/stop times
const uint32_t hstart = (de_hstart + d.horizontal_total_px() - d.horizontal_back_porch_px -
d.horizontal_sync_width_px) %
d.horizontal_total_px();
const uint32_t hend =
(de_hstart + d.horizontal_total_px() - d.horizontal_back_porch_px) % d.horizontal_total_px();
out.hs_hs_addr = hstart;
out.hs_he_addr = hend;
// Calculate and Store VSync horizontal and vertical start/stop times
out.vs_hs_addr = (hstart + d.horizontal_total_px()) % d.horizontal_total_px();
out.vs_he_addr = out.vs_hs_addr;
const uint32_t vstart = (de_vstart + d.vertical_total_lines() - d.vertical_back_porch_lines -
d.vertical_sync_width_lines) %
d.vertical_total_lines();
const uint32_t vend = (de_vstart + d.vertical_total_lines() - d.vertical_back_porch_lines) %
d.vertical_total_lines();
out.vs_vs_addr = vstart;
out.vs_ve_addr = vend;
return out;
}
zx::result<> Clock::WaitForHdmiPllToLock() {
uint32_t pll_lock;
constexpr int kMaxPllLockAttempt = 3;
for (int lock_attempts = 0; lock_attempts < kMaxPllLockAttempt; lock_attempts++) {
zxlogf(TRACE, "Waiting for PLL Lock: (%d/3).", lock_attempts + 1);
// The configurations used in retries are from Amlogic-provided code which
// is undocumented.
if (lock_attempts == 1) {
hhi_mmio_.Write32(
SetFieldValue32(hhi_mmio_.Read32(HHI_HDMI_PLL_CNTL3), /*field_begin_bit=*/31,
/*field_size_bits=*/1, /*field_value=*/1),
HHI_HDMI_PLL_CNTL3);
} else if (lock_attempts == 2) {
hhi_mmio_.Write32(0x55540000, HHI_HDMI_PLL_CNTL6); // more magic
}
int retries = 1000;
while ((pll_lock = GetFieldValue32(hhi_mmio_.Read32(HHI_HDMI_PLL_CNTL0),
/*field_begin_bit=*/LCD_PLL_LOCK_HPLL_G12A,
/*field_size_bits=*/1)) != 1 &&
retries--) {
zx_nanosleep(zx_deadline_after(ZX_USEC(50)));
}
if (pll_lock) {
return zx::ok();
}
}
zxlogf(ERROR, "Failed to lock HDMI PLL after %d attempts.", kMaxPllLockAttempt);
return zx::error(ZX_ERR_UNAVAILABLE);
}
// static
zx::result<HdmiPllConfigForMipiDsi> Clock::GenerateHPLL(
int64_t pixel_clock_frequency_hz, int64_t maximum_per_data_lane_bit_per_second) {
HdmiPllConfigForMipiDsi pll_cfg;
// Requested Pixel clock
if (pixel_clock_frequency_hz > kMaxPixelClockFrequencyHz) {
zxlogf(ERROR, "Pixel clock out of range (%" PRId64 " Hz)", pixel_clock_frequency_hz);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
constexpr uint32_t kMinClockFactor = 1u;
constexpr uint32_t kMaxClockFactor = 255u;
// If clock factor is not specified in display panel configuration, the driver
// will find the first valid clock factor in between kMinClockFactor and
// kMaxClockFactor (both inclusive).
uint32_t clock_factor_min = kMinClockFactor;
uint32_t clock_factor_max = kMaxClockFactor;
for (uint32_t clock_factor = clock_factor_min; clock_factor <= clock_factor_max; clock_factor++) {
pll_cfg.clock_factor = clock_factor;
// Desired PLL Frequency based on pixel clock needed
const int64_t requested_pll_frequency_hz = pixel_clock_frequency_hz * pll_cfg.clock_factor;
// Make sure all clocks are within range
// If these values are not within range, we will not have a valid display
const int64_t dphy_data_lane_bit_rate_max_hz = maximum_per_data_lane_bit_per_second;
const int64_t dphy_data_lane_bit_rate_min_hz =
dphy_data_lane_bit_rate_max_hz - pixel_clock_frequency_hz;
if ((requested_pll_frequency_hz < dphy_data_lane_bit_rate_min_hz) ||
(requested_pll_frequency_hz > dphy_data_lane_bit_rate_max_hz)) {
zxlogf(TRACE, "Calculated clocks out of range for xd = %u, skipped", clock_factor);
continue;
}
// Now that we have valid frequency ranges, let's calculated all the PLL-related
// multipliers/dividers
// [fin] * [m/n] = [voltage_controlled_oscillator_output_frequency_hz]
// [voltage_controlled_oscillator_output_frequency_khz] / [output_divider1]
// / [output_divider2] / [output_divider3]
// = requested_pll_frequency
//
// The PLL output is used for the MIPI D-PHY clock lane; its frequency
// must be equal to the MIPI D-PHY data lane bit rate.
int32_t output_divider3 = (1 << (MAX_OD_SEL - 1));
while (output_divider3 != 0) {
const int64_t output_divider3_input_frequency_hz =
requested_pll_frequency_hz * output_divider3;
int output_divider2 = output_divider3;
while (output_divider2 != 0) {
const int64_t output_divider2_input_frequency_hz =
output_divider3_input_frequency_hz * output_divider2;
int32_t output_divider1 = output_divider2;
while (output_divider1 != 0) {
const int64_t output_divider1_input_frequency_hz =
output_divider2_input_frequency_hz * output_divider1;
const int64_t voltage_controlled_oscillator_output_frequency_hz =
output_divider1_input_frequency_hz;
if ((voltage_controlled_oscillator_output_frequency_hz >=
kMinVoltageControlledOscillatorFrequencyHz) &&
(voltage_controlled_oscillator_output_frequency_hz <=
kMaxVoltageControlledOscillatorFrequencyHz)) {
// within range!
pll_cfg.output_divider1_selection = output_divider1 >> 1;
pll_cfg.output_divider2_selection = output_divider2 >> 1;
pll_cfg.output_divider3_selection = output_divider3 >> 1;
pll_cfg.pll_frequency_hz = requested_pll_frequency_hz;
zxlogf(TRACE, "od1=%d, od2=%d, od3=%d", (output_divider1 >> 1), (output_divider2 >> 1),
(output_divider3 >> 1));
zxlogf(TRACE, "pll_fvco=%" PRId64, voltage_controlled_oscillator_output_frequency_hz);
pll_cfg.pll_voltage_controlled_oscillator_output_frequency_hz =
voltage_controlled_oscillator_output_frequency_hz;
// For simplicity, assume pll_divider = 1.
pll_cfg.pll_divider = 1;
// Calculate pll_multiplier such that
// kExternalOscillatorFrequencyHz x pll_multiplier =
// voltage_controlled_oscillator_output_frequency_hz
pll_cfg.pll_multiplier_integer = static_cast<int32_t>(
voltage_controlled_oscillator_output_frequency_hz / kExternalOscillatorFrequencyHz);
pll_cfg.pll_multiplier_fraction = (voltage_controlled_oscillator_output_frequency_hz %
kExternalOscillatorFrequencyHz) *
PLL_FRAC_RANGE / kExternalOscillatorFrequencyHz;
zxlogf(TRACE, "m=%d, n=%d, frac=0x%x", pll_cfg.pll_multiplier_integer,
pll_cfg.pll_divider, pll_cfg.pll_multiplier_fraction);
pll_cfg.dphy_data_lane_bits_per_second = pll_cfg.pll_frequency_hz; // Hz
return zx::ok(std::move(pll_cfg));
}
output_divider1 >>= 1;
}
output_divider2 >>= 1;
}
output_divider3 >>= 1;
}
}
zxlogf(ERROR, "Could not generate correct PLL values for: ");
zxlogf(ERROR, " pixel_clock_frequency_hz = %" PRId64, pixel_clock_frequency_hz);
zxlogf(ERROR, " max_per_data_lane_bit_rate_hz = %" PRId64, maximum_per_data_lane_bit_per_second);
return zx::error(ZX_ERR_INTERNAL);
}
void Clock::Disable() {
if (!clock_enabled_) {
return;
}
vpu_mmio_.Write32(0, ENCL_VIDEO_EN);
VideoClockOutputControl::Get()
.ReadFrom(&hhi_mmio_)
.set_encoder_lvds_enabled(false)
.WriteTo(&hhi_mmio_);
VideoClock2Control::Get()
.ReadFrom(&hhi_mmio_)
.set_div1_enabled(false)
.set_div2_enabled(false)
.set_div4_enabled(false)
.set_div6_enabled(false)
.set_div12_enabled(false)
.WriteTo(&hhi_mmio_);
VideoClock2Control::Get().ReadFrom(&hhi_mmio_).set_clock_enabled(false).WriteTo(&hhi_mmio_);
// disable pll
hhi_mmio_.Write32(SetFieldValue32(hhi_mmio_.Read32(HHI_HDMI_PLL_CNTL0),
/*field_begin_bit=*/LCD_PLL_EN_HPLL_G12A,
/*field_size_bits=*/1, /*field_value=*/0),
HHI_HDMI_PLL_CNTL0);
clock_enabled_ = false;
}
zx::result<> Clock::Enable(const PanelConfig& panel_config) {
if (clock_enabled_) {
return zx::ok();
}
// Populate internal LCD timing structure based on predefined tables
lcd_timing_ = CalculateLcdTiming(panel_config.display_timing);
zx::result<HdmiPllConfigForMipiDsi> pll_result =
GenerateHPLL(panel_config.display_timing.pixel_clock_frequency_hz,
panel_config.maximum_per_data_lane_bit_per_second());
if (pll_result.is_error()) {
zxlogf(ERROR, "Failed to generate HDMI PLL and Video clock tree configuration: %s",
pll_result.status_string());
return pll_result.take_error();
}
pll_cfg_ = std::move(pll_result).value();
uint32_t regVal;
bool useFrac = pll_cfg_.pll_multiplier_fraction != 0;
regVal = ((1 << LCD_PLL_EN_HPLL_G12A) | (1 << LCD_PLL_OUT_GATE_CTRL_G12A) | // clk out gate
(pll_cfg_.pll_divider << LCD_PLL_N_HPLL_G12A) |
(pll_cfg_.pll_multiplier_integer << LCD_PLL_M_HPLL_G12A) |
(pll_cfg_.output_divider1_selection << LCD_PLL_OD1_HPLL_G12A) |
(pll_cfg_.output_divider2_selection << LCD_PLL_OD2_HPLL_G12A) |
(pll_cfg_.output_divider3_selection << LCD_PLL_OD3_HPLL_G12A) |
(useFrac ? (1 << 27) : (0 << 27)));
hhi_mmio_.Write32(regVal, HHI_HDMI_PLL_CNTL0);
hhi_mmio_.Write32(pll_cfg_.pll_multiplier_fraction, HHI_HDMI_PLL_CNTL1);
hhi_mmio_.Write32(0x00, HHI_HDMI_PLL_CNTL2);
// Magic numbers from U-Boot.
hhi_mmio_.Write32(useFrac ? 0x6a285c00 : 0x48681c00, HHI_HDMI_PLL_CNTL3);
hhi_mmio_.Write32(useFrac ? 0x65771290 : 0x33771290, HHI_HDMI_PLL_CNTL4);
hhi_mmio_.Write32(0x39272000, HHI_HDMI_PLL_CNTL5);
hhi_mmio_.Write32(useFrac ? 0x56540000 : 0x56540000, HHI_HDMI_PLL_CNTL6);
// reset dpll
hhi_mmio_.Write32(SetFieldValue32(hhi_mmio_.Read32(HHI_HDMI_PLL_CNTL0),
/*field_begin_bit=*/LCD_PLL_RST_HPLL_G12A,
/*field_size_bits=*/1, /*field_value=*/1),
HHI_HDMI_PLL_CNTL0);
zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
// release from reset
hhi_mmio_.Write32(SetFieldValue32(hhi_mmio_.Read32(HHI_HDMI_PLL_CNTL0),
/*field_begin_bit=*/LCD_PLL_RST_HPLL_G12A,
/*field_size_bits=*/1, /*field_value=*/0),
HHI_HDMI_PLL_CNTL0);
zx_nanosleep(zx_deadline_after(ZX_USEC(50)));
zx::result<> wait_for_pll_lock_result = WaitForHdmiPllToLock();
if (!wait_for_pll_lock_result.is_ok()) {
zxlogf(ERROR, "Failed to lock HDMI PLL: %s", wait_for_pll_lock_result.status_string());
return wait_for_pll_lock_result.take_error();
}
// Disable Video Clock mux 2 since we are changing its input selection.
VideoClock2Control::Get().ReadFrom(&hhi_mmio_).set_clock_enabled(false).WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(5)));
// Sets the HDMI clock tree frequency division ratio to 1.
// Disable the HDMI clock tree output
HdmiClockTreeControl hdmi_clock_tree_control = HdmiClockTreeControl::Get().ReadFrom(&hhi_mmio_);
hdmi_clock_tree_control.set_clock_output_enabled(false).WriteTo(&hhi_mmio_);
hdmi_clock_tree_control.set_preset_pattern_update_enabled(false).WriteTo(&hhi_mmio_);
hdmi_clock_tree_control.SetFrequencyDividerRatio(ToU28_4(1.0)).WriteTo(&hhi_mmio_);
// Enable the final output clock
hdmi_clock_tree_control.set_clock_output_enabled(true).WriteTo(&hhi_mmio_);
// Enable DSI measure clocks.
VideoInputMeasureClockControl::Get()
.ReadFrom(&hhi_mmio_)
.set_dsi_measure_clock_selection(
VideoInputMeasureClockControl::ClockSource::kExternalOscillator24Mhz)
.WriteTo(&hhi_mmio_);
VideoInputMeasureClockControl::Get()
.ReadFrom(&hhi_mmio_)
.SetDsiMeasureClockDivider(1)
.WriteTo(&hhi_mmio_);
VideoInputMeasureClockControl::Get()
.ReadFrom(&hhi_mmio_)
.set_dsi_measure_clock_enabled(true)
.WriteTo(&hhi_mmio_);
// Use Video PLL (vid_pll) as MIPI_DSY PHY clock source.
MipiDsiPhyClockControl::Get()
.ReadFrom(&hhi_mmio_)
.set_clock_source(MipiDsiPhyClockControl::ClockSource::kVideoPll)
.WriteTo(&hhi_mmio_);
// Enable MIPI-DSY PHY clock.
MipiDsiPhyClockControl::Get().ReadFrom(&hhi_mmio_).set_enabled(true).WriteTo(&hhi_mmio_);
// Set divider to 1.
// TODO(https://fxbug.dev/42082049): This should occur before enabling the clock.
MipiDsiPhyClockControl::Get().ReadFrom(&hhi_mmio_).SetDivider(1).WriteTo(&hhi_mmio_);
// Set the Video clock 2 divider.
VideoClock2Divider::Get()
.ReadFrom(&hhi_mmio_)
.SetDivider2(pll_cfg_.clock_factor)
.WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(5)));
VideoClock2Control::Get()
.ReadFrom(&hhi_mmio_)
.set_mux_source(VideoClockMuxSource::kVideoPll)
.WriteTo(&hhi_mmio_);
VideoClock2Control::Get().ReadFrom(&hhi_mmio_).set_clock_enabled(true).WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(2)));
// Select video clock 2 for ENCL clock.
VideoClock2Divider::Get()
.ReadFrom(&hhi_mmio_)
.set_encl_clock_selection(EncoderClockSource::kVideoClock2)
.WriteTo(&hhi_mmio_);
// Enable video clock 2 divider.
VideoClock2Divider::Get().ReadFrom(&hhi_mmio_).set_divider_enabled(true).WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(5)));
VideoClock2Control::Get().ReadFrom(&hhi_mmio_).set_div1_enabled(true).WriteTo(&hhi_mmio_);
VideoClock2Control::Get().ReadFrom(&hhi_mmio_).set_soft_reset(true).WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
VideoClock2Control::Get().ReadFrom(&hhi_mmio_).set_soft_reset(false).WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(5)));
// Enable ENCL clock output.
VideoClockOutputControl::Get()
.ReadFrom(&hhi_mmio_)
.set_encoder_lvds_enabled(true)
.WriteTo(&hhi_mmio_);
zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
vpu_mmio_.Write32(0, ENCL_VIDEO_EN);
// Connect both VIUs (Video Input Units) to the LCD Encoder.
VideoInputUnitEncoderMuxControl::Get()
.ReadFrom(&vpu_mmio_)
.set_vsync_shared_by_viu_blocks(false)
.set_viu1_encoder_selection(VideoInputUnitEncoderMuxControl::Encoder::kLcd)
.set_viu2_encoder_selection(VideoInputUnitEncoderMuxControl::Encoder::kLcd)
.WriteTo(&vpu_mmio_);
// Undocumented registers below
vpu_mmio_.Write32(0x8000, ENCL_VIDEO_MODE); // bit[15] shadown en
vpu_mmio_.Write32(0x0418, ENCL_VIDEO_MODE_ADV); // Sampling rate: 1
// bypass filter -- Undocumented registers
const display::DisplayTiming& display_timing = panel_config.display_timing;
vpu_mmio_.Write32(0x1000, ENCL_VIDEO_FILT_CTRL);
vpu_mmio_.Write32(display_timing.horizontal_total_px() - 1, ENCL_VIDEO_MAX_PXCNT);
vpu_mmio_.Write32(display_timing.vertical_total_lines() - 1, ENCL_VIDEO_MAX_LNCNT);
vpu_mmio_.Write32(lcd_timing_.vid_pixel_on, ENCL_VIDEO_HAVON_BEGIN);
vpu_mmio_.Write32(display_timing.horizontal_active_px - 1 + lcd_timing_.vid_pixel_on,
ENCL_VIDEO_HAVON_END);
vpu_mmio_.Write32(lcd_timing_.vid_line_on, ENCL_VIDEO_VAVON_BLINE);
vpu_mmio_.Write32(display_timing.vertical_active_lines - 1 + lcd_timing_.vid_line_on,
ENCL_VIDEO_VAVON_ELINE);
vpu_mmio_.Write32(lcd_timing_.hs_hs_addr, ENCL_VIDEO_HSO_BEGIN);
vpu_mmio_.Write32(lcd_timing_.hs_he_addr, ENCL_VIDEO_HSO_END);
vpu_mmio_.Write32(lcd_timing_.vs_hs_addr, ENCL_VIDEO_VSO_BEGIN);
vpu_mmio_.Write32(lcd_timing_.vs_he_addr, ENCL_VIDEO_VSO_END);
vpu_mmio_.Write32(lcd_timing_.vs_vs_addr, ENCL_VIDEO_VSO_BLINE);
vpu_mmio_.Write32(lcd_timing_.vs_ve_addr, ENCL_VIDEO_VSO_ELINE);
vpu_mmio_.Write32(3, ENCL_VIDEO_RGBIN_CTRL);
vpu_mmio_.Write32(1, ENCL_VIDEO_EN);
vpu_mmio_.Write32(0, L_RGB_BASE_ADDR);
vpu_mmio_.Write32(0x400, L_RGB_COEFF_ADDR);
vpu_mmio_.Write32(0x400, L_DITH_CNTL_ADDR);
// The driver behavior here deviates from the Amlogic-provided code.
//
// The Amlogic-provided code sets up the timing controller (TCON) within the
// LCD Encoder to generate Display Enable (DE), Horizontal Sync (HSYNC) and
// Vertical Sync (VSYNC) timing signals.
//
// These signals are useful for LVDS or TTL LCD interfaces, but not for the
// MIPI-DSI interface. The MIPI-DSI Host Controller IP block always sets the
// output timings using the values from its own control registers, and it
// doesn't use the outputs from the timing controller.
//
// Therefore, this driver doesn't set the timing controller registers and
// keeps the timing controller disabled. This was tested on Nelson (S905D3)
// and Khadas VIM3 (A311D) boards.
vpu_mmio_.Write32(vpu_mmio_.Read32(VPP_MISC) & ~(VPP_OUT_SATURATE), VPP_MISC);
// Ready to be used
clock_enabled_ = true;
return zx::ok();
}
void Clock::SetVideoOn(bool on) { vpu_mmio_.Write32(on, ENCL_VIDEO_EN); }
// static
zx::result<std::unique_ptr<Clock>> Clock::Create(
fidl::UnownedClientEnd<fuchsia_hardware_platform_device::Device> platform_device,
bool already_enabled) {
ZX_DEBUG_ASSERT(platform_device.is_valid());
zx::result<fdf::MmioBuffer> vpu_mmio_result = MapMmio(MmioResourceIndex::kVpu, platform_device);
if (vpu_mmio_result.is_error()) {
return vpu_mmio_result.take_error();
}
fdf::MmioBuffer vpu_mmio = std::move(vpu_mmio_result).value();
zx::result<fdf::MmioBuffer> hhi_mmio_result = MapMmio(MmioResourceIndex::kHhi, platform_device);
if (hhi_mmio_result.is_error()) {
return hhi_mmio_result.take_error();
}
fdf::MmioBuffer hhi_mmio = std::move(hhi_mmio_result).value();
fbl::AllocChecker alloc_checker;
auto clock =
fbl::make_unique_checked<Clock>(&alloc_checker, std::move(vpu_mmio), std::move(hhi_mmio),
/*clock_enabled=*/already_enabled);
if (!alloc_checker.check()) {
zxlogf(ERROR, "Failed to allocate memory for Clock");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(clock));
}
} // namespace amlogic_display