blob: 017b5f47023bb32244ecc404e5c19e6f769a9928 [file] [log] [blame]
// Copyright 2019 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/camera/drivers/hw_accel/ge2d/ge2d.h"
#include <fuchsia/hardware/amlogiccanvas/cpp/banjo.h>
#include <lib/image-format/image_format.h>
#include <lib/syslog/cpp/macros.h>
#include <stdint.h>
#include <zircon/assert.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <memory>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/trace/event.h>
#include <ddktl/device.h>
#include <fbl/auto_lock.h>
#include "src/camera/drivers/hw_accel/ge2d/ge2d_regs.h"
namespace ge2d {
namespace {
constexpr uint32_t kGe2d = 0;
constexpr auto kTag = "ge2d";
} // namespace
zx_status_t Ge2dDevice::Ge2dInitTaskResize(
const buffer_collection_info_2_t* input_buffer_collection,
const buffer_collection_info_2_t* output_buffer_collection, const resize_info_t* info,
const image_format_2_t* input_image_format,
const image_format_2_t* output_image_format_table_list, size_t output_image_format_table_count,
uint32_t output_image_format_index, const hw_accel_frame_callback_t* frame_callback,
const hw_accel_res_change_callback_t* res_callback,
const hw_accel_remove_task_callback_t* task_remove_callback, uint32_t* out_task_index) {
if (out_task_index == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
auto task = std::make_unique<Ge2dTask>();
zx_status_t status = task->InitResize(
input_buffer_collection, output_buffer_collection, info, input_image_format,
output_image_format_table_list, output_image_format_table_count, output_image_format_index,
frame_callback, res_callback, task_remove_callback, bti_, canvas_);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "Task Creation Failed";
return status;
}
fbl::AutoLock al(&interface_lock_);
// Put an entry in the hashmap.
task_map_[next_task_index_] = std::move(task);
*out_task_index = next_task_index_;
next_task_index_++;
return status;
}
zx_status_t Ge2dDevice::Ge2dInitTaskWaterMark(
const buffer_collection_info_2_t* input_buffer_collection,
const buffer_collection_info_2_t* output_buffer_collection, const water_mark_info_t* info_list,
size_t info_count, const image_format_2_t* image_format_table_list,
size_t image_format_table_count, uint32_t image_format_index,
const hw_accel_frame_callback_t* frame_callback,
const hw_accel_res_change_callback_t* res_callback,
const hw_accel_remove_task_callback_t* task_remove_callback, uint32_t* out_task_index) {
if (out_task_index == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (info_count != image_format_table_count)
return ZX_ERR_INVALID_ARGS;
auto task = std::make_unique<Ge2dTask>();
zx_status_t status =
task->InitWatermark(input_buffer_collection, output_buffer_collection, info_list,
image_format_table_list, image_format_table_count, image_format_index,
frame_callback, res_callback, task_remove_callback, bti_, canvas_);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "Task Creation Failed";
return status;
}
fbl::AutoLock al(&interface_lock_);
// Put an entry in the hashmap.
task_map_[next_task_index_] = std::move(task);
*out_task_index = next_task_index_;
next_task_index_++;
return status;
}
zx_status_t Ge2dDevice::Ge2dInitTaskInPlaceWaterMark(
const buffer_collection_info_2_t* buffer_collection, const water_mark_info_t* info_list,
size_t info_count, const image_format_2_t* image_format_table_list,
size_t image_format_table_count, uint32_t image_format_index,
const hw_accel_frame_callback_t* frame_callback,
const hw_accel_res_change_callback_t* res_callback,
const hw_accel_remove_task_callback_t* task_remove_callback, uint32_t* out_task_index) {
if (out_task_index == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (info_count != image_format_table_count)
return ZX_ERR_INVALID_ARGS;
auto task = std::make_unique<Ge2dTask>();
zx_status_t status = task->InitInPlaceWatermark(
buffer_collection, info_list, image_format_table_list, image_format_table_count,
image_format_index, frame_callback, res_callback, task_remove_callback, bti_, canvas_);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "Task Creation Failed";
return status;
}
fbl::AutoLock al(&interface_lock_);
// Put an entry in the hashmap.
task_map_[next_task_index_] = std::move(task);
*out_task_index = next_task_index_;
next_task_index_++;
return status;
}
void Ge2dDevice::Ge2dRemoveTask(uint32_t task_index) {
fbl::AutoLock al(&interface_lock_);
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
if (task_entry == task_map_.end()) {
// Release lock so death test doesn't hang.
al.release();
ZX_ASSERT(false);
}
TaskInfo info;
info.op = GE2D_OP_REMOVETASK;
info.task = task_entry->second.get();
info.task_index = task_index;
// Put the task on the queue.
fbl::AutoLock lock(&lock_);
processing_queue_.push_front(info);
frame_processing_signal_.Signal();
}
void Ge2dDevice::Ge2dReleaseFrame(uint32_t task_index, uint32_t buffer_index) {
fbl::AutoLock al(&interface_lock_);
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
ZX_ASSERT(task_entry != task_map_.end());
auto task = task_entry->second.get();
ZX_ASSERT(ZX_OK == task->ReleaseOutputBuffer(buffer_index));
}
zx_status_t Ge2dDevice::Ge2dSetOutputResolution(uint32_t task_index,
uint32_t new_output_image_format_index) {
fbl::AutoLock al(&interface_lock_);
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
if (task_entry == task_map_.end()) {
return ZX_ERR_INVALID_ARGS;
}
if (task_entry->second->Ge2dTaskType() != Ge2dTask::GE2D_RESIZE) {
return ZX_ERR_INVALID_ARGS;
}
// Validate new image format index|.
if (!task_entry->second->IsOutputFormatIndexValid(new_output_image_format_index)) {
return ZX_ERR_INVALID_ARGS;
}
TaskInfo info;
info.op = GE2D_OP_SETOUTPUTRES;
info.task = task_entry->second.get();
info.index = new_output_image_format_index;
// Put the task on queue.
fbl::AutoLock lock(&lock_);
processing_queue_.push_front(info);
frame_processing_signal_.Signal();
return ZX_OK;
}
zx_status_t Ge2dDevice::Ge2dSetInputAndOutputResolution(uint32_t task_index,
uint32_t new_image_format_index) {
fbl::AutoLock al(&interface_lock_);
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
if (task_entry == task_map_.end()) {
return ZX_ERR_INVALID_ARGS;
}
if (task_entry->second->Ge2dTaskType() != Ge2dTask::GE2D_WATERMARK &&
task_entry->second->Ge2dTaskType() != Ge2dTask::GE2D_IN_PLACE_WATERMARK) {
return ZX_ERR_INVALID_ARGS;
}
// Validate new image format index|.
if (!task_entry->second->IsInputFormatIndexValid(new_image_format_index)) {
return ZX_ERR_INVALID_ARGS;
}
if (task_entry->second->has_output_images() &&
!task_entry->second->IsOutputFormatIndexValid(new_image_format_index)) {
return ZX_ERR_INVALID_ARGS;
}
TaskInfo info;
info.op = GE2D_OP_SETINPUTOUTPUTRES;
info.task = task_entry->second.get();
info.index = new_image_format_index;
// Put the task on queue.
fbl::AutoLock lock(&lock_);
processing_queue_.push_front(info);
frame_processing_signal_.Signal();
return ZX_OK;
}
zx_status_t Ge2dDevice::Ge2dProcessFrame(uint32_t task_index, uint32_t input_buffer_index,
uint64_t capture_timestamp) {
TRACE_DURATION("camera", "Ge2dDevice::Ge2dProcessFrame");
fbl::AutoLock al(&interface_lock_);
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
if (task_entry == task_map_.end()) {
return ZX_ERR_INVALID_ARGS;
}
// Validate |input_buffer_index|.
if (!task_entry->second->IsInputBufferIndexValid(input_buffer_index)) {
return ZX_ERR_INVALID_ARGS;
}
TaskInfo info;
info.op = GE2D_OP_FRAME;
info.task = task_entry->second.get();
info.index = input_buffer_index;
info.capture_timestamp = capture_timestamp;
// Put the task on queue.
TRACE_FLOW_BEGIN("camera", "ge2d_process_frame", info.index);
fbl::AutoLock lock(&lock_);
processing_queue_.push_front(info);
frame_processing_signal_.Signal();
return ZX_OK;
}
void Ge2dDevice::Ge2dSetCropRect(uint32_t task_index, const rect_t* crop) {
fbl::AutoLock al(&interface_lock_);
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
if (task_entry == task_map_.end()) {
return;
}
if (task_entry->second->Ge2dTaskType() != Ge2dTask::GE2D_RESIZE)
return;
TaskInfo info;
info.op = GE2D_OP_SETCROPRECT;
info.task = task_entry->second.get();
info.index = 0;
info.crop_rect = *crop;
// Put the task on queue.
fbl::AutoLock lock(&lock_);
processing_queue_.push_front(info);
frame_processing_signal_.Signal();
}
void Ge2dDevice::InitializeScalingCoefficients() {
// 33x4 FIR coefficients to use. First takes 100% of pixel[1], while the last takes 50% of
// pixel[1] and pixel[2].
constexpr uint32_t kBilinearCoefficients[] = {
0x00800000, 0x007e0200, 0x007c0400, 0x007a0600, 0x00780800, 0x00760a00, 0x00740c00,
0x00720e00, 0x00701000, 0x006e1200, 0x006c1400, 0x006a1600, 0x00681800, 0x00661a00,
0x00641c00, 0x00621e00, 0x00602000, 0x005e2200, 0x005c2400, 0x005a2600, 0x00582800,
0x00562a00, 0x00542c00, 0x00522e00, 0x00503000, 0x004e3200, 0x004c3400, 0x004a3600,
0x00483800, 0x00463a00, 0x00443c00, 0x00423e00, 0x00404000};
// Vertical scaler autoincrementing write
ScaleCoefIdx::Get().FromValue(0).WriteTo(&ge2d_mmio_);
for (uint32_t value : kBilinearCoefficients) {
ScaleCoef::Get().FromValue(value).WriteTo(&ge2d_mmio_);
}
// Horizontal scaler autoincrementing write
ScaleCoefIdx::Get().FromValue(0).set_horizontal(1).WriteTo(&ge2d_mmio_);
for (uint32_t value : kBilinearCoefficients) {
ScaleCoef::Get().FromValue(value).WriteTo(&ge2d_mmio_);
}
}
void Ge2dDevice::ProcessTask(TaskInfo& info) {
TRACE_DURATION("camera", "Ge2dDevice::ProcessTask");
TRACE_FLOW_END("camera", "ge2d_process_frame", info.index);
switch (info.op) {
case GE2D_OP_SETOUTPUTRES:
case GE2D_OP_SETINPUTOUTPUTRES:
return ProcessChangeResolution(info);
case GE2D_OP_SETCROPRECT:
return ProcessSetCropRect(info);
case GE2D_OP_FRAME:
return ProcessFrame(info);
case GE2D_OP_REMOVETASK:
return ProcessRemoveTask(info);
}
}
void Ge2dDevice::ProcessSetCropRect(TaskInfo& info) { info.task->SetCropRect(info.crop_rect); }
void Ge2dDevice::ProcessChangeResolution(TaskInfo& info) {
auto task = info.task;
if (task->has_output_images()) {
// This has to free and reallocate the output buffer canvas ids.
task->Ge2dChangeOutputRes(info.index);
}
if (info.op == GE2D_OP_SETINPUTOUTPUTRES) {
// This has to free and reallocate the input buffer canvas ids.
task->Ge2dChangeInputRes(info.index);
}
frame_available_info f_info;
f_info.frame_status = FRAME_STATUS_OK;
f_info.metadata.timestamp = static_cast<uint64_t>(zx_clock_get_monotonic());
f_info.metadata.image_format_index = task->output_format_index();
return task->ResolutionChangeCallback(&f_info);
}
// Floors.
static uint32_t ConvertToFixedPoint24(double input) {
return static_cast<uint32_t>((1 << 24) * input);
}
static void CalculateInitialPhase(uint32_t input_dim, uint32_t output_dim, uint32_t* phase_out,
uint32_t* repeat_out) {
// Linux uses a multiplied-by-10 fixed-point, but this seems simpler and more precise.
double rate_ratio = static_cast<double>(output_dim) / input_dim;
if (rate_ratio == 1.0) {
*phase_out = 0;
*repeat_out = 0;
} else {
// We subtract 0.5 here because the pixel value itself is at phase 0, not 0.5.
double pixel_initial_phase = 0.5 / rate_ratio - 0.5;
// We need to decide how to fill in the FIR filter initially.
if (pixel_initial_phase >= 0) {
// When scaling down the first output pixel center is after the first input pixel center, so
// we set repeat = 1 so the inputs looks like (image[0], image[0], image[1], image[2]) and we
// interpolate between image[0] and image[1].
*repeat_out = 1;
} else {
// When scaling up the first output pixel center is before the first input pixel center, so we
// set repeat = 2 and the input looks like (image[0], image[0], image[0], image[1]) so the
// first output must be image[0] (due to the bilinear filter coefficients we're using).
*repeat_out = 2;
// Increase initial phase by 1 to compensate.
pixel_initial_phase++;
}
*phase_out = ConvertToFixedPoint24(pixel_initial_phase);
}
}
void Ge2dDevice::InitializeScaler(uint32_t input_width, uint32_t input_height,
uint32_t output_width, uint32_t output_height) {
bool horizontal_scaling = (input_width != output_width);
bool vertical_scaling = (input_height != output_height);
InitializeScalingCoefficients();
bool use_preh_scaler = input_width > output_width * 2;
bool use_prev_scaler = input_height > output_height * 2;
// Prescaler seems to divide size by 2.
uint32_t scaler_input_width = use_preh_scaler ? ((input_width + 1) / 2) : input_width;
uint32_t scaler_input_height = use_prev_scaler ? ((input_height + 1) / 2) : input_height;
// The scaler starts at an initial phase value, and for every output pixel increments it by a
// step. Integer values (in 5.24 fixed-point) are the input pixel values themselves (starting at
// 0). The scaler is a polyphase scaler, so the phase picks the FIR coefficients to use (from the
// table above). For bilinear filtering, a phase of 0 takes all its input from pixel[1], and 1
// would take it all from pixel[2].
constexpr uint32_t kFixedPoint = 24;
uint32_t hsc_phase_step =
ConvertToFixedPoint24(static_cast<double>(scaler_input_width) / output_width);
uint32_t vsc_phase_step =
ConvertToFixedPoint24(static_cast<double>(scaler_input_height) / output_height);
// Horizontal scaler dividing provides more efficiency (somehow). It seems like it allows
// calculating phases at larger blocks.
// The dividing length is roughly 124 * (output_width / input_width).
uint32_t hsc_dividing_length = ConvertToFixedPoint24(124) / hsc_phase_step;
uint32_t hsc_rounded_step = hsc_dividing_length * hsc_phase_step;
uint32_t hsc_advance_num = hsc_rounded_step >> kFixedPoint;
uint32_t hsc_advance_phase = hsc_rounded_step & ((1 << kFixedPoint) - 1);
uint32_t horizontal_initial_phase, horizontal_repeat;
uint32_t vertical_initial_phase, vertical_repeat;
// The linux driver uses |input_width| and |input_height| here, but that seems incorrect.
CalculateInitialPhase(scaler_input_width, output_width, &horizontal_initial_phase,
&horizontal_repeat);
CalculateInitialPhase(scaler_input_height, output_height, &vertical_initial_phase,
&vertical_repeat);
ScMiscCtrl::Get()
.ReadFrom(&ge2d_mmio_)
.set_hsc_div_en(horizontal_scaling)
.set_hsc_dividing_length(hsc_dividing_length)
.set_pre_hsc_enable(use_preh_scaler)
.set_pre_vsc_enable(use_prev_scaler)
.set_vsc_enable(vertical_scaling)
.set_hsc_enable(horizontal_scaling)
.set_hsc_rpt_ctrl(1)
.set_vsc_rpt_ctrl(1)
.WriteTo(&ge2d_mmio_);
HscStartPhaseStep::Get().FromValue(0).set_phase_step(hsc_phase_step).WriteTo(&ge2d_mmio_);
HscAdvCtrl::Get()
.FromValue(0)
.set_advance_num(hsc_advance_num & 0xff)
.set_advance_phase(hsc_advance_phase)
.WriteTo(&ge2d_mmio_);
// We clamp the initial phases, because that's what the hardware
// supports. This can mess up scaling down to <= 1/3, though the prescaler can
// help reduce how often that's a problem. The linux driver wraps these
// values, which seems worse.
HscIniCtrl::Get()
.FromValue(0)
.set_horizontal_repeat_p0(horizontal_repeat)
.set_horizontal_advance_num_upper(hsc_advance_num >> 8)
.set_horizontal_initial_phase(std::min(horizontal_initial_phase, 0xffffffu))
.WriteTo(&ge2d_mmio_);
VscStartPhaseStep::Get().FromValue(0).set_phase_step(vsc_phase_step).WriteTo(&ge2d_mmio_);
VscIniCtrl::Get()
.FromValue(0)
.set_vertical_repeat_p0(vertical_repeat)
.set_vertical_initial_phase(std::min(vertical_initial_phase, 0xffffffu))
.WriteTo(&ge2d_mmio_);
// Leave horizontal and vertical phase slopes set to 0.
}
void Ge2dDevice::SetupInputOutputFormats(bool scaling_enabled, const image_format_2_t& input_format,
const image_format_2_t& output_format,
const image_format_2_t& src2_format) {
bool is_src_nv12 = input_format.pixel_format.type == fuchsia_sysmem_PixelFormatType_NV12;
bool is_dst_nv12 = output_format.pixel_format.type == fuchsia_sysmem_PixelFormatType_NV12;
// When using NV12 output DST1 gets Y and DST2 gets CbCr.
GenCtrl0::Get()
.FromValue(0)
.set_src1_separate_enable(is_src_nv12)
.set_x_yc_ratio(1)
.set_y_yc_ratio(1)
.WriteTo(&ge2d_mmio_);
GenCtrl2::Get()
.FromValue(0)
.set_dst_little_endian(0) // endianness conversion happens in canvas
.set_dst1_color_map(is_dst_nv12 ? 0 : GenCtrl2::kColorMap32RGBA8888)
.set_dst1_format(is_dst_nv12 ? GenCtrl2::kFormat8Bit : GenCtrl2::kFormat32Bit)
.set_src1_little_endian(0) // endianness conversion happens in canvas
.set_src1_color_map(is_src_nv12 ? GenCtrl2::kColorMap24NV12 : GenCtrl2::kColorMap32RGBA8888)
.set_src1_format(is_src_nv12 ? GenCtrl2::kFormat24Bit : GenCtrl2::kFormat32Bit)
.set_src1_color_expand_mode(1)
.set_src2_little_endian(0) // endianness conversion happens in canvas
.set_src2_color_map(GenCtrl2::kColorMap32RGBA8888)
.set_src2_format(GenCtrl2::kFormat32Bit)
.WriteTo(&ge2d_mmio_);
GenCtrl3::Get()
.FromValue(0)
.set_dst2_color_map(GenCtrl2::kColorMap16CbCr)
.set_dst2_format(GenCtrl2::kFormat16Bit)
.set_dst2_x_discard_mode(GenCtrl3::kDiscardModeOdd)
.set_dst2_y_discard_mode(GenCtrl3::kDiscardModeOdd)
.set_dst2_enable(is_dst_nv12)
.set_dst1_enable(1)
.WriteTo(&ge2d_mmio_);
if (is_src_nv12 && !is_dst_nv12) {
// YCbCr BT.601 studio swing to RGB. Outputs of matrix multiplication seem
// to be divided by 1024.
MatrixCoef00_01::Get().FromValue(0).set_coef00(0x4a8).WriteTo(&ge2d_mmio_);
MatrixCoef02_10::Get().FromValue(0).set_coef02(0x662).set_coef10(0x4a8).WriteTo(&ge2d_mmio_);
MatrixCoef11_12::Get().FromValue(0).set_coef11(0x1e6f).set_coef12(0x1cbf).WriteTo(&ge2d_mmio_);
MatrixCoef20_21::Get().FromValue(0).set_coef20(0x4a8).set_coef21(0x811).WriteTo(&ge2d_mmio_);
MatrixCoef22Ctrl::Get()
.FromValue(0)
.set_saturation_enable(true)
.set_matrix_enable(true)
.WriteTo(&ge2d_mmio_);
MatrixPreOffset::Get()
.FromValue(0)
.set_offset0(0x1f0)
.set_offset1(0x180)
.set_offset2(0x180)
.WriteTo(&ge2d_mmio_);
MatrixOffset::Get().FromValue(0).set_offset0(0).set_offset1(0).set_offset2(0).WriteTo(
&ge2d_mmio_);
} else if (!is_src_nv12 && is_dst_nv12) {
// RGB to BT.601 studio swing. Outputs of matrix multiplication seem
// to be divided by 1024.
MatrixCoef00_01::Get().FromValue(0).set_coef00(0x107).set_coef01(0x204).WriteTo(&ge2d_mmio_);
MatrixCoef02_10::Get().FromValue(0).set_coef02(0x64).set_coef10(0x1f68).WriteTo(&ge2d_mmio_);
MatrixCoef11_12::Get().FromValue(0).set_coef11(0x1ed6).set_coef12(0x1c2).WriteTo(&ge2d_mmio_);
MatrixCoef20_21::Get().FromValue(0).set_coef20(0x1c2).set_coef21(0x1e87).WriteTo(&ge2d_mmio_);
MatrixCoef22Ctrl::Get()
.FromValue(0)
.set_coef22(0x1fb7)
.set_saturation_enable(false)
.set_matrix_enable(true)
.WriteTo(&ge2d_mmio_);
MatrixPreOffset::Get().FromValue(0).set_offset0(0).set_offset1(0).set_offset2(0).WriteTo(
&ge2d_mmio_);
MatrixOffset::Get().FromValue(0).set_offset0(16).set_offset1(128).set_offset2(128).WriteTo(
&ge2d_mmio_);
} else {
// No colorspace conversion.
MatrixCoef22Ctrl::Get().FromValue(0).set_matrix_enable(false).WriteTo(&ge2d_mmio_);
}
// To match the linux driver we repeat the UV planes instead of interpolating if we're not
// scaling the output. This is arguably incorrect, depending on chroma siting.
Src1FmtCtrl::Get()
.FromValue(0)
.set_horizontal_enable(is_src_nv12)
.set_vertical_enable(is_src_nv12)
.set_y_chroma_phase(0x4c)
.set_x_chroma_phase(0x8)
.set_horizontal_repeat(!scaling_enabled)
.set_vertical_repeat(!scaling_enabled)
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetBlending(bool enable) {
if (enable) {
// Blend src2 (non-premultiplied) on top of src1. The hardware considers
// SRC1 to be source and SRC2 to be dest.
AluOpCtrl::Get()
.ReadFrom(&ge2d_mmio_)
.set_src2_cmult_ad(0)
.set_src1_color_mult(AluOpCtrl::kColorMultNone)
.set_src2_color_mult(AluOpCtrl::kColorMultNonPremult)
.set_blending_mode(AluOpCtrl::kBlendingModeAdd)
.set_source_factor(AluOpCtrl::kBlendingFactorOneMinusDstAlpha)
.set_logic_operation(AluOpCtrl::kBlendingFactorOne)
.set_alpha_blending_mode(AluOpCtrl::kBlendingModeAdd)
.set_alpha_source_factor(AluOpCtrl::kBlendingFactorZero)
.set_alpha_logic_operation(AluOpCtrl::kBlendingFactorOne)
.WriteTo(&ge2d_mmio_);
} else {
// Copy src1 color to output, but set alpha to 0xff.
AluOpCtrl::Get()
.ReadFrom(&ge2d_mmio_)
.set_src1_color_mult(AluOpCtrl::kColorMultNone)
.set_blending_mode(AluOpCtrl::kBlendingModeLogicOp)
.set_source_factor(AluOpCtrl::kBlendingFactorOne)
.set_logic_operation(AluOpCtrl::kLogicOperationCopy)
.set_alpha_blending_mode(AluOpCtrl::kBlendingModeLogicOp)
.set_alpha_logic_operation(AluOpCtrl::kLogicOperationSet)
.WriteTo(&ge2d_mmio_);
}
AluConstColor::Get().FromValue(0).set_a(0xff).WriteTo(&ge2d_mmio_);
GenCtrl1::Get().ReadFrom(&ge2d_mmio_).set_global_alpha(0xff).WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetInputRect(const rect_t& rect) {
uint32_t input_x_start = rect.x;
uint32_t input_x_end = rect.x + rect.width - 1;
uint32_t input_y_start = rect.y;
uint32_t input_y_end = rect.y + rect.height - 1;
Src1ClipXStartEnd::Get()
.FromValue(0)
.set_end(input_x_end)
.set_start(input_x_start)
.WriteTo(&ge2d_mmio_);
// The linux driver does Src1XStartEnd.set_start_extra(2).set_end_extra(3) but that seems to cause
// the first columns's chroma to be duplicated.
Src1XStartEnd::Get()
.FromValue(0)
.set_end(input_x_end)
.set_start(input_x_start)
.WriteTo(&ge2d_mmio_);
Src1ClipYStartEnd::Get()
.FromValue(0)
.set_end(input_y_end)
.set_start(input_y_start)
.WriteTo(&ge2d_mmio_);
// The linux driver does Src1YStartEnd.set_start_extra(2) but that seems to cause the first row's
// chroma to be duplicated.
Src1YStartEnd::Get()
.FromValue(0)
.set_end(input_y_end)
.set_start(input_y_start)
.set_end_extra(3)
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetSrc2InputRect(const rect_t& rect) {
uint32_t input_x_start = rect.x;
uint32_t input_x_end = rect.x + rect.width - 1;
uint32_t input_y_start = rect.y;
uint32_t input_y_end = rect.y + rect.height - 1;
Src2ClipXStartEnd::Get()
.FromValue(0)
.set_end(input_x_end)
.set_start(input_x_start)
.WriteTo(&ge2d_mmio_);
Src2XStartEnd::Get()
.FromValue(0)
.set_end(input_x_end)
.set_start(input_x_start)
.WriteTo(&ge2d_mmio_);
Src2ClipYStartEnd::Get()
.FromValue(0)
.set_end(input_y_end)
.set_start(input_y_start)
.WriteTo(&ge2d_mmio_);
Src2YStartEnd::Get()
.FromValue(0)
.set_end(input_y_end)
.set_start(input_y_start)
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetOutputRect(const rect_t& rect) {
uint32_t output_x_start = rect.x;
uint32_t output_x_end = rect.x + rect.width - 1;
uint32_t output_y_start = rect.y;
uint32_t output_y_end = rect.y + rect.height - 1;
DstClipXStartEnd::Get()
.FromValue(0)
.set_end(output_x_end)
.set_start(output_x_start)
.WriteTo(&ge2d_mmio_);
DstXStartEnd::Get()
.FromValue(0)
.set_end(output_x_end)
.set_start(output_x_start)
.WriteTo(&ge2d_mmio_);
DstClipYStartEnd::Get()
.FromValue(0)
.set_end(output_y_end)
.set_start(output_y_start)
.WriteTo(&ge2d_mmio_);
DstYStartEnd::Get()
.FromValue(0)
.set_end(output_y_end)
.set_start(output_y_start)
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetRects(const rect_t& input_rect, const rect_t& output_rect) {
InitializeScaler(input_rect.width, input_rect.height, output_rect.width, output_rect.height);
SetInputRect(input_rect);
SetOutputRect(output_rect);
}
void Ge2dDevice::ProcessAndWaitForIdle() {
TRACE_DURATION("camera", "Ge2dDevice::ProcessAndWaitForIdle");
CmdCtrl::Get().FromValue(0).set_cmd_wr(1).WriteTo(&ge2d_mmio_);
zx_port_packet_t packet;
ZX_ASSERT(ZX_OK == WaitForInterrupt(&packet));
if (packet.key == kPortKeyIrqMsg) {
ZX_ASSERT(ge2d_irq_.ack() == ZX_OK);
}
ZX_ASSERT(!Status0::Get().ReadFrom(&ge2d_mmio_).busy());
}
static rect_t FullImageRect(const image_format_2_t& format) {
return {
.x = 0,
.y = 0,
.width = format.coded_width,
.height = format.coded_height,
};
}
void Ge2dDevice::SetSrc1Input(const image_canvas_id_t& canvas) {
Src1Canvas::Get()
.FromValue(0)
.set_y(canvas.canvas_idx[kYComponent].id())
.set_u(canvas.canvas_idx[kUVComponent].id())
.set_v(canvas.canvas_idx[kUVComponent].id())
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetSrc2Input(const image_canvas_id_t& canvas) {
// Src2 doesn't support multiplanar images.
ZX_ASSERT(!canvas.canvas_idx[kUVComponent].valid());
ZX_ASSERT(canvas.canvas_idx[kYComponent].valid());
Src2DstCanvas::Get()
.ReadFrom(&ge2d_mmio_)
.set_src2(canvas.canvas_idx[kYComponent].id())
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::SetDstOutput(const image_canvas_id_t& canvas) {
Src2DstCanvas::Get()
.ReadFrom(&ge2d_mmio_)
.set_dst1(canvas.canvas_idx[kYComponent].id())
.set_dst2(canvas.canvas_idx[kUVComponent].id())
.WriteTo(&ge2d_mmio_);
}
void Ge2dDevice::ProcessResizeTask(Ge2dTask* task, uint32_t input_buffer_index,
const fzl::VmoPool::Buffer& output_buffer) {
TRACE_DURATION("camera", "Ge2dDevice::ProcessResizeTask");
image_format_2_t input_format = task->input_format();
image_format_2_t output_format = task->output_format();
rect_t output_rect = FullImageRect(output_format);
resize_info_t resize_info = task->resize_info();
bool scaling_enabled = (resize_info.crop.width != output_format.coded_width) ||
(resize_info.crop.height != output_format.coded_height);
SetRects(resize_info.crop, output_rect);
SetupInputOutputFormats(scaling_enabled, input_format, output_format);
SetBlending(false);
SetSrc1Input(task->GetInputCanvasIds(input_buffer_index));
SetDstOutput(task->GetOutputCanvasIds(output_buffer.vmo_handle()));
ProcessAndWaitForIdle();
}
void Ge2dDevice::ProcessWatermarkTask(Ge2dTask* task, uint32_t input_buffer_index,
const fzl::VmoPool::Buffer& output_buffer) {
TRACE_DURATION("camera", "Ge2dDevice::ProcessWatermarkTask");
image_format_2_t input_format = task->input_format();
image_format_2_t output_format = task->output_format();
rect_t output_rect = FullImageRect(output_format);
image_format_2_t watermark_format = task->WatermarkFormat();
rect_t input_rect = {
.x = task->watermark_loc_x(),
.y = task->watermark_loc_y(),
.width = watermark_format.coded_width,
.height = watermark_format.coded_height,
};
rect_t watermark_origin_rect = FullImageRect(watermark_format);
auto& input_ids = task->GetInputCanvasIds(input_buffer_index);
auto& output_canvas = task->GetOutputCanvasIds(output_buffer.vmo_handle());
// Copy entire input into output, unmodified.
SetRects(output_rect, output_rect);
SetupInputOutputFormats(/*scaling_enabled=*/false, input_format, output_format);
SetSrc1Input(input_ids);
SetDstOutput(output_canvas);
SetBlending(false);
ProcessAndWaitForIdle();
// Blend portion of input with watermark into temporary image (does colorspace conversion).
SetRects(input_rect, watermark_origin_rect);
SetSrc2InputRect(watermark_origin_rect);
SetBlending(true);
SetupInputOutputFormats(/*scaling_enabled=*/false, input_format, watermark_format);
SetSrc1Input(input_ids);
SetSrc2Input(task->watermark_input_canvas());
auto& intermediate_canvas = task->watermark_blended_canvas();
SetDstOutput(intermediate_canvas);
ProcessAndWaitForIdle();
// Copy from temporary image to correct region of output (does colorspace conversion).
SetRects(watermark_origin_rect, input_rect);
SetBlending(false);
SetupInputOutputFormats(/*scaling_enabled=*/false, watermark_format, output_format);
SetSrc1Input(intermediate_canvas);
SetDstOutput(output_canvas);
ProcessAndWaitForIdle();
}
void Ge2dDevice::ProcessInPlaceWatermarkTask(Ge2dTask* task, uint32_t input_buffer_index) {
TRACE_DURATION("camera", "Ge2dDevice::ProcessInPlaceWatermarkTask");
image_format_2_t input_format = task->input_format();
image_format_2_t output_format = input_format;
image_format_2_t watermark_format = task->WatermarkFormat();
rect_t input_rect = {
.x = task->watermark_loc_x(),
.y = task->watermark_loc_y(),
.width = watermark_format.coded_width,
.height = watermark_format.coded_height,
};
rect_t watermark_origin_rect = FullImageRect(watermark_format);
auto& input_ids = task->GetInputCanvasIds(input_buffer_index);
auto& output_canvas = input_ids;
// Blend portion of input with watermark into temporary image (does colorspace conversion).
SetRects(input_rect, watermark_origin_rect);
SetSrc2InputRect(watermark_origin_rect);
SetBlending(true);
SetupInputOutputFormats(/*scaling_enabled=*/false, input_format, watermark_format);
SetSrc1Input(input_ids);
SetSrc2Input(task->watermark_input_canvas());
auto& intermediate_canvas = task->watermark_blended_canvas();
SetDstOutput(intermediate_canvas);
ProcessAndWaitForIdle();
// Copy from temporary image to correct region of output (does colorspace conversion).
SetRects(watermark_origin_rect, input_rect);
SetBlending(false);
SetupInputOutputFormats(/*scaling_enabled=*/false, watermark_format, output_format);
SetSrc1Input(intermediate_canvas);
SetDstOutput(output_canvas);
ProcessAndWaitForIdle();
}
void Ge2dDevice::ProcessFrame(TaskInfo& info) {
TRACE_DURATION("camera", "Ge2dDevice::ProcessFrame");
auto task = info.task;
auto input_buffer_index = info.index;
if (task->Ge2dTaskType() == Ge2dTask::GE2D_IN_PLACE_WATERMARK) {
ProcessInPlaceWatermarkTask(task, input_buffer_index);
// Invoke the callback function and tell about the output buffer index
// which is ready to be used.
frame_available_info f_info;
f_info.frame_status = FRAME_STATUS_OK;
f_info.buffer_id = input_buffer_index;
f_info.metadata.timestamp = static_cast<uint64_t>(zx_clock_get_monotonic());
f_info.metadata.image_format_index = task->input_format_index();
f_info.metadata.input_buffer_index = input_buffer_index;
f_info.metadata.capture_timestamp = info.capture_timestamp;
task->FrameReadyCallback(&f_info);
return;
}
ZX_DEBUG_ASSERT(task->has_output_images());
auto output_buffer = task->WriteLockOutputBuffer();
if (!output_buffer) {
frame_available_info f_info;
f_info.frame_status = FRAME_STATUS_ERROR_FRAME;
f_info.buffer_id = 0;
f_info.metadata.timestamp = static_cast<uint64_t>(zx_clock_get_monotonic());
f_info.metadata.image_format_index = task->output_format_index();
f_info.metadata.input_buffer_index = input_buffer_index;
f_info.metadata.capture_timestamp = info.capture_timestamp;
task->FrameReadyCallback(&f_info);
return;
}
if (task->Ge2dTaskType() == Ge2dTask::GE2D_RESIZE) {
ProcessResizeTask(task, input_buffer_index, *output_buffer);
} else {
ZX_DEBUG_ASSERT(task->Ge2dTaskType() == Ge2dTask::GE2D_WATERMARK);
ProcessWatermarkTask(task, input_buffer_index, *output_buffer);
}
// Invoke the callback function and tell about the output buffer index
// which is ready to be used.
frame_available_info f_info;
f_info.frame_status = FRAME_STATUS_OK;
f_info.buffer_id = output_buffer->ReleaseWriteLockAndGetIndex();
f_info.metadata.timestamp = static_cast<uint64_t>(zx_clock_get_monotonic());
f_info.metadata.image_format_index = task->output_format_index();
f_info.metadata.input_buffer_index = input_buffer_index;
f_info.metadata.capture_timestamp = info.capture_timestamp;
task->FrameReadyCallback(&f_info);
}
void Ge2dDevice::ProcessRemoveTask(TaskInfo& info) {
fbl::AutoLock al(&interface_lock_);
auto task = info.task;
auto task_index = info.task_index;
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
if (task_entry == task_map_.end()) {
task->RemoveTaskCallback(TASK_REMOVE_STATUS_ERROR_INVALID);
return;
}
task->RemoveTaskCallback(TASK_REMOVE_STATUS_OK);
// Remove map entry.
task_map_.erase(task_entry);
}
int Ge2dDevice::FrameProcessingThread() {
FX_LOGST(TRACE, kTag) << "start";
for (;;) {
fbl::AutoLock al(&lock_);
while (processing_queue_.empty() && !shutdown_) {
frame_processing_signal_.Wait(&lock_);
}
if (shutdown_) {
break;
}
auto info = processing_queue_.back();
processing_queue_.pop_back();
al.release();
ProcessTask(info);
}
return ZX_OK;
}
zx_status_t Ge2dDevice::StartThread() {
return thrd_status_to_zx_status(thrd_create_with_name(
&processing_thread_,
[](void* arg) -> int { return reinterpret_cast<Ge2dDevice*>(arg)->FrameProcessingThread(); },
this, "ge2d-processing-thread"));
}
zx_status_t Ge2dDevice::StopThread() {
// Signal the worker thread and wait for it to terminate.
{
fbl::AutoLock al(&lock_);
shutdown_ = true;
frame_processing_signal_.Signal();
}
JoinThread();
return ZX_OK;
}
zx_status_t Ge2dDevice::WaitForInterrupt(zx_port_packet_t* packet) {
return port_.wait(zx::time::infinite(), packet);
}
// static
zx_status_t Ge2dDevice::Setup(zx_device_t* parent, std::unique_ptr<Ge2dDevice>* out) {
auto pdev = ddk::PDev::FromFragment(parent);
if (!pdev.is_valid()) {
FX_LOGST(ERROR, kTag) << "ZX_PROTOCOL_PDEV not available";
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> ge2d_mmio;
zx_status_t status = pdev.MapMmio(kGe2d, &ge2d_mmio);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "pdev_.MapMmio failed";
return status;
}
zx::interrupt ge2d_irq;
status = pdev.GetInterrupt(0, &ge2d_irq);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "pdev_.GetInterrupt failed";
return status;
}
zx::port port;
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "port create failed";
return status;
}
status = ge2d_irq.bind(port, kPortKeyIrqMsg, 0 /*options*/);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "interrupt bind failed";
return status;
}
zx::bti bti;
status = pdev.GetBti(0, &bti);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "could not obtain bti";
return status;
}
ddk::AmlogicCanvasProtocolClient canvas(parent, "canvas");
if (!canvas.is_valid()) {
FX_LOGST(ERROR, kTag) << "Could not get Amlogic Canvas protocol";
return ZX_ERR_NO_RESOURCES;
}
amlogic_canvas_protocol_t c;
canvas.GetProto(&c);
// TODO(fxbug.dev/43822): Initialize clock.
GenCtrl1::Get().FromValue(0).set_soft_reset(1).WriteTo(&*ge2d_mmio);
GenCtrl1::Get().FromValue(0).set_soft_reset(0).WriteTo(&*ge2d_mmio);
GenCtrl1::Get().FromValue(0).set_interrupt_on_idling(1).WriteTo(&*ge2d_mmio);
auto ge2d_device = std::make_unique<Ge2dDevice>(
parent, std::move(*ge2d_mmio), std::move(ge2d_irq), std::move(bti), std::move(port), c);
status = ge2d_device->StartThread();
*out = std::move(ge2d_device);
return status;
}
void Ge2dDevice::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
void Ge2dDevice::DdkRelease() {
StopThread();
delete this;
}
void Ge2dDevice::ShutDown() {}
} // namespace ge2d