blob: 0685030d9aadf9e8b3d4115f09224df4fb513938 [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 "amlogic-display.h"
#include <fuchsia/hardware/amlogiccanvas/cpp/banjo.h>
#include <fuchsia/hardware/display/capture/cpp/banjo.h>
#include <fuchsia/hardware/display/clamprgb/cpp/banjo.h>
#include <fuchsia/hardware/display/controller/cpp/banjo.h>
#include <fuchsia/hardware/dsiimpl/cpp/banjo.h>
#include <fuchsia/hardware/platform/device/cpp/banjo.h>
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <lib/ddk/device.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/fit/defer.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/image-format-llcpp/image-format-llcpp.h>
#include <lib/image-format/image_format.h>
#include <lib/zircon-internal/align.h>
#include <lib/zx/channel.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/pixelformat.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <cstddef>
#include <iterator>
#include <ddk/metadata/display.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/vector.h>
#include "common.h"
#include "src/graphics/display/drivers/amlogic-display/amlogic-display-bind.h"
#include "vpp-regs.h"
namespace sysmem = fuchsia_sysmem;
namespace amlogic_display {
namespace {
constexpr uint32_t kCanvasLittleEndian64Bit = 7;
constexpr uint32_t kBufferAlignment = 64;
} // namespace
zx_status_t AmlogicDisplay::DisplayClampRgbImplSetMinimumRgb(uint8_t minimum_rgb) {
if (osd_) {
osd_->SetMinimumRgb(minimum_rgb);
return ZX_OK;
}
return ZX_ERR_INTERNAL;
}
zx_status_t AmlogicDisplay::RestartDisplay() {
vpu_->PowerOff();
vpu_->PowerOn();
vpu_->VppInit();
// Need to call this function since VPU/VPP registers were reset
vpu_->SetFirstTimeDriverLoad();
return vout_->RestartDisplay(parent_);
}
zx_status_t AmlogicDisplay::DisplayInit() {
zx_status_t status;
fbl::AllocChecker ac;
// Setup VPU and VPP units first
vpu_ = fbl::make_unique_checked<amlogic_display::Vpu>(&ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = vpu_->Init(pdev_);
if (status != ZX_OK) {
DISP_ERROR("Could not initialize VPU object\n");
return status;
}
// Determine whether it's first time boot or not
const bool skip_disp_init = vpu_->SetFirstTimeDriverLoad();
if (skip_disp_init) {
DISP_INFO("First time driver load. Skip display initialization\n");
} else {
DISP_INFO("Display driver reloaded. Initialize display system\n");
}
if (skip_disp_init) {
// Make sure AFBC engine is on. Since bootloader does not use AFBC, it might not have powered
// on AFBC engine.
vpu_->AfbcPower(true);
} else {
RestartDisplay();
}
osd_ = fbl::make_unique_checked<amlogic_display::Osd>(
&ac, vout_->supports_afbc(), vout_->fb_width(), vout_->fb_height(), vout_->display_width(),
vout_->display_height(), &inspector_.GetRoot());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Initialize osd object
status = osd_->Init(pdev_);
if (status != ZX_OK) {
DISP_ERROR("Could not initialize OSD object\n");
return status;
}
osd_->HwInit();
current_image_valid_ = false;
return ZX_OK;
}
// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops
void AmlogicDisplay::DisplayControllerImplSetDisplayControllerInterface(
const display_controller_interface_protocol_t* intf) {
fbl::AutoLock lock(&display_lock_);
dc_intf_ = ddk::DisplayControllerInterfaceProtocolClient(intf);
added_display_args_t args;
vout_->PopulateAddedDisplayArgs(&args, display_id_);
dc_intf_.OnDisplaysChanged(&args, 1, nullptr, 0, nullptr, 0, nullptr);
}
// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops
zx_status_t AmlogicDisplay::DisplayControllerImplImportVmoImage(image_t* image, zx::vmo vmo,
size_t offset) {
return ZX_ERR_NOT_SUPPORTED;
}
// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops
zx_status_t AmlogicDisplay::DisplayControllerImplImportImage(image_t* image,
zx_unowned_handle_t handle,
uint32_t index) {
zx_status_t status = ZX_OK;
auto import_info = std::make_unique<ImageInfo>();
if (import_info == nullptr) {
return ZX_ERR_NO_MEMORY;
}
if (image->type != IMAGE_TYPE_SIMPLE || !vout_->IsFormatSupported(image->pixel_format)) {
status = ZX_ERR_INVALID_ARGS;
return status;
}
auto result = fidl::WireCall<sysmem::BufferCollection>(zx::unowned_channel(handle))
.WaitForBuffersAllocated();
if (!result.ok()) {
return result.status();
}
if (result->status != ZX_OK) {
return result->status;
}
sysmem::wire::BufferCollectionInfo_2& collection_info = result->buffer_collection_info;
if (!collection_info.settings.has_image_format_constraints ||
index >= collection_info.buffer_count) {
return ZX_ERR_OUT_OF_RANGE;
}
ZX_DEBUG_ASSERT(
collection_info.settings.image_format_constraints.pixel_format.has_format_modifier);
const auto format_modifier =
collection_info.settings.image_format_constraints.pixel_format.format_modifier.value;
switch (format_modifier) {
case sysmem::wire::FORMAT_MODIFIER_ARM_AFBC_16X16:
case sysmem::wire::FORMAT_MODIFIER_ARM_AFBC_16X16_TE: {
// AFBC does not use canvas.
uint64_t offset = collection_info.buffers[index].vmo_usable_start;
size_t size =
ZX_ROUNDUP(ImageFormatImageSize(image_format::ConstraintsToFormat(
collection_info.settings.image_format_constraints,
image->width, image->height)
.value()),
PAGE_SIZE);
zx_paddr_t paddr;
zx_status_t status =
bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, collection_info.buffers[index].vmo,
offset & ~(PAGE_SIZE - 1), size, &paddr, 1, &import_info->pmt);
if (status != ZX_OK) {
DISP_ERROR("Could not pin BTI (%d)\n", status);
return status;
}
import_info->paddr = paddr;
import_info->image_height = image->height;
import_info->image_width = image->width;
import_info->is_afbc = true;
} break;
case sysmem::wire::FORMAT_MODIFIER_LINEAR:
case sysmem::wire::FORMAT_MODIFIER_ARM_LINEAR_TE: {
uint32_t minimum_row_bytes;
if (!image_format::GetMinimumRowBytes(collection_info.settings.image_format_constraints,
image->width, &minimum_row_bytes)) {
DISP_ERROR("Invalid image width %d for collection\n", image->width);
return ZX_ERR_INVALID_ARGS;
}
canvas_info_t canvas_info;
canvas_info.height = image->height;
canvas_info.stride_bytes = minimum_row_bytes;
canvas_info.wrap = 0;
canvas_info.blkmode = 0;
canvas_info.endianness = 0;
canvas_info.flags = CANVAS_FLAGS_READ;
uint8_t local_canvas_idx;
status = canvas_.Config(std::move(collection_info.buffers[index].vmo),
collection_info.buffers[index].vmo_usable_start, &canvas_info,
&local_canvas_idx);
if (status != ZX_OK) {
DISP_ERROR("Could not configure canvas: %d\n", status);
return ZX_ERR_NO_RESOURCES;
}
import_info->canvas = canvas_;
import_info->canvas_idx = local_canvas_idx;
import_info->image_height = image->height;
import_info->image_width = image->width;
import_info->is_afbc = false;
} break;
default:
ZX_DEBUG_ASSERT_MSG(false, "Invalid pixel format modifier: %lu\n", format_modifier);
return ZX_ERR_INVALID_ARGS;
}
image->handle = reinterpret_cast<uint64_t>(import_info.get());
fbl::AutoLock lock(&image_lock_);
imported_images_.push_back(std::move(import_info));
return status;
}
// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops
void AmlogicDisplay::DisplayControllerImplReleaseImage(image_t* image) {
fbl::AutoLock lock(&image_lock_);
auto info = reinterpret_cast<ImageInfo*>(image->handle);
imported_images_.erase(*info);
}
// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops
uint32_t AmlogicDisplay::DisplayControllerImplCheckConfiguration(
const display_config_t** display_configs, size_t display_count, uint32_t** layer_cfg_results,
size_t* layer_cfg_result_count) {
if (display_count != 1) {
ZX_DEBUG_ASSERT(display_count == 0);
return CONFIG_DISPLAY_OK;
}
fbl::AutoLock lock(&display_lock_);
// no-op, just wait for the client to try a new config
if (!display_attached_ || display_configs[0]->display_id != display_id_) {
return CONFIG_DISPLAY_OK;
}
if (vout_->CheckMode(&display_configs[0]->mode) || (display_configs[0]->mode.v_addressable % 8)) {
return CONFIG_DISPLAY_UNSUPPORTED_MODES;
}
bool success = true;
if (display_configs[0]->layer_count > 1) {
// We only support 1 layer
success = false;
}
if (success && display_configs[0]->cc_flags) {
// Make sure cc values are correct
if (display_configs[0]->cc_flags & COLOR_CONVERSION_PREOFFSET) {
for (int i = 0; i < 3; i++) {
success = success && display_configs[0]->cc_preoffsets[i] > -1;
success = success && display_configs[0]->cc_preoffsets[i] < 1;
}
}
if (success && display_configs[0]->cc_flags & COLOR_CONVERSION_POSTOFFSET) {
for (int i = 0; i < 3; i++) {
success = success && display_configs[0]->cc_postoffsets[i] > -1;
success = success && display_configs[0]->cc_postoffsets[i] < 1;
}
}
}
if (success && display_configs[0]->gamma_table_present) {
// Make sure all channels have the same size and equal to the expected table size of hardware
if (display_configs[0]->gamma_red_count != Osd::kGammaTableSize ||
display_configs[0]->gamma_red_count != display_configs[0]->gamma_green_count ||
display_configs[0]->gamma_red_count != display_configs[0]->gamma_blue_count) {
layer_cfg_results[0][0] |= CLIENT_GAMMA;
}
}
if (success) {
const uint32_t width = display_configs[0]->mode.h_addressable;
const uint32_t height = display_configs[0]->mode.v_addressable;
// Make sure ther layer configuration is supported
const primary_layer_t& layer = display_configs[0]->layer_list[0]->cfg.primary;
frame_t frame = {
.x_pos = 0,
.y_pos = 0,
.width = width,
.height = height,
};
if (layer.alpha_mode == ALPHA_PREMULTIPLIED) {
// we don't support pre-multiplied alpha mode
layer_cfg_results[0][0] |= CLIENT_ALPHA;
}
success = display_configs[0]->layer_list[0]->type == LAYER_TYPE_PRIMARY &&
layer.transform_mode == FRAME_TRANSFORM_IDENTITY && layer.image.width == width &&
layer.image.height == height &&
memcmp(&layer.dest_frame, &frame, sizeof(frame_t)) == 0 &&
memcmp(&layer.src_frame, &frame, sizeof(frame_t)) == 0;
}
if (!success) {
layer_cfg_results[0][0] = CLIENT_MERGE_BASE;
for (unsigned i = 1; i < display_configs[0]->layer_count; i++) {
layer_cfg_results[0][i] = CLIENT_MERGE_SRC;
}
}
return CONFIG_DISPLAY_OK;
}
// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops
void AmlogicDisplay::DisplayControllerImplApplyConfiguration(
const display_config_t** display_configs, size_t display_count) {
ZX_DEBUG_ASSERT(display_configs);
fbl::AutoLock lock(&display_lock_);
zx_status_t status;
if (display_count == 1 && display_configs[0]->layer_count) {
if (!full_init_done_) {
if ((status = DisplayInit()) != ZX_OK) {
DISP_ERROR("Display Hardware Initialization failed! %d\n", status);
ZX_ASSERT(0);
}
full_init_done_ = true;
}
status = vout_->ApplyConfiguration(&display_configs[0]->mode);
if (status != ZX_OK) {
DISP_ERROR("Could not apply config to Vout! %d\n", status);
return;
}
// The only way a checked configuration could now be invalid is if display was
// unplugged. If that's the case, then the upper layers will give a new configuration
// once they finish handling the unplug event. So just return.
if (!display_attached_ || display_configs[0]->display_id != display_id_) {
return;
}
// Since Amlogic does not support plug'n play (fixed display), there is no way
// a checked configuration could be invalid at this point.
auto info =
reinterpret_cast<ImageInfo*>(display_configs[0]->layer_list[0]->cfg.primary.image.handle);
current_image_valid_ = true;
current_image_ = display_configs[0]->layer_list[0]->cfg.primary.image.handle;
osd_->FlipOnVsync(info->canvas_idx, display_configs[0]);
} else {
current_image_valid_ = false;
if (full_init_done_) {
{
fbl::AutoLock lock2(&capture_lock_);
if (capture_active_id_ != INVALID_ID) {
// there's an active capture. stop it before disabling osd
vpu_->CaptureDone();
capture_active_id_ = INVALID_ID;
}
}
osd_->Disable();
}
}
// If bootloader does not enable any of the display hardware, no vsync will be generated.
// This fakes a vsync to let clients know we are ready until we actually initialize hardware
if (!full_init_done_) {
if (dc_intf_.is_valid()) {
if (display_count == 0 || display_configs[0]->layer_count == 0) {
dc_intf_.OnDisplayVsync(display_id_, zx_clock_get_monotonic(), nullptr, 0);
}
}
}
}
void AmlogicDisplay::DdkSuspend(ddk::SuspendTxn txn) {
fbl::AutoLock lock(&display_lock_);
if (txn.suspend_reason() != DEVICE_SUSPEND_REASON_MEXEC) {
txn.Reply(ZX_ERR_NOT_SUPPORTED, txn.requested_state());
return;
}
if (osd_) {
osd_->Disable();
}
fbl::AutoLock l(&image_lock_);
for (auto& i : imported_images_) {
if (i.pmt) {
i.pmt.unpin();
}
if (i.canvas.is_valid() && i.canvas_idx > 0) {
i.canvas.Free(i.canvas_idx);
}
}
txn.Reply(ZX_OK, txn.requested_state());
}
void AmlogicDisplay::DdkResume(ddk::ResumeTxn txn) {
fbl::AutoLock lock(&display_lock_);
if (osd_) {
osd_->Enable();
}
txn.Reply(ZX_OK, DEV_POWER_STATE_D0, txn.requested_state());
}
void AmlogicDisplay::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
void AmlogicDisplay::DdkRelease() {
if (osd_) {
osd_->Release();
}
vsync_irq_.destroy();
thrd_join(vsync_thread_, nullptr);
vd1_wr_irq_.destroy();
thrd_join(capture_thread_, nullptr);
hpd_irq_.destroy();
thrd_join(hpd_thread_, nullptr);
delete this;
}
zx_status_t AmlogicDisplay::DdkGetProtocol(uint32_t proto_id, void* out_protocol) {
auto* proto = static_cast<ddk::AnyProtocol*>(out_protocol);
proto->ctx = this;
switch (proto_id) {
case ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL:
proto->ops = &display_controller_impl_protocol_ops_;
return ZX_OK;
case ZX_PROTOCOL_DISPLAY_CAPTURE_IMPL:
if (!vout_->supports_capture()) {
return ZX_ERR_NOT_SUPPORTED;
}
proto->ops = &display_capture_impl_protocol_ops_;
return ZX_OK;
case ZX_PROTOCOL_DISPLAY_CLAMP_RGB_IMPL:
proto->ops = &display_clamp_rgb_impl_protocol_ops_;
return ZX_OK;
case ZX_PROTOCOL_I2C_IMPL:
if (!vout_->supports_hpd()) {
return ZX_ERR_NOT_SUPPORTED;
}
proto->ops = &i2c_impl_protocol_ops_;
return ZX_OK;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t AmlogicDisplay::SetupDisplayInterface() {
fbl::AutoLock lock(&display_lock_);
added_display_info_t info{.is_standard_srgb_out = 0}; // Random default
if (dc_intf_.is_valid()) {
added_display_args_t args;
vout_->PopulateAddedDisplayArgs(&args, display_id_);
dc_intf_.OnDisplaysChanged(&args, 1, nullptr, 0, &info, 1, nullptr);
}
return vout_->OnDisplaysChanged(info);
}
zx_status_t AmlogicDisplay::DisplayControllerImplGetSysmemConnection(zx::channel connection) {
zx_status_t status = sysmem_.Connect(std::move(connection));
if (status != ZX_OK) {
DISP_ERROR("Could not connect to sysmem\n");
return status;
}
return ZX_OK;
}
zx_status_t AmlogicDisplay::DisplayControllerImplSetBufferCollectionConstraints(
const image_t* config, zx_unowned_handle_t collection) {
sysmem::wire::BufferCollectionConstraints constraints = {};
const char* buffer_name;
if (config->type == IMAGE_TYPE_CAPTURE) {
constraints.usage.cpu = sysmem::wire::cpuUsageReadOften | sysmem::wire::cpuUsageWriteOften;
} else {
constraints.usage.display = sysmem::wire::displayUsageLayer;
}
constraints.has_buffer_memory_constraints = true;
sysmem::wire::BufferMemoryConstraints& buffer_constraints = constraints.buffer_memory_constraints;
buffer_constraints.physically_contiguous_required = true;
buffer_constraints.secure_required = false;
buffer_constraints.ram_domain_supported = true;
buffer_constraints.cpu_domain_supported = false;
buffer_constraints.inaccessible_domain_supported = true;
buffer_constraints.heap_permitted_count = 2;
buffer_constraints.heap_permitted[0] = sysmem::wire::HeapType::SYSTEM_RAM;
buffer_constraints.heap_permitted[1] = sysmem::wire::HeapType::AMLOGIC_SECURE;
constraints.image_format_constraints_count = config->type == IMAGE_TYPE_CAPTURE ? 1 : 4;
for (uint32_t i = 0; i < constraints.image_format_constraints_count; i++) {
sysmem::wire::ImageFormatConstraints& image_constraints =
constraints.image_format_constraints[i];
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = sysmem::wire::ColorSpaceType::SRGB;
if (config->type == IMAGE_TYPE_CAPTURE) {
ZX_DEBUG_ASSERT(i == 0);
image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::BGR24;
image_constraints.pixel_format.format_modifier.value = sysmem::wire::FORMAT_MODIFIER_LINEAR;
image_constraints.min_coded_width = vout_->display_width();
image_constraints.max_coded_width = vout_->display_width();
image_constraints.min_coded_height = vout_->display_height();
image_constraints.max_coded_height = vout_->display_height();
image_constraints.min_bytes_per_row =
ZX_ALIGN(vout_->display_width() * ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_888),
kBufferAlignment);
image_constraints.max_coded_width_times_coded_height =
vout_->display_width() * vout_->display_height();
buffer_name = "Display capture";
} else {
// The beginning of ARM linear TE memory is a regular linear image, so we can support it by
// ignoring everything after that. We never write to the image, so we don't need to worry
// about keeping the TE buffer in sync.
ZX_DEBUG_ASSERT(i <= 3);
switch (i) {
case 0:
image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::BGRA32;
image_constraints.pixel_format.format_modifier.value =
sysmem::wire::FORMAT_MODIFIER_LINEAR;
break;
case 1:
image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::BGRA32;
image_constraints.pixel_format.format_modifier.value =
sysmem::wire::FORMAT_MODIFIER_ARM_LINEAR_TE;
break;
case 2:
image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::R8G8B8A8;
image_constraints.pixel_format.format_modifier.value =
sysmem::wire::FORMAT_MODIFIER_ARM_AFBC_16X16;
break;
case 3:
image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::R8G8B8A8;
image_constraints.pixel_format.format_modifier.value =
sysmem::wire::FORMAT_MODIFIER_ARM_AFBC_16X16_TE;
break;
}
buffer_name = "Display";
}
image_constraints.bytes_per_row_divisor = kBufferAlignment;
image_constraints.start_offset_divisor = kBufferAlignment;
}
// Set priority to 10 to override the Vulkan driver name priority of 5, but be less than most
// application priorities.
constexpr uint32_t kNamePriority = 10;
auto name_res = fidl::WireCall<sysmem::BufferCollection>(zx::unowned_channel(collection))
.SetName(kNamePriority, fidl::StringView::FromExternal(buffer_name));
if (!name_res.ok()) {
DISP_ERROR("Failed to set name: %d", name_res.status());
return name_res.status();
}
auto res = fidl::WireCall<sysmem::BufferCollection>(zx::unowned_channel(collection))
.SetConstraints(true, constraints);
if (!res.ok()) {
DISP_ERROR("Failed to set constraints: %d", res.status());
return res.status();
}
return ZX_OK;
}
void AmlogicDisplay::DisplayCaptureImplSetDisplayCaptureInterface(
const display_capture_interface_protocol_t* intf) {
fbl::AutoLock lock(&capture_lock_);
capture_intf_ = ddk::DisplayCaptureInterfaceProtocolClient(intf);
capture_active_id_ = INVALID_ID;
}
zx_status_t AmlogicDisplay::DisplayCaptureImplImportImageForCapture(zx_unowned_handle_t collection,
uint32_t index,
uint64_t* out_capture_handle) {
auto import_capture = std::make_unique<ImageInfo>();
if (import_capture == nullptr) {
return ZX_ERR_NO_MEMORY;
}
fbl::AutoLock lock(&capture_lock_);
auto result = fidl::WireCall<sysmem::BufferCollection>(zx::unowned_channel(collection))
.WaitForBuffersAllocated();
if (!result.ok()) {
return result.status();
}
if (result->status != ZX_OK) {
return result->status;
}
sysmem::wire::BufferCollectionInfo_2& collection_info = result->buffer_collection_info;
if (!collection_info.settings.has_image_format_constraints ||
index >= collection_info.buffer_count) {
return ZX_ERR_OUT_OF_RANGE;
}
// Ensure the proper format
ZX_DEBUG_ASSERT(collection_info.settings.image_format_constraints.pixel_format.type ==
sysmem::wire::PixelFormatType::BGR24);
// Allocate a canvas for the capture image
canvas_info_t canvas_info = {};
canvas_info.height = collection_info.settings.image_format_constraints.min_coded_height;
canvas_info.stride_bytes = collection_info.settings.image_format_constraints.min_bytes_per_row;
canvas_info.wrap = 0;
canvas_info.blkmode = 0;
canvas_info.endianness = kCanvasLittleEndian64Bit;
canvas_info.flags = CANVAS_FLAGS_READ | CANVAS_FLAGS_WRITE;
uint8_t canvas_idx;
zx_status_t status =
canvas_.Config(std::move(collection_info.buffers[index].vmo),
collection_info.buffers[index].vmo_usable_start, &canvas_info, &canvas_idx);
if (status != ZX_OK) {
DISP_ERROR("Could not configure canvas %d\n", status);
return status;
}
// At this point, we have setup a canvas with the BufferCollection-based VMO. Store the
// capture information
import_capture->canvas_idx = canvas_idx;
import_capture->image_height = collection_info.settings.image_format_constraints.min_coded_height;
import_capture->image_width = collection_info.settings.image_format_constraints.min_coded_width;
*out_capture_handle = reinterpret_cast<uint64_t>(import_capture.get());
imported_captures_.push_back(std::move(import_capture));
return ZX_OK;
}
zx_status_t AmlogicDisplay::DisplayCaptureImplStartCapture(uint64_t capture_handle) {
fbl::AutoLock lock(&capture_lock_);
if (capture_active_id_ != INVALID_ID) {
DISP_ERROR("Cannot start capture while another capture is in progress\n");
return ZX_ERR_SHOULD_WAIT;
}
// Confirm a valid image is being displayed
// Check whether a valid image is being displayed at the time of start capture.
// There is a chance that a client might release the image being displayed during
// capture, but that behavior is not within specified spec
{
fbl::AutoLock lock2(&display_lock_);
if (!current_image_valid_) {
DISP_ERROR("No Valid Image is being displayed\n");
return ZX_ERR_UNAVAILABLE;
}
}
// Confirm that the handle was previously imported (hence valid)
auto info = reinterpret_cast<ImageInfo*>(capture_handle);
if (imported_captures_.find_if([info](auto& i) { return i.canvas_idx == info->canvas_idx; }) ==
imported_captures_.end()) {
// invalid handle
DISP_ERROR("Invalid capture_handle\n");
return ZX_ERR_NOT_FOUND;
}
ZX_DEBUG_ASSERT(info->canvas_idx > 0);
ZX_DEBUG_ASSERT(info->image_height > 0);
ZX_DEBUG_ASSERT(info->image_width > 0);
auto status = vpu_->CaptureInit(info->canvas_idx, info->image_height, info->image_width);
if (status != ZX_OK) {
DISP_ERROR("Failed to init capture %d\n", status);
return status;
}
status = vpu_->CaptureStart();
if (status != ZX_OK) {
DISP_ERROR("Failed to start capture %d\n", status);
return status;
}
capture_active_id_ = capture_handle;
return ZX_OK;
}
zx_status_t AmlogicDisplay::DisplayCaptureImplReleaseCapture(uint64_t capture_handle) {
fbl::AutoLock lock(&capture_lock_);
if (capture_handle == capture_active_id_) {
return ZX_ERR_SHOULD_WAIT;
}
// Find and erase previously imported capture
auto idx = reinterpret_cast<ImageInfo*>(capture_handle)->canvas_idx;
if (imported_captures_.erase_if([idx](auto& i) { return i.canvas_idx == idx; }) == nullptr) {
DISP_ERROR("Tried to release non-existent capture image %d\n", idx);
return ZX_ERR_NOT_FOUND;
}
return ZX_OK;
}
bool AmlogicDisplay::DisplayCaptureImplIsCaptureCompleted() {
fbl::AutoLock lock(&capture_lock_);
return (capture_active_id_ == INVALID_ID);
}
int AmlogicDisplay::CaptureThread() {
zx_status_t status;
while (true) {
zx::time timestamp;
status = vd1_wr_irq_.wait(&timestamp);
if (status != ZX_OK) {
DISP_ERROR("Vd1 Wr interrupt wait failed %d\n", status);
break;
}
fbl::AutoLock lock(&capture_lock_);
vpu_->CaptureDone();
if (capture_intf_.is_valid()) {
capture_intf_.OnCaptureComplete();
}
capture_active_id_ = INVALID_ID;
}
return status;
}
int AmlogicDisplay::VSyncThread() {
zx_status_t status;
while (true) {
zx::time timestamp;
status = vsync_irq_.wait(&timestamp);
if (status != ZX_OK) {
DISP_ERROR("VSync Interrupt Wait failed\n");
break;
}
fbl::AutoLock lock(&display_lock_);
uint64_t live = 0;
if (osd_) {
live = osd_->GetLastImageApplied();
}
bool current_image_valid = live != 0;
if (dc_intf_.is_valid() && display_attached_) {
dc_intf_.OnDisplayVsync(display_id_, timestamp.get(), &live, current_image_valid);
}
}
return status;
}
int AmlogicDisplay::HpdThread() {
zx_status_t status;
while (1) {
status = hpd_irq_.wait(NULL);
if (status != ZX_OK) {
DISP_ERROR("Waiting in Interrupt failed %d\n", status);
break;
}
usleep(500000);
uint8_t hpd;
status = hpd_gpio_.Read(&hpd);
if (status != ZX_OK) {
DISP_ERROR("gpio_read failed HDMI HPD\n");
continue;
}
fbl::AutoLock lock(&display_lock_);
bool display_added = false;
added_display_args_t args;
added_display_info_t info;
uint64_t display_removed = INVALID_DISPLAY_ID;
if (hpd && !display_attached_) {
DISP_ERROR("Display is connected\n");
display_attached_ = true;
vout_->DisplayConnected();
vout_->PopulateAddedDisplayArgs(&args, display_id_);
display_added = true;
hpd_gpio_.SetPolarity(GPIO_POLARITY_LOW);
} else if (!hpd && display_attached_) {
DISP_ERROR("Display Disconnected!\n");
vout_->DisplayDisconnected();
display_removed = display_id_;
display_id_++;
display_attached_ = false;
hpd_gpio_.SetPolarity(GPIO_POLARITY_HIGH);
}
if (dc_intf_.is_valid() && (display_removed != INVALID_DISPLAY_ID || display_added)) {
dc_intf_.OnDisplaysChanged(&args, display_added ? 1 : 0, &display_removed,
display_removed != INVALID_DISPLAY_ID, &info,
display_added ? 1 : 0, NULL);
if (display_added) {
// See if we need to change output color to RGB
status = vout_->OnDisplaysChanged(info);
}
}
}
return status;
}
// TODO(payamm): make sure unbind/release are called if we return error
zx_status_t AmlogicDisplay::Bind() {
fbl::AllocChecker ac;
vout_ = fbl::make_unique_checked<amlogic_display::Vout>(&ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
display_panel_t display_info;
size_t actual;
zx_status_t status = device_get_metadata(parent_, DEVICE_METADATA_DISPLAY_CONFIG, &display_info,
sizeof(display_info), &actual);
if (status != ZX_OK) {
status = vout_->InitHdmi(parent_);
if (status != ZX_OK) {
DISP_ERROR("Could not initialize HDMI Vout device! %d\n", status);
return status;
}
} else if (actual != sizeof(display_panel_t)) {
DISP_ERROR("Could not get display panel metadata %d\n", status);
return status;
} else {
DISP_INFO("Provided Display Info: %d x %d with panel type %d\n", display_info.width,
display_info.height, display_info.panel_type);
display_attached_ = true;
fbl::AutoLock lock(&display_lock_);
status =
vout_->InitDsi(parent_, display_info.panel_type, display_info.width, display_info.height);
if (status != ZX_OK) {
DISP_ERROR("Could not initialize DSI Vout device! %d\n", status);
return status;
}
}
status = ddk::PDev::FromFragment(parent_, &pdev_);
if (status != ZX_OK) {
DISP_ERROR("Could not get PDEV protocol\n");
return status;
}
// Get board info
status = pdev_.GetBoardInfo(&board_info_);
if (status != ZX_OK) {
DISP_ERROR("Could not obtain board info\n");
return status;
}
status = ddk::SysmemProtocolClient::CreateFromDevice(parent_, "sysmem", &sysmem_);
if (status != ZX_OK) {
DISP_ERROR("Could not get Display SYSMEM protocol\n");
return status;
}
status = ddk::AmlogicCanvasProtocolClient::CreateFromDevice(parent_, "canvas", &canvas_);
if (status != ZX_OK) {
DISP_ERROR("Could not obtain CANVAS protocol\n");
return status;
}
status = pdev_.GetBti(0, &bti_);
if (status != ZX_OK) {
DISP_ERROR("Could not get BTI handle\n");
return status;
}
// Setup Display Interface
status = SetupDisplayInterface();
if (status != ZX_OK) {
DISP_ERROR("Amlogic display setup failed! %d\n", status);
return status;
}
// Map VSync Interrupt
status = pdev_.GetInterrupt(IRQ_VSYNC, 0, &vsync_irq_);
if (status != ZX_OK) {
DISP_ERROR("Could not map vsync interrupt\n");
return status;
}
auto start_thread = [](void* arg) { return static_cast<AmlogicDisplay*>(arg)->VSyncThread(); };
status = thrd_create_with_name(&vsync_thread_, start_thread, this, "vsync_thread");
if (status != ZX_OK) {
DISP_ERROR("Could not create vsync_thread\n");
return status;
}
if (vout_->supports_capture()) {
// Map VD1_WR Interrupt (used for capture)
status = pdev_.GetInterrupt(IRQ_VD1_WR, 0, &vd1_wr_irq_);
if (status != ZX_OK) {
DISP_ERROR("Could not map vd1 wr interrupt\n");
return status;
}
auto vd_thread = [](void* arg) { return static_cast<AmlogicDisplay*>(arg)->CaptureThread(); };
status = thrd_create_with_name(&capture_thread_, vd_thread, this, "capture_thread");
if (status != ZX_OK) {
DISP_ERROR("Could not create capture_thread\n");
return status;
}
}
if (vout_->supports_hpd()) {
status = ddk::GpioProtocolClient::CreateFromDevice(parent_, "gpio", &hpd_gpio_);
if (status != ZX_OK) {
DISP_ERROR("Could not obtain GPIO protocol\n");
return status;
}
status = hpd_gpio_.ConfigIn(GPIO_PULL_DOWN);
if (status != ZX_OK) {
DISP_ERROR("gpio_config_in failed for gpio\n");
return status;
}
status = hpd_gpio_.GetInterrupt(ZX_INTERRUPT_MODE_LEVEL_HIGH, &hpd_irq_);
if (status != ZX_OK) {
DISP_ERROR("gpio_get_interrupt failed for gpio\n");
return status;
}
auto hpd_thread = [](void* arg) { return static_cast<AmlogicDisplay*>(arg)->HpdThread(); };
status = thrd_create_with_name(&hpd_thread_, hpd_thread, this, "hpd_thread");
if (status != ZX_OK) {
DISP_ERROR("Could not create hpd_thread\n");
return status;
}
}
// Set profile for vsync thread.
// TODO(fxbug.dev/40858): Migrate to the role-based API when available, instead of hard
// coding parameters.
{
const zx_duration_t capacity = ZX_USEC(500);
const zx_duration_t deadline = ZX_MSEC(8);
const zx_duration_t period = deadline;
zx_handle_t profile = ZX_HANDLE_INVALID;
if ((status = device_get_deadline_profile(this->zxdev(), capacity, deadline, period,
"dev/display/amlogic-display/vsync_thread",
&profile)) != ZX_OK) {
DISP_ERROR("Failed to get deadline profile: %d\n", status);
} else {
const zx_handle_t thread_handle = thrd_get_zx_handle(vsync_thread_);
status = zx_object_set_profile(thread_handle, profile, 0);
if (status != ZX_OK) {
DISP_ERROR("Failed to set deadline profile: %d\n", status);
}
zx_handle_close(profile);
}
}
auto cleanup = fit::defer([&]() { DdkRelease(); });
status = DdkAdd(ddk::DeviceAddArgs("amlogic-display")
.set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE)
.set_inspect_vmo(inspector_.DuplicateVmo()));
if (status != ZX_OK) {
DISP_ERROR("Could not add device\n");
return status;
}
cleanup.cancel();
return ZX_OK;
}
// main bind function called from dev manager
zx_status_t amlogic_display_bind(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
auto dev = fbl::make_unique_checked<amlogic_display::AmlogicDisplay>(&ac, parent);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->Bind();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev
__UNUSED auto ptr = dev.release();
}
return status;
}
static constexpr zx_driver_ops_t amlogic_display_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = amlogic_display_bind;
return ops;
}();
} // namespace amlogic_display
// clang-format off
ZIRCON_DRIVER(amlogic_display, amlogic_display::amlogic_display_ops, "zircon", "0.1");