blob: 0e5681cf49aabb647736ed706be66d9f7d65aa54 [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/vout.h"
#include <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <fuchsia/hardware/display/controller/c/banjo.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/stdcompat/span.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstdint>
#include <memory>
#include <fbl/alloc_checker.h>
#include "src/graphics/display/drivers/amlogic-display/clock.h"
#include "src/graphics/display/drivers/amlogic-display/common.h"
#include "src/graphics/display/drivers/amlogic-display/dsi-host.h"
#include "src/graphics/display/drivers/amlogic-display/logging.h"
#include "src/graphics/display/drivers/amlogic-display/panel-config.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/driver-framework-migration-utils/logging/zxlogf.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/namespace/namespace.h"
namespace amlogic_display {
namespace {
// List of supported features
struct supported_features_t {
bool hpd;
};
constexpr supported_features_t kDsiSupportedFeatures = supported_features_t{
.hpd = false,
};
constexpr supported_features_t kHdmiSupportedFeatures = supported_features_t{
.hpd = true,
};
} // namespace
Vout::Vout(std::unique_ptr<DsiHost> dsi_host, std::unique_ptr<Clock> dsi_clock, uint32_t width,
uint32_t height, const PanelConfig* panel_config, inspect::Node node)
: type_(VoutType::kDsi),
supports_hpd_(kDsiSupportedFeatures.hpd),
node_(std::move(node)),
dsi_{
.dsi_host = std::move(dsi_host),
.clock = std::move(dsi_clock),
.width = width,
.height = height,
.panel_config = *panel_config,
} {
ZX_DEBUG_ASSERT(panel_config != nullptr);
node_.RecordInt("vout_type", static_cast<int>(type()));
}
Vout::Vout(std::unique_ptr<HdmiHost> hdmi_host, inspect::Node node)
: type_(VoutType::kHdmi),
supports_hpd_(kHdmiSupportedFeatures.hpd),
node_(std::move(node)),
hdmi_{.hdmi_host = std::move(hdmi_host)} {
node_.RecordInt("vout_type", static_cast<int>(type()));
}
zx::result<std::unique_ptr<Vout>> Vout::CreateDsiVout(display::Namespace& incoming,
uint32_t panel_type, uint32_t width,
uint32_t height, inspect::Node node) {
zxlogf(INFO, "Fixed panel type is %d", panel_type);
const PanelConfig* panel_config = GetPanelConfig(panel_type);
if (panel_config == nullptr) {
zxlogf(ERROR, "Failed to get panel config for panel %" PRIu32, panel_type);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<std::unique_ptr<DsiHost>> dsi_host_result =
DsiHost::Create(incoming, panel_type, panel_config);
if (dsi_host_result.is_error()) {
zxlogf(ERROR, "Could not create DSI host: %s", dsi_host_result.status_string());
return dsi_host_result.take_error();
}
std::unique_ptr<DsiHost> dsi_host = std::move(dsi_host_result).value();
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, "Failed to get a valid platform device client");
return zx::error(ZX_ERR_INTERNAL);
}
zx::result<std::unique_ptr<Clock>> clock_result =
Clock::Create(platform_device, kBootloaderDisplayEnabled);
if (clock_result.is_error()) {
zxlogf(ERROR, "Could not create Clock: %s", clock_result.status_string());
return clock_result.take_error();
}
std::unique_ptr<Clock> clock = std::move(clock_result).value();
fbl::AllocChecker alloc_checker;
std::unique_ptr<Vout> vout =
fbl::make_unique_checked<Vout>(&alloc_checker, std::move(dsi_host), std::move(clock), width,
height, panel_config, std::move(node));
if (!alloc_checker.check()) {
zxlogf(ERROR, "Failed to allocate memory for Vout.");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(vout));
}
zx::result<std::unique_ptr<Vout>> Vout::CreateDsiVoutForTesting(uint32_t panel_type, uint32_t width,
uint32_t height) {
const PanelConfig* panel_config = GetPanelConfig(panel_type);
if (panel_config == nullptr) {
zxlogf(ERROR, "Failed to get panel config for panel %" PRIu32, panel_type);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
fbl::AllocChecker alloc_checker;
std::unique_ptr<Vout> vout = fbl::make_unique_checked<Vout>(
&alloc_checker,
/*dsi_host=*/nullptr, /*dsi_clock=*/nullptr, width, height, panel_config, inspect::Node{});
if (!alloc_checker.check()) {
zxlogf(ERROR, "Failed to allocate memory for Vout.");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(vout));
}
zx::result<std::unique_ptr<Vout>> Vout::CreateHdmiVout(display::Namespace& incoming,
inspect::Node node) {
zx::result<std::unique_ptr<HdmiHost>> hdmi_host_result = HdmiHost::Create(incoming);
if (hdmi_host_result.is_error()) {
zxlogf(ERROR, "Could not create HDMI host: %s", hdmi_host_result.status_string());
return hdmi_host_result.take_error();
}
fbl::AllocChecker alloc_checker;
std::unique_ptr<Vout> vout = fbl::make_unique_checked<Vout>(
&alloc_checker, std::move(hdmi_host_result).value(), std::move(node));
if (!alloc_checker.check()) {
zxlogf(ERROR, "Failed to allocate memory for Vout.");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(vout));
}
void Vout::PopulateAddedDisplayArgs(
added_display_args_t* args, display::DisplayId display_id,
cpp20::span<const fuchsia_images2_pixel_format_enum_value_t> pixel_formats) {
switch (type_) {
case VoutType::kDsi: {
args->display_id = display::ToBanjoDisplayId(display_id);
args->panel_capabilities_source = PANEL_CAPABILITIES_SOURCE_DISPLAY_MODE;
args->panel.mode = display::ToBanjoDisplayMode(dsi_.panel_config.display_timing);
args->pixel_format_list = pixel_formats.data();
args->pixel_format_count = pixel_formats.size();
return;
}
case VoutType::kHdmi:
args->display_id = display::ToBanjoDisplayId(display_id);
args->panel_capabilities_source = PANEL_CAPABILITIES_SOURCE_EDID_I2C;
args->panel.i2c.ops = &i2c_impl_protocol_ops_;
args->panel.i2c.ctx = this;
args->pixel_format_list = pixel_formats.data();
args->pixel_format_count = pixel_formats.size();
return;
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
void Vout::DisplayConnected() {
switch (type_) {
case VoutType::kHdmi:
// A new connected display is not yet set up with any display timing.
hdmi_.current_display_timing_ = {};
return;
case VoutType::kDsi:
return;
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
void Vout::DisplayDisconnected() {
switch (type_) {
case VoutType::kHdmi:
hdmi_.hdmi_host->HostOff();
return;
case VoutType::kDsi:
return;
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
zx::result<> Vout::PowerOff() {
switch (type_) {
case VoutType::kDsi: {
dsi_.clock->Disable();
dsi_.dsi_host->Disable();
return zx::ok();
}
case VoutType::kHdmi: {
hdmi_.hdmi_host->HostOff();
return zx::ok();
}
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
zx::result<> Vout::PowerOn() {
switch (type_) {
case VoutType::kDsi: {
zx::result<> clock_enable_result = dsi_.clock->Enable(dsi_.panel_config);
if (!clock_enable_result.is_ok()) {
zxlogf(ERROR, "Could not enable display clocks: %s", clock_enable_result.status_string());
return clock_enable_result;
}
dsi_.clock->SetVideoOn(false);
// Configure and enable DSI host interface.
zx::result<> dsi_host_enable_result = dsi_.dsi_host->Enable(dsi_.clock->GetBitrate());
if (!dsi_host_enable_result.is_ok()) {
zxlogf(ERROR, "Could not enable DSI Host: %s", dsi_host_enable_result.status_string());
return dsi_host_enable_result;
}
dsi_.clock->SetVideoOn(true);
return zx::ok();
}
case VoutType::kHdmi: {
zx::result<> hdmi_host_on_result = zx::make_result(hdmi_.hdmi_host->HostOn());
if (!hdmi_host_on_result.is_ok()) {
zxlogf(ERROR, "Could not enable HDMI host: %s", hdmi_host_on_result.status_string());
return hdmi_host_on_result;
}
hdmi_.current_display_timing_ = {};
return zx::ok();
}
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
zx::result<> Vout::SetFrameVisibility(bool frame_visible) {
switch (type_) {
case VoutType::kDsi:
return zx::error(ZX_ERR_NOT_SUPPORTED);
case VoutType::kHdmi:
hdmi_.hdmi_host->ReplaceEncoderPixelColorWithBlack(!frame_visible);
return zx::ok();
}
}
bool Vout::IsDisplayTimingSupported(const display::DisplayTiming& timing) {
ZX_DEBUG_ASSERT_MSG(type_ == VoutType::kHdmi,
"Vout display timing check is only supported for HDMI output.");
return hdmi_.hdmi_host->IsDisplayTimingSupported(timing);
}
zx::result<> Vout::ApplyConfiguration(const display::DisplayTiming& timing) {
ZX_DEBUG_ASSERT_MSG(type_ == VoutType::kHdmi,
"Vout display timing setup is only supported for HDMI output.");
zx_status_t status = hdmi_.hdmi_host->ModeSet(timing);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to set HDMI display timing: %s", zx_status_get_string(status));
return zx::error(status);
}
hdmi_.current_display_timing_ = timing;
return zx::ok();
}
zx_status_t Vout::I2cImplTransact(const i2c_impl_op_t* op_list, size_t op_count) {
switch (type_) {
case VoutType::kHdmi:
return hdmi_.hdmi_host->EdidTransfer(op_list, op_count);
case VoutType::kDsi:
return ZX_ERR_NOT_SUPPORTED;
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
void Vout::Dump() {
switch (type_) {
case VoutType::kDsi: {
LogPanelConfig(dsi_.panel_config);
return;
}
case VoutType::kHdmi:
zxlogf(INFO, "horizontal_active_px = %d", hdmi_.current_display_timing_.horizontal_active_px);
zxlogf(INFO, "horizontal_front_porch_px = %d",
hdmi_.current_display_timing_.horizontal_front_porch_px);
zxlogf(INFO, "horizontal_sync_width_px = %d",
hdmi_.current_display_timing_.horizontal_sync_width_px);
zxlogf(INFO, "horizontal_back_porch_px = %d",
hdmi_.current_display_timing_.horizontal_back_porch_px);
zxlogf(INFO, "vertical_active_lines = %d",
hdmi_.current_display_timing_.vertical_active_lines);
zxlogf(INFO, "vertical_front_porch_lines = %d",
hdmi_.current_display_timing_.vertical_front_porch_lines);
zxlogf(INFO, "vertical_sync_width_lines = %d",
hdmi_.current_display_timing_.vertical_sync_width_lines);
zxlogf(INFO, "vertical_back_porch_lines = %d",
hdmi_.current_display_timing_.vertical_back_porch_lines);
zxlogf(INFO, "pixel_clock_frequency_hz = %" PRId64,
hdmi_.current_display_timing_.pixel_clock_frequency_hz);
zxlogf(INFO, "fields_per_frame (enum) = %u",
static_cast<uint32_t>(hdmi_.current_display_timing_.fields_per_frame));
zxlogf(INFO, "hsync_polarity (enum) = %u",
static_cast<uint32_t>(hdmi_.current_display_timing_.hsync_polarity));
zxlogf(INFO, "vsync_polarity (enum) = %u",
static_cast<uint32_t>(hdmi_.current_display_timing_.vsync_polarity));
zxlogf(INFO, "vblank_alternates = %d", hdmi_.current_display_timing_.vblank_alternates);
zxlogf(INFO, "pixel_repetition = %d", hdmi_.current_display_timing_.pixel_repetition);
return;
}
ZX_ASSERT_MSG(false, "Invalid Vout type: %u", static_cast<uint8_t>(type_));
}
} // namespace amlogic_display