| // 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/media/drivers/amlogic_encoder/device_ctx.h" |
| |
| #include <fuchsia/mediacodec/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <zircon/assert.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| #include <optional> |
| |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/mmio-buffer.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/composite.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <ddktl/protocol/composite.h> |
| |
| #include "ddktl/protocol/amlogiccanvas.h" |
| #include "ddktl/protocol/sysmem.h" |
| #include "lib/mmio/mmio.h" |
| #include "lib/zx/interrupt.h" |
| #include "src/media/drivers/amlogic_encoder/local_codec_factory.h" |
| #include "src/media/drivers/amlogic_encoder/macros.h" |
| #include "src/media/drivers/amlogic_encoder/memory_barriers.h" |
| #include "src/media/drivers/amlogic_encoder/registers.h" |
| |
| enum MmioRegion { |
| kCbus, |
| kDosbus, |
| kAobus, |
| kHiubus, |
| }; |
| |
| enum Interrupt { |
| kDosMbox2Irq, |
| }; |
| |
| enum { |
| kComponentPdev = 0, |
| kComponentSysmem = 1, |
| kComponentCanvas = 2, |
| kComponentCount = 3, |
| }; |
| |
| // clock_level = 5 or 6 will cause encoder reset, reference bug 120995073 |
| constexpr uint32_t kClockLevel = 4; |
| |
| const fuchsia_hardware_mediacodec_Device_ops_t kFidlOps = { |
| .GetCodecFactory = |
| [](void* ctx, zx_handle_t handle) { |
| zx::channel request(handle); |
| reinterpret_cast<DeviceCtx*>(ctx)->GetCodecFactory(std::move(request)); |
| return ZX_OK; |
| }, |
| }; |
| |
| std::unique_ptr<DeviceCtx> DeviceCtx::Create(zx_device_t* parent) { |
| ddk::CompositeProtocolClient composite(parent); |
| if (!composite.is_valid()) { |
| ENCODE_ERROR("Could not get composite protocol"); |
| return nullptr; |
| } |
| |
| zx_device_t* components[kComponentCount]; |
| size_t component_count = 0; |
| composite.GetComponents(components, fbl::count_of(components), &component_count); |
| |
| if (component_count != kComponentCount) { |
| ENCODE_ERROR("Could not get components"); |
| return nullptr; |
| } |
| |
| ddk::PDev pdev(components[kComponentPdev]); |
| if (!pdev.is_valid()) { |
| ENCODE_ERROR("Failed to get pdev protocol"); |
| return nullptr; |
| } |
| |
| ddk::SysmemProtocolClient sysmem(components[kComponentSysmem]); |
| if (!sysmem.is_valid()) { |
| ENCODE_ERROR("Could not get sysmem protocol"); |
| return nullptr; |
| } |
| |
| ddk::AmlogicCanvasProtocolClient canvas(components[kComponentCanvas]); |
| if (!canvas.is_valid()) { |
| ENCODE_ERROR("Could not get canvas protocol"); |
| return nullptr; |
| } |
| |
| std::optional<ddk::MmioBuffer> mmio; |
| auto status = pdev.MapMmio(kCbus, &mmio); |
| if (status != ZX_OK || !mmio) { |
| ENCODE_ERROR("Failed map cbus %d", status); |
| return nullptr; |
| } |
| CbusRegisterIo cbus(std::move(*mmio)); |
| |
| mmio = std::nullopt; |
| status = pdev.MapMmio(kDosbus, &mmio); |
| if (status != ZX_OK || !mmio) { |
| ENCODE_ERROR("Failed map dosbus %d", status); |
| return nullptr; |
| } |
| DosRegisterIo dosbus(std::move(*mmio)); |
| |
| mmio = std::nullopt; |
| status = pdev.MapMmio(kAobus, &mmio); |
| if (status != ZX_OK || !mmio) { |
| ENCODE_ERROR("Failed map aobus %d", status); |
| return nullptr; |
| } |
| AoRegisterIo aobus(std::move(*mmio)); |
| |
| mmio = std::nullopt; |
| status = pdev.MapMmio(kHiubus, &mmio); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("Failed map hiubus %d", status); |
| return nullptr; |
| } |
| HiuRegisterIo hiubus(std::move(*mmio)); |
| |
| zx::interrupt interrupt; |
| status = pdev.GetInterrupt(kDosMbox2Irq, 0, &interrupt); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("Failed get enc interrupt"); |
| return nullptr; |
| } |
| |
| zx::bti bti; |
| status = pdev.GetBti(0, &bti); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("Failed get bti"); |
| return nullptr; |
| } |
| |
| return std::make_unique<DeviceCtx>(parent, pdev, canvas, sysmem, std::move(cbus), |
| std::move(dosbus), std::move(aobus), std::move(hiubus), |
| std::move(interrupt), std::move(bti)); |
| } |
| |
| void DeviceCtx::DdkUnbindNew(ddk::UnbindTxn txn) { |
| ShutDown(); |
| txn.Reply(); |
| } |
| |
| void DeviceCtx::DdkRelease() { delete this; } |
| |
| zx_status_t DeviceCtx::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { |
| return fuchsia_hardware_mediacodec_Device_dispatch(this, txn, msg, &kFidlOps); |
| } |
| |
| void DeviceCtx::ShutDown() { |
| if (interrupt_handle_) { |
| zx_interrupt_destroy(interrupt_handle_.get()); |
| if (interrupt_thread_.joinable()) { |
| interrupt_thread_.join(); |
| } |
| } |
| loop_.Shutdown(); |
| } |
| |
| zx_status_t DeviceCtx::Bind() { |
| pdev_device_info_t info; |
| auto status = pdev_.GetDeviceInfo(&info); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("pdev_get_device_info failed"); |
| return status; |
| } |
| |
| switch (info.pid) { |
| case PDEV_PID_AMLOGIC_S905D2: |
| soc_type_ = SocType::kG12A; |
| break; |
| case PDEV_PID_AMLOGIC_T931: |
| soc_type_ = SocType::kG12B; |
| break; |
| default: |
| ENCODE_ERROR("Unknown soc pid: %d\n", info.pid); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| status = load_firmware(parent(), "h264_enc.bin", firmware_vmo_.reset_and_get_address(), |
| &firmware_size_); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("Couldn't load firmware\n"); |
| return status; |
| } |
| |
| zx::vmar::root_self()->map(0, firmware_vmo_, 0, firmware_size_, ZX_VM_PERM_READ, &firmware_ptr_); |
| |
| sysmem_sync_ptr_.Bind(ConnectToSysmem()); |
| if (!sysmem_sync_ptr_) { |
| ENCODE_ERROR("ConnectToSysmem() failed"); |
| status = ZX_ERR_INTERNAL; |
| return status; |
| } |
| |
| InterruptInit(); |
| |
| status = loop_.StartThread("device-loop", &loop_thread_); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("could not start loop thread"); |
| return status; |
| } |
| |
| status = DdkAdd("amlogic_video_enc"); |
| |
| return status; |
| } |
| |
| zx_status_t DeviceCtx::LoadFirmware() { |
| io_buffer_t firmware_buffer; |
| const uint64_t kFirmwareSize = 8 * 4096; |
| const uint32_t kDmaCount = 0x1000; |
| // Most buffers should be 64-kbyte aligned. |
| const uint32_t kBufferAlignShift = 16; |
| const char* kBufferName = "EncFirmware"; |
| |
| HcodecAssistMmcCtrl1::Get().FromValue(HcodecAssistMmcCtrl1::kCtrl).WriteTo(&dosbus_); |
| |
| zx_status_t status = io_buffer_init_aligned(&firmware_buffer, bti_.get(), kFirmwareSize, |
| kBufferAlignShift, IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("Failed to make firmware buffer"); |
| return status; |
| } |
| |
| zx_object_set_property(firmware_buffer.vmo_handle, ZX_PROP_NAME, kBufferName, |
| strlen(kBufferName)); |
| |
| memcpy(io_buffer_virt(&firmware_buffer), reinterpret_cast<void*>(firmware_ptr_), |
| std::min(firmware_size_, kFirmwareSize)); |
| io_buffer_cache_flush(&firmware_buffer, 0, kFirmwareSize); |
| |
| BarrierAfterFlush(); |
| |
| HcodecMpsr::Get().FromValue(0).WriteTo(&dosbus_); |
| HcodecCpsr::Get().FromValue(0).WriteTo(&dosbus_); |
| |
| // delay |
| for (int i = 0; i < 3; i++) { |
| HcodecMpsr::Get().ReadFrom(&dosbus_); |
| } |
| |
| HcodecImemDmaAdr::Get() |
| .FromValue(truncate_to_32(io_buffer_phys(&firmware_buffer))) |
| .WriteTo(&dosbus_); |
| HcodecImemDmaCount::Get().FromValue(kDmaCount).WriteTo(&dosbus_); |
| HcodecImemDmaCtrl::Get() |
| .FromValue(0) |
| .set_ready(1) |
| .set_ctrl(HcodecImemDmaCtrl::kCtrl) |
| .WriteTo(&dosbus_); |
| |
| if (!WaitForRegister(std::chrono::seconds(1), [this] { |
| return HcodecImemDmaCtrl::Get().ReadFrom(&dosbus_).ready() == 0; |
| })) { |
| ENCODE_ERROR("Failed to load microcode."); |
| |
| BarrierBeforeRelease(); |
| io_buffer_release(&firmware_buffer); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| BarrierBeforeRelease(); |
| io_buffer_release(&firmware_buffer); |
| return ZX_OK; |
| } |
| |
| void DeviceCtx::InterruptInit() { |
| interrupt_thread_ = std::thread([this]() { |
| while (true) { |
| zx_time_t time; |
| zx_status_t status = zx_interrupt_wait(interrupt_handle_.get(), &time); |
| if (status != ZX_OK) { |
| ENCODE_ERROR("zx_interrupt_wait() failed - status: %d", status); |
| return; |
| } |
| |
| HcodecIrqMboxClear::Get().FromValue(1).WriteTo(&dosbus_); |
| hw_status_ = |
| static_cast<EncoderStatus>(HcodecEncoderStatus::Get().ReadFrom(&dosbus_).reg_value()); |
| sync_completion_signal(&interrupt_sync_); |
| } |
| }); |
| } |
| |
| zx_status_t DeviceCtx::BufferAlloc() { |
| constexpr uint32_t kDec0Size = 0x300000; |
| constexpr uint32_t kDec1Size = 0x300000; |
| constexpr uint32_t kAssitSize = 0xc0000; |
| constexpr uint32_t kScaleBufSize = 0x300000; |
| constexpr uint32_t kDumpInfoSize = 0xa0000; |
| constexpr uint32_t kCbrInfoSize = 0x2000; |
| |
| auto result = InternalBuffer::Create("H264EncoderDec0", &sysmem_sync_ptr_, bti(), kDec0Size, |
| /*is_writable=*/true, |
| /*is_mapping_needed*/ |
| false); |
| if (!result.is_ok()) { |
| ENCODE_ERROR("Failed to make buffer - status: %d", result.error()); |
| return result.error(); |
| } |
| dec0_.emplace(result.take_value()); |
| |
| result = InternalBuffer::Create("H264EncoderDec1", &sysmem_sync_ptr_, bti(), kDec1Size, |
| |
| /*is_writable=*/true, |
| /*is_mapping_needed*/ |
| false); |
| if (!result.is_ok()) { |
| ENCODE_ERROR("Failed to make buffer - status: %d", result.error()); |
| return result.error(); |
| } |
| dec1_.emplace(result.take_value()); |
| |
| result = InternalBuffer::Create("H264EncoderAssit", &sysmem_sync_ptr_, bti(), kAssitSize, |
| |
| /*is_writable=*/true, |
| /*is_mapping_needed*/ |
| false); |
| if (!result.is_ok()) { |
| ENCODE_ERROR("Failed to make buffer - status: %d", result.error()); |
| return result.error(); |
| } |
| assit_.emplace(result.take_value()); |
| |
| result = InternalBuffer::Create("H264EncoderScaleBuf", &sysmem_sync_ptr_, bti(), kScaleBufSize, |
| /*is_writable=*/true, |
| /*is_mapping_needed*/ |
| false); |
| if (!result.is_ok()) { |
| ENCODE_ERROR("Failed to make buffer - status: %d", result.error()); |
| return result.error(); |
| } |
| scale_buff_.emplace(result.take_value()); |
| |
| result = InternalBuffer::Create("H264EncoderDumpInfo", &sysmem_sync_ptr_, bti(), kDumpInfoSize, |
| /*is_writable=*/true, |
| /*is_mapping_needed*/ |
| false); |
| if (!result.is_ok()) { |
| ENCODE_ERROR("Failed to make buffer - status: %d", result.error()); |
| return result.error(); |
| } |
| dump_info_.emplace(result.take_value()); |
| |
| result = InternalBuffer::Create("H264EncoderCbrInfo", &sysmem_sync_ptr_, bti(), kCbrInfoSize, |
| /*is_writable=*/true, |
| /*is_mapping_needed*/ |
| false); |
| if (!result.is_ok()) { |
| ENCODE_ERROR("Failed to make buffer - status: %d", result.error()); |
| return result.error(); |
| } |
| cbr_info_.emplace(result.take_value()); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceCtx::PowerOn() { |
| // ungate dos clk |
| // TODO(45193) shared with vdec |
| HhiGclkMpeg0::Get().ReadFrom(&hiubus_).set_dos(true).WriteTo(&hiubus_); |
| |
| // power up HCODEC |
| AoRtiGenPwrSleep0::Get() |
| .ReadFrom(&aobus_) |
| .set_dos_hcodec_d1_pwr_off(0) |
| .set_dos_hcodec_pwr_off(0) |
| .WriteTo(&aobus_); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| |
| // reset all of HCODEC |
| DosSwReset1::Get().FromValue(DosSwReset1::kAll).WriteTo(&dosbus_); |
| DosSwReset1::Get().FromValue(DosSwReset1::kNone).WriteTo(&dosbus_); |
| |
| // ungate clock |
| switch (kClockLevel) { |
| case 4: // 400mhz |
| { |
| // TODO(45193) shared with vdec, should be synchronized |
| HhiVdecClkCntl::Get() |
| .ReadFrom(&hiubus_) |
| .set_hcodec_clk_sel(/*fclk_div4*/ 2) |
| .set_hcodec_clk_en(1) |
| .set_hcodec_clk_div(/*fclk_div2p5*/ 0) |
| .WriteTo(&hiubus_); |
| break; |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // TODO(45193) shared with vdec |
| DosGclkEn0::Get().ReadFrom(&dosbus_).set_hcodec_en(0x7fff).WriteTo(&dosbus_); |
| |
| // powerup hcodec memories |
| DosMemPdHcodec::Get().FromValue(0).WriteTo(&dosbus_); |
| |
| // remove hcodec iso |
| AoRtiGenPwrIso0::Get() |
| .ReadFrom(&aobus_) |
| .set_dos_hcodec_iso_in_en(0) |
| .set_dos_hcodec_iso_out_en(0) |
| .WriteTo(&aobus_); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| |
| // TODO(45193) shared with vdec |
| // disable auto clock gate |
| DosGenCtrl0::Get().ReadFrom(&dosbus_).set_hcodec_auto_clock_gate(1).WriteTo(&dosbus_); |
| DosGenCtrl0::Get().ReadFrom(&dosbus_).set_hcodec_auto_clock_gate(0).WriteTo(&dosbus_); |
| |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceCtx::CanvasInit() { return ZX_OK; } |
| |
| void DeviceCtx::Reset() { |
| for (int i = 0; i < 3; i++) { |
| // delay |
| DosSwReset1::Get().ReadFrom(&dosbus_); |
| } |
| |
| DosSwReset1::Get() |
| .FromValue(0) |
| .set_hcodec_assist(1) |
| .set_hcodec_iqidct(1) |
| .set_hcodec_mc(1) |
| .set_hcodec_dblk(1) |
| .set_hcodec_afifo(1) |
| .set_hcodec_vlc(1) |
| .set_hcodec_qdct(1) |
| .WriteTo(&dosbus_); |
| |
| DosSwReset1::Get().FromValue(DosSwReset1::kNone).WriteTo(&dosbus_); |
| |
| for (int i = 0; i < 3; i++) { |
| // delay |
| DosSwReset1::Get().ReadFrom(&dosbus_); |
| } |
| } |
| |
| void DeviceCtx::Config() { |
| HcodecVlcTotalBytes::Get().FromValue(0).WriteTo(&dosbus_); |
| HcodecVlcConfig::Get().FromValue(0x7).WriteTo(&dosbus_); |
| HcodecVlcIntControl::Get().FromValue(0).WriteTo(&dosbus_); |
| |
| HcodecAssistAmr1Int0::Get().FromValue(0x15).WriteTo(&dosbus_); |
| HcodecAssistAmr1Int1::Get().FromValue(0x8).WriteTo(&dosbus_); |
| HcodecAssistAmr1Int3::Get().FromValue(0x14).WriteTo(&dosbus_); |
| |
| HcodecIdrPicId::Get().FromValue(idr_pic_id_).WriteTo(&dosbus_); |
| HcodecFrameNumber::Get().FromValue(frame_number_).WriteTo(&dosbus_); |
| HcodecPicOrderCntLsb::Get().FromValue(pic_order_cnt_lsb_).WriteTo(&dosbus_); |
| |
| const uint32_t kInitQpPicture = 26; |
| const uint32_t kLog2MaxFrameNum = 4; |
| const uint32_t kLog2MaxPicOrderCntLsb = 4; |
| const uint32_t kAnc0BufferId = 0; |
| |
| HcodecLog2MaxPicOrderCntLsb::Get().FromValue(kLog2MaxPicOrderCntLsb).WriteTo(&dosbus_); |
| HcodecLog2MaxFrameNum::Get().FromValue(kLog2MaxFrameNum).WriteTo(&dosbus_); |
| HcodecAnc0BufferId::Get().FromValue(kAnc0BufferId).WriteTo(&dosbus_); |
| HcodecQpPicture::Get().FromValue(kInitQpPicture).WriteTo(&dosbus_); |
| } |
| |
| // encoder control |
| zx_status_t DeviceCtx::EncoderInit(const fuchsia::media::FormatDetails& format_details) { |
| if (!format_details.has_domain() || !format_details.domain().is_video() || |
| !format_details.domain().video().is_uncompressed()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| encoder_width_ = format_details.domain().video().uncompressed().image_format.display_width; |
| encoder_height_ = format_details.domain().video().uncompressed().image_format.display_height; |
| rows_per_slice_ = picture_to_mb(encoder_height_); |
| |
| auto status = BufferAlloc(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = PowerOn(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = CanvasInit(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = LoadFirmware(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| Reset(); |
| |
| Config(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceCtx::StartEncoder() { return ZX_OK; } |
| |
| zx_status_t DeviceCtx::StopEncoder() { return ZX_OK; } |
| zx_status_t DeviceCtx::WaitForIdle() { return ZX_OK; } |
| zx_status_t DeviceCtx::EncodeFrame(const CodecBuffer* buffer, uint8_t* data, uint32_t len) { |
| return ZX_OK; |
| } |
| void DeviceCtx::ReturnBuffer(const CodecBuffer* buffer) {} |
| void DeviceCtx::SetOutputBuffers(std::vector<const CodecBuffer*> buffers) {} |
| |
| void DeviceCtx::SetEncodeParams(fuchsia::media::FormatDetails format_details) {} |
| |
| fidl::InterfaceHandle<fuchsia::sysmem::Allocator> DeviceCtx::ConnectToSysmem() { |
| fidl::InterfaceHandle<fuchsia::sysmem::Allocator> client_end; |
| fidl::InterfaceRequest<fuchsia::sysmem::Allocator> server_end = client_end.NewRequest(); |
| zx_status_t connect_status = sysmem_.Connect(server_end.TakeChannel()); |
| if (connect_status != ZX_OK) { |
| // failure |
| return fidl::InterfaceHandle<fuchsia::sysmem::Allocator>(); |
| } |
| return client_end; |
| } |
| |
| void DeviceCtx::GetCodecFactory(zx::channel request) { |
| // post to fidl thread to avoid racing on creation/error |
| async::PostTask(loop_.dispatcher(), [this, request = std::move(request)]() mutable { |
| fidl::InterfaceRequest<fuchsia::mediacodec::CodecFactory> factory_request(std::move(request)); |
| |
| std::unique_ptr<LocalCodecFactory> codec_factory = std::make_unique<LocalCodecFactory>( |
| loop_.dispatcher(), this, std::move(factory_request), |
| /*factory_done_callback=*/ |
| [this](LocalCodecFactory* codec_factory, |
| std::unique_ptr<CodecImpl> created_codec_instance) { |
| // own codec impl and bind it |
| codec_instance_ = std::move(created_codec_instance); |
| codec_instance_->BindAsync(/*error_handler=*/[this] { |
| // Drop codec impl and close channel on error |
| codec_instance_ = nullptr; |
| }); |
| // drop factory and close factory channel |
| codec_factories_.erase(codec_factory); |
| }, |
| codec_admission_control_.get(), |
| /* error_handler */ |
| [this](LocalCodecFactory* codec_factory, zx_status_t error) { |
| // Drop and close factory channel on error. |
| codec_factories_.erase(codec_factory); |
| }); |
| |
| codec_factories_.emplace(codec_factory.get(), std::move(codec_factory)); |
| }); |
| } |