| // 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 "vout.h" |
| |
| #include <fbl/alloc_checker.h> |
| |
| namespace amlogic_display { |
| |
| namespace { |
| |
| // List of supported pixel formats |
| constexpr zx_pixel_format_t kDsiSupportedPixelFormats[4] = { |
| ZX_PIXEL_FORMAT_ARGB_8888, ZX_PIXEL_FORMAT_RGB_x888, ZX_PIXEL_FORMAT_ABGR_8888, |
| ZX_PIXEL_FORMAT_BGR_888x}; |
| // TODO(fxb/69236): Add more supported formats |
| zx_pixel_format_t kHdmiSupportedPixelFormats[] = {ZX_PIXEL_FORMAT_RGB_x888}; |
| |
| // List of supported features |
| struct supported_features_t { |
| bool afbc; |
| bool capture; |
| bool hpd; |
| }; |
| |
| // TODO(fxb/69025): read feature support from metadata instead of hardcoding. |
| constexpr supported_features_t kDsiSupportedFeatures = supported_features_t{ |
| .afbc = true, |
| .capture = true, |
| .hpd = false, |
| }; |
| |
| constexpr supported_features_t kHdmiSupportedFeatures = supported_features_t{ |
| .afbc = false, |
| .capture = false, |
| .hpd = true, |
| }; |
| |
| } // namespace |
| |
| zx_status_t Vout::InitDsi(zx_device_t* parent, uint32_t panel_type, uint32_t width, |
| uint32_t height) { |
| type_ = VoutType::kDsi; |
| |
| supports_afbc_ = kDsiSupportedFeatures.afbc; |
| supports_capture_ = kDsiSupportedFeatures.capture; |
| supports_hpd_ = kDsiSupportedFeatures.hpd; |
| |
| dsi_.width = width; |
| dsi_.height = height; |
| |
| const display_setting_t* init_disp_table; |
| switch (panel_type) { |
| case PANEL_TV070WSM_FT: |
| case PANEL_TV070WSM_FT_9365: |
| init_disp_table = &kDisplaySettingTV070WSM_FT; |
| break; |
| case PANEL_P070ACB_FT: |
| init_disp_table = &kDisplaySettingP070ACB_FT; |
| break; |
| case PANEL_KD070D82_FT_9365: |
| case PANEL_KD070D82_FT: |
| init_disp_table = &kDisplaySettingKD070D82_FT; |
| break; |
| case PANEL_TV101WXM_FT_9365: |
| case PANEL_TV101WXM_FT: |
| init_disp_table = &kDisplaySettingTV101WXM_FT; |
| break; |
| case PANEL_G101B158_FT: |
| init_disp_table = &kDisplaySettingG101B158_FT; |
| break; |
| case PANEL_TV080WXM_FT: |
| init_disp_table = &kDisplaySettingTV080WXM_FT; |
| break; |
| default: |
| DISP_ERROR("Unsupported panel detected!\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| dsi_.disp_setting.h_active = init_disp_table->h_active; |
| dsi_.disp_setting.v_active = init_disp_table->v_active; |
| dsi_.disp_setting.h_period = init_disp_table->h_period; |
| dsi_.disp_setting.v_period = init_disp_table->v_period; |
| dsi_.disp_setting.hsync_width = init_disp_table->hsync_width; |
| dsi_.disp_setting.hsync_bp = init_disp_table->hsync_bp; |
| dsi_.disp_setting.hsync_pol = init_disp_table->hsync_pol; |
| dsi_.disp_setting.vsync_width = init_disp_table->vsync_width; |
| dsi_.disp_setting.vsync_bp = init_disp_table->vsync_bp; |
| dsi_.disp_setting.vsync_pol = init_disp_table->vsync_pol; |
| dsi_.disp_setting.lcd_clock = init_disp_table->lcd_clock; |
| dsi_.disp_setting.clock_factor = init_disp_table->clock_factor; |
| dsi_.disp_setting.lane_num = init_disp_table->lane_num; |
| dsi_.disp_setting.bit_rate_max = init_disp_table->bit_rate_max; |
| |
| fbl::AllocChecker ac; |
| dsi_.dsi_host = fbl::make_unique_checked<amlogic_display::AmlDsiHost>(&ac, parent, panel_type); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Vout::InitHdmi(zx_device_t* parent) { |
| type_ = VoutType::kHdmi; |
| |
| supports_afbc_ = kHdmiSupportedFeatures.afbc; |
| supports_capture_ = kHdmiSupportedFeatures.capture; |
| supports_hpd_ = kHdmiSupportedFeatures.hpd; |
| |
| ddk::HdmiProtocolClient hdmi(parent, "hdmi"); |
| if (!hdmi.is_valid()) { |
| zxlogf(ERROR, "Could not get hdmi fragment"); |
| return ZX_ERR_INTERNAL; |
| } |
| zx::channel client_end, server_end; |
| zx_status_t status; |
| if ((status = zx::channel::create(0, &client_end, &server_end)) != ZX_OK) { |
| zxlogf(ERROR, "Could not create channel %d\n", status); |
| return status; |
| } |
| hdmi.Connect(std::move(server_end)); |
| |
| fbl::AllocChecker ac; |
| hdmi_.hdmi_host = |
| fbl::make_unique_checked<amlogic_display::AmlHdmiHost>(&ac, parent, std::move(client_end)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| status = hdmi_.hdmi_host->Init(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not initialize HDMI host %d\n", status); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Vout::RestartDisplay(zx_device_t* parent) { |
| ddk::PDev pdev; |
| zx_status_t status = ddk::PDev::FromFragment(parent, &pdev); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not get PDEV protocol\n"); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| switch (type_) { |
| case VoutType::kDsi: |
| dsi_.clock = fbl::make_unique_checked<amlogic_display::AmlogicDisplayClock>(&ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| status = dsi_.clock->Init(pdev); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not initialize Clock object\n"); |
| return status; |
| } |
| |
| // Enable all display related clocks |
| status = dsi_.clock->Enable(dsi_.disp_setting); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not enable display clocks!\n"); |
| return status; |
| } |
| |
| // Program and Enable DSI Host Interface |
| status = dsi_.dsi_host->Init(dsi_.clock->GetBitrate()); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not initialize DSI Host\n"); |
| return status; |
| } |
| |
| status = dsi_.dsi_host->HostOn(dsi_.disp_setting); |
| if (status != ZX_OK) { |
| DISP_ERROR("DSI Host On failed! %d\n", status); |
| return status; |
| } |
| break; |
| case VoutType::kHdmi: |
| status = hdmi_.hdmi_host->HostOn(); |
| if (status != ZX_OK) { |
| DISP_ERROR("HDMI initialization failed! %d\n", status); |
| return status; |
| } |
| break; |
| default: |
| DISP_ERROR("Unrecognized Vout type %u\n", type_); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void Vout::PopulateAddedDisplayArgs(added_display_args_t* args, uint64_t display_id) { |
| switch (type_) { |
| case VoutType::kDsi: |
| args->display_id = display_id; |
| args->edid_present = false; |
| args->panel.params.height = dsi_.height; |
| args->panel.params.width = dsi_.width; |
| args->panel.params.refresh_rate_e2 = 6000; // Just guess that it's 60fps |
| args->pixel_format_list = kDsiSupportedPixelFormats; |
| args->pixel_format_count = countof(kDsiSupportedPixelFormats); |
| args->cursor_info_count = 0; |
| break; |
| case VoutType::kHdmi: |
| args->display_id = display_id; |
| args->edid_present = true; |
| args->panel.i2c_bus_id = 0; |
| args->pixel_format_list = kHdmiSupportedPixelFormats; |
| args->pixel_format_count = countof(kHdmiSupportedPixelFormats); |
| args->cursor_info_count = 0; |
| break; |
| default: |
| zxlogf(ERROR, "Unrecognized vout type %u\n", type_); |
| return; |
| } |
| } |
| |
| bool Vout::IsFormatSupported(zx_pixel_format_t format) { |
| switch (type_) { |
| case VoutType::kDsi: |
| for (auto f : kDsiSupportedPixelFormats) { |
| if (f == format) { |
| return true; |
| } |
| } |
| return false; |
| case VoutType::kHdmi: |
| for (auto f : kHdmiSupportedPixelFormats) { |
| if (f == format) { |
| return true; |
| } |
| } |
| return false; |
| default: |
| return false; |
| } |
| } |
| |
| void Vout::DisplayConnected() { |
| switch (type_) { |
| case kHdmi: |
| memset(&hdmi_.cur_display_mode_, 0, sizeof(display_mode_t)); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void Vout::DisplayDisconnected() { |
| switch (type_) { |
| case kHdmi: |
| hdmi_.hdmi_host->HostOff(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool Vout::CheckMode(const display_mode_t* mode) { |
| switch (type_) { |
| case kDsi: |
| return false; |
| case kHdmi: |
| return memcmp(&hdmi_.cur_display_mode_, mode, sizeof(display_mode_t)) && |
| (hdmi_.hdmi_host->GetVic(mode) != ZX_OK); |
| default: |
| return false; |
| } |
| } |
| |
| zx_status_t Vout::ApplyConfiguration(const display_mode_t* mode) { |
| zx_status_t status; |
| switch (type_) { |
| case kDsi: |
| return ZX_OK; |
| case kHdmi: |
| if (!memcmp(&hdmi_.cur_display_mode_, mode, sizeof(display_mode_t))) { |
| // No new configs |
| return ZX_OK; |
| } |
| |
| display_mode_t modified_mode; |
| memcpy(&modified_mode, mode, sizeof(display_mode_t)); |
| status = hdmi_.hdmi_host->GetVic(&modified_mode); |
| if (status != ZX_OK) { |
| DISP_ERROR("Apply with bad mode"); |
| return status; |
| } |
| |
| memcpy(&hdmi_.cur_display_mode_, mode, sizeof(display_mode_t)); |
| // FIXME: Need documentation for HDMI PLL initialization |
| hdmi_.hdmi_host->ConfigurePll(); |
| hdmi_.hdmi_host->ModeSet(modified_mode); |
| return ZX_OK; |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t Vout::OnDisplaysChanged(added_display_info_t& info) { |
| switch (type_) { |
| case kDsi: |
| // Not used anywhere: ZX_PIXEL_FORMAT_RGB_x888; |
| return ZX_OK; |
| case kHdmi: |
| hdmi_.hdmi_host->UpdateOutputColorFormat( |
| info.is_standard_srgb_out ? fuchsia_hardware_hdmi::wire::ColorFormat::kCfRgb |
| : fuchsia_hardware_hdmi::wire::ColorFormat::kCf444); |
| return ZX_OK; |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t Vout::EdidTransfer(uint32_t bus_id, const i2c_impl_op_t* op_list, size_t op_count) { |
| switch (type_) { |
| case kHdmi: |
| hdmi_.hdmi_host->EdidTransfer(bus_id, op_list, op_count); |
| return ZX_OK; |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| void Vout::Dump() { |
| switch (type_) { |
| case VoutType::kDsi: |
| DISP_INFO("#############################\n"); |
| DISP_INFO("Dumping disp_setting structure:\n"); |
| DISP_INFO("#############################\n"); |
| DISP_INFO("h_active = 0x%x (%u)\n", dsi_.disp_setting.h_active, dsi_.disp_setting.h_active); |
| DISP_INFO("v_active = 0x%x (%u)\n", dsi_.disp_setting.v_active, dsi_.disp_setting.v_active); |
| DISP_INFO("h_period = 0x%x (%u)\n", dsi_.disp_setting.h_period, dsi_.disp_setting.h_period); |
| DISP_INFO("v_period = 0x%x (%u)\n", dsi_.disp_setting.v_period, dsi_.disp_setting.v_period); |
| DISP_INFO("hsync_width = 0x%x (%u)\n", dsi_.disp_setting.hsync_width, |
| dsi_.disp_setting.hsync_width); |
| DISP_INFO("hsync_bp = 0x%x (%u)\n", dsi_.disp_setting.hsync_bp, dsi_.disp_setting.hsync_bp); |
| DISP_INFO("hsync_pol = 0x%x (%u)\n", dsi_.disp_setting.hsync_pol, |
| dsi_.disp_setting.hsync_pol); |
| DISP_INFO("vsync_width = 0x%x (%u)\n", dsi_.disp_setting.vsync_width, |
| dsi_.disp_setting.vsync_width); |
| DISP_INFO("vsync_bp = 0x%x (%u)\n", dsi_.disp_setting.vsync_bp, dsi_.disp_setting.vsync_bp); |
| DISP_INFO("vsync_pol = 0x%x (%u)\n", dsi_.disp_setting.vsync_pol, |
| dsi_.disp_setting.vsync_pol); |
| DISP_INFO("lcd_clock = 0x%x (%u)\n", dsi_.disp_setting.lcd_clock, |
| dsi_.disp_setting.lcd_clock); |
| DISP_INFO("lane_num = 0x%x (%u)\n", dsi_.disp_setting.lane_num, dsi_.disp_setting.lane_num); |
| DISP_INFO("bit_rate_max = 0x%x (%u)\n", dsi_.disp_setting.bit_rate_max, |
| dsi_.disp_setting.bit_rate_max); |
| DISP_INFO("clock_factor = 0x%x (%u)\n", dsi_.disp_setting.clock_factor, |
| dsi_.disp_setting.clock_factor); |
| break; |
| case VoutType::kHdmi: |
| DISP_INFO("pixel_clock_10khz = 0x%x (%u)\n", hdmi_.cur_display_mode_.pixel_clock_10khz, |
| hdmi_.cur_display_mode_.pixel_clock_10khz); |
| DISP_INFO("h_addressable = 0x%x (%u)\n", hdmi_.cur_display_mode_.h_addressable, |
| hdmi_.cur_display_mode_.h_addressable); |
| DISP_INFO("h_front_porch = 0x%x (%u)\n", hdmi_.cur_display_mode_.h_front_porch, |
| hdmi_.cur_display_mode_.h_front_porch); |
| DISP_INFO("h_sync_pulse = 0x%x (%u)\n", hdmi_.cur_display_mode_.h_sync_pulse, |
| hdmi_.cur_display_mode_.h_sync_pulse); |
| DISP_INFO("h_blanking = 0x%x (%u)\n", hdmi_.cur_display_mode_.h_blanking, |
| hdmi_.cur_display_mode_.h_blanking); |
| DISP_INFO("v_addressable = 0x%x (%u)\n", hdmi_.cur_display_mode_.v_addressable, |
| hdmi_.cur_display_mode_.v_addressable); |
| DISP_INFO("v_front_porch = 0x%x (%u)\n", hdmi_.cur_display_mode_.v_front_porch, |
| hdmi_.cur_display_mode_.v_front_porch); |
| DISP_INFO("v_sync_pulse = 0x%x (%u)\n", hdmi_.cur_display_mode_.v_sync_pulse, |
| hdmi_.cur_display_mode_.v_sync_pulse); |
| DISP_INFO("v_blanking = 0x%x (%u)\n", hdmi_.cur_display_mode_.v_blanking, |
| hdmi_.cur_display_mode_.v_blanking); |
| DISP_INFO("flags = 0x%x (%u)\n", hdmi_.cur_display_mode_.flags, |
| hdmi_.cur_display_mode_.flags); |
| break; |
| default: |
| DISP_ERROR("Unrecognized Vout type %u\n", type_); |
| } |
| } |
| |
| } // namespace amlogic_display |