| // 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 |