| // 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/ddk/debug.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/trace/event.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 <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, |
| watermark_input_contiguous_vmos_, watermark_blended_contiguous_vmo_, |
| 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, watermark_input_contiguous_vmos_, watermark_blended_contiguous_vmo_, |
| 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 = info.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<fdf::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); |
| |
| std::vector<zx::vmo> watermark_input_contiguous_vmos; |
| for (uint32_t i = 0; i < kWatermarkInputBufferCount; i++) { |
| zx::vmo vmo; |
| status = zx::vmo::create_contiguous(bti, kWatermarkMaxSize, 0, &vmo); |
| if (status != ZX_OK) { |
| FX_LOGST(ERROR, kTag) << "Unable to create contiguous memory for input watermark VMO"; |
| return ZX_ERR_NO_MEMORY; |
| } |
| watermark_input_contiguous_vmos.push_back(std::move(vmo)); |
| } |
| |
| zx::vmo watermark_blended_contiguous_vmo; |
| status = zx::vmo::create_contiguous(bti, kWatermarkMaxSize, 0, &watermark_blended_contiguous_vmo); |
| if (status != ZX_OK) { |
| FX_LOGST(ERROR, kTag) << "Unable to create contiguous memory for blended watermark VMO"; |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| auto ge2d_device = std::make_unique<Ge2dDevice>( |
| parent, std::move(*ge2d_mmio), std::move(ge2d_irq), std::move(bti), std::move(port), |
| std::move(watermark_input_contiguous_vmos), std::move(watermark_blended_contiguous_vmo), 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 |