blob: bb70df1f9e7193d235b90998c92b468db649ae54 [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-video.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/platform-defs.h>
#include <ddk/protocol/platform-device.h>
#include <hw/reg.h>
#include <hwreg/bitfields.h>
#include <hwreg/mmio.h>
#include <lib/zx/channel.h>
#include <memory.h>
#include <stdint.h>
#include <zircon/device/media-codec.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <chrono>
#include <memory>
#include <thread>
#include "device_ctx.h"
#include "device_fidl.h"
#include "local_codec_factory.h"
#include "macros.h"
#include "mpeg12_decoder.h"
#include "pts_manager.h"
#include "registers.h"
#if ENABLE_DECODER_TESTS
#include "tests/test_support.h"
#endif
// These match the regions exported when the bus device was added.
enum MmioRegion {
kCbus,
kDosbus,
kHiubus,
kAobus,
kDmc,
};
enum Interrupt {
kDemuxIrq,
kParserIrq,
kDosMbox0Irq,
kDosMbox1Irq,
kDosMbox2Irq,
};
AmlogicVideo::AmlogicVideo() { zx::event::create(0, &parser_finished_event_); }
AmlogicVideo::~AmlogicVideo() {
if (parser_interrupt_handle_) {
zx_interrupt_destroy(parser_interrupt_handle_.get());
if (parser_interrupt_thread_.joinable())
parser_interrupt_thread_.join();
}
if (vdec0_interrupt_handle_) {
zx_interrupt_destroy(vdec0_interrupt_handle_.get());
if (vdec0_interrupt_thread_.joinable())
vdec0_interrupt_thread_.join();
}
if (vdec1_interrupt_handle_) {
zx_interrupt_destroy(vdec1_interrupt_handle_.get());
if (vdec1_interrupt_thread_.joinable())
vdec1_interrupt_thread_.join();
}
video_decoder_.reset();
if (core_)
core_->PowerOff();
io_buffer_release(&mmio_cbus_);
io_buffer_release(&mmio_dosbus_);
io_buffer_release(&mmio_hiubus_);
io_buffer_release(&mmio_aobus_);
io_buffer_release(&mmio_dmc_);
io_buffer_release(&search_pattern_);
}
void AmlogicVideo::UngateClocks() {
HhiGclkMpeg0::Get()
.ReadFrom(hiubus_.get())
.set_dos(true)
.WriteTo(hiubus_.get());
HhiGclkMpeg1::Get()
.ReadFrom(hiubus_.get())
.set_u_parser_top(true)
.set_aiu(0xff)
.set_demux(true)
.set_audio_in(true)
.WriteTo(hiubus_.get());
HhiGclkMpeg2::Get()
.ReadFrom(hiubus_.get())
.set_vpu_interrupt(true)
.WriteTo(hiubus_.get());
}
void AmlogicVideo::GateClocks() {
// Keep VPU interrupt enabled, as it's used for vsync by the display.
HhiGclkMpeg1::Get()
.ReadFrom(hiubus_.get())
.set_u_parser_top(false)
.set_aiu(0)
.set_demux(false)
.set_audio_in(false)
.WriteTo(hiubus_.get());
HhiGclkMpeg0::Get()
.ReadFrom(hiubus_.get())
.set_dos(false)
.WriteTo(hiubus_.get());
}
zx_status_t AmlogicVideo::InitializeStreamBuffer(bool use_parser,
uint32_t size) {
stream_buffer_ = std::make_unique<StreamBuffer>();
zx_status_t status = io_buffer_init(stream_buffer_->buffer(), bti_.get(),
size, IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make video fifo");
return ZX_ERR_NO_MEMORY;
}
io_buffer_cache_flush(stream_buffer_->buffer(), 0,
io_buffer_size(stream_buffer_->buffer(), 0));
uint32_t buffer_address =
truncate_to_32(io_buffer_phys(stream_buffer_->buffer()));
core_->InitializeStreamInput(use_parser, buffer_address,
io_buffer_size(stream_buffer_->buffer(), 0));
return ZX_OK;
}
std::unique_ptr<CanvasEntry> AmlogicVideo::ConfigureCanvas(
io_buffer_t* io_buffer, uint32_t offset, uint32_t width, uint32_t height,
uint32_t wrap, uint32_t blockmode) {
assert(width % 8 == 0);
assert(offset % 8 == 0);
canvas_info_t info;
info.height = height;
info.stride_bytes = width;
info.wrap = wrap;
info.blkmode = blockmode;
enum {
kSwapBytes = 1,
kSwapWords = 2,
kSwapDoublewords = 4,
kSwapQuadwords = 8,
};
info.endianness =
kSwapBytes | kSwapWords |
kSwapDoublewords; // 64-bit big-endian to little-endian conversion.
zx::unowned_vmo vmo(io_buffer->vmo_handle);
zx::vmo dup_vmo;
zx_status_t status = vmo->duplicate(ZX_RIGHT_SAME_RIGHTS, &dup_vmo);
if (status != ZX_OK) {
DECODE_ERROR("Failed to duplicate handle, status: %d\n", status);
return nullptr;
}
uint8_t idx;
status = canvas_config(&canvas_, dup_vmo.release(), offset, &info, &idx);
if (status != ZX_OK) {
DECODE_ERROR("Failed to configure canvas, status: %d\n", status);
return nullptr;
}
return std::make_unique<CanvasEntry>(idx);
}
void AmlogicVideo::FreeCanvas(std::unique_ptr<CanvasEntry> canvas) {
canvas_free(&canvas_, canvas->index());
canvas->invalidate();
}
zx_status_t AmlogicVideo::AllocateIoBuffer(io_buffer_t* buffer, size_t size,
uint32_t alignment_log2,
uint32_t flags) {
return io_buffer_init_aligned(buffer, bti_.get(), size, alignment_log2,
flags);
}
// This parser handles MPEG elementary streams.
zx_status_t AmlogicVideo::InitializeEsParser() {
Reset1Register::Get().FromValue(0).set_parser(true).WriteTo(reset_.get());
FecInputControl::Get().FromValue(0).WriteTo(demux_.get());
TsHiuCtl::Get()
.ReadFrom(demux_.get())
.set_use_hi_bsf_interface(false)
.WriteTo(demux_.get());
TsHiuCtl2::Get()
.ReadFrom(demux_.get())
.set_use_hi_bsf_interface(false)
.WriteTo(demux_.get());
TsHiuCtl3::Get()
.ReadFrom(demux_.get())
.set_use_hi_bsf_interface(false)
.WriteTo(demux_.get());
TsFileConfig::Get()
.ReadFrom(demux_.get())
.set_ts_hiu_enable(false)
.WriteTo(demux_.get());
ParserConfig::Get()
.FromValue(0)
.set_pfifo_empty_cnt(10)
.set_max_es_write_cycle(1)
.set_max_fetch_cycle(16)
.WriteTo(parser_.get());
PfifoRdPtr::Get().FromValue(0).WriteTo(parser_.get());
PfifoWrPtr::Get().FromValue(0).WriteTo(parser_.get());
constexpr uint32_t kEsStartCodePattern = 0x00000100;
constexpr uint32_t kEsStartCodeMask = 0x0000ff00;
ParserSearchPattern::Get()
.FromValue(kEsStartCodePattern)
.WriteTo(parser_.get());
ParserSearchMask::Get().FromValue(kEsStartCodeMask).WriteTo(parser_.get());
ParserConfig::Get()
.FromValue(0)
.set_pfifo_empty_cnt(10)
.set_max_es_write_cycle(1)
.set_max_fetch_cycle(16)
.set_startcode_width(ParserConfig::kWidth24)
.set_pfifo_access_width(ParserConfig::kWidth8)
.WriteTo(parser_.get());
ParserControl::Get()
.FromValue(ParserControl::kAutoSearch)
.WriteTo(parser_.get());
// Set up output fifo.
uint32_t buffer_address =
truncate_to_32(io_buffer_phys(stream_buffer_->buffer()));
ParserVideoStartPtr::Get().FromValue(buffer_address).WriteTo(parser_.get());
ParserVideoEndPtr::Get()
.FromValue(buffer_address + io_buffer_size(stream_buffer_->buffer(), 0) -
8)
.WriteTo(parser_.get());
ParserEsControl::Get()
.ReadFrom(parser_.get())
.set_video_manual_read_ptr_update(false)
.WriteTo(parser_.get());
core_->InitializeParserInput();
// 512 bytes includes some padding to force the parser to read it completely.
constexpr uint32_t kSearchPatternSize = 512;
zx_status_t status =
io_buffer_init(&search_pattern_, bti_.get(), kSearchPatternSize,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to create search pattern buffer");
return status;
}
uint8_t input_search_pattern[kSearchPatternSize] = {0, 0, 1, 0xff};
memcpy(io_buffer_virt(&search_pattern_), input_search_pattern,
kSearchPatternSize);
io_buffer_cache_flush(&search_pattern_, 0, kSearchPatternSize);
// This check exists so we can call InitializeEsParser() more than once, when
// called from CodecImpl (indirectly via a CodecAdapter).
if (!parser_interrupt_thread_.joinable()) {
parser_interrupt_thread_ = std::thread([this]() {
DLOG("Starting parser thread\n");
while (true) {
zx_time_t time;
zx_status_t zx_status =
zx_interrupt_wait(parser_interrupt_handle_.get(), &time);
if (zx_status != ZX_OK)
return;
auto status = ParserIntStatus::Get().ReadFrom(parser_.get());
// Clear interrupt.
status.WriteTo(parser_.get());
DLOG("Got Parser interrupt status %x\n", status.reg_value());
if (status.start_code_found()) {
PfifoRdPtr::Get().FromValue(0).WriteTo(parser_.get());
PfifoWrPtr::Get().FromValue(0).WriteTo(parser_.get());
parser_finished_event_.signal(0, ZX_USER_SIGNAL_0);
}
}
});
}
ParserIntStatus::Get().FromValue(0xffff).WriteTo(parser_.get());
ParserIntEnable::Get()
.FromValue(0)
.set_host_en_start_code_found(true)
.WriteTo(parser_.get());
return ZX_OK;
}
zx_status_t AmlogicVideo::ParseVideo(void* data, uint32_t len) {
io_buffer_t input_file;
zx_status_t status = io_buffer_init(&input_file, bti_.get(), len,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to create input file");
return ZX_ERR_NO_MEMORY;
}
PfifoRdPtr::Get().FromValue(0).WriteTo(parser_.get());
PfifoWrPtr::Get().FromValue(0).WriteTo(parser_.get());
ParserControl::Get()
.ReadFrom(parser_.get())
.set_es_pack_size(len)
.WriteTo(parser_.get());
ParserControl::Get()
.ReadFrom(parser_.get())
.set_type(0)
.set_write(true)
.set_command(ParserControl::kAutoSearch)
.WriteTo(parser_.get());
memcpy(io_buffer_virt(&input_file), data, len);
io_buffer_cache_flush(&input_file, 0, len);
ParserFetchAddr::Get()
.FromValue(truncate_to_32(io_buffer_phys(&input_file)))
.WriteTo(parser_.get());
ParserFetchCmd::Get().FromValue(0).set_len(len).set_fetch_endian(7).WriteTo(
parser_.get());
// The parser finished interrupt shouldn't be signalled until after
// es_pack_size data has been read.
assert(ZX_ERR_TIMED_OUT == parser_finished_event_.wait_one(
ZX_USER_SIGNAL_0, zx::time(), nullptr));
ParserFetchAddr::Get()
.FromValue(truncate_to_32(io_buffer_phys(&search_pattern_)))
.WriteTo(parser_.get());
ParserFetchCmd::Get()
.FromValue(0)
.set_len(io_buffer_size(&search_pattern_, 0))
.set_fetch_endian(7)
.WriteTo(parser_.get());
status = parser_finished_event_.wait_one(
ZX_USER_SIGNAL_0, zx::deadline_after(zx::msec(10000)), nullptr);
parser_finished_event_.signal(ZX_USER_SIGNAL_0, 0);
if (status != ZX_OK) {
DECODE_ERROR("Parser timed out\n");
ParserFetchCmd::Get().FromValue(0).WriteTo(parser_.get());
// TODO(dustingreen): Evaluate whether it's safe to immediately do this
// io_buffer_release(). If the ParserFetchCmd write of 0 just above
// guarantees the HW will immediately stop reading input data from the
// input_file buffer, vs. potentially reading a bit more and allowing those
// reads to influence a lower-capability-client-visible output buffer. We
// might need to find a way to round-trip that we've stopped the parser
// before deleting the input buffer.
io_buffer_release(&input_file);
return ZX_ERR_TIMED_OUT;
}
io_buffer_release(&input_file);
return ZX_OK;
}
zx_status_t AmlogicVideo::ProcessVideoNoParser(void* data, uint32_t len) {
return ProcessVideoNoParserAtOffset(data, len, core_->GetStreamInputOffset());
}
zx_status_t AmlogicVideo::ProcessVideoNoParserAtOffset(
void* data, uint32_t len, uint32_t current_offset) {
if (len + current_offset > io_buffer_size(stream_buffer_->buffer(), 0)) {
DECODE_ERROR("Video too large\n");
return ZX_ERR_OUT_OF_RANGE;
}
memcpy(static_cast<uint8_t*>(io_buffer_virt(stream_buffer_->buffer())) +
current_offset,
data, len);
io_buffer_cache_flush(stream_buffer_->buffer(), current_offset, len);
core_->UpdateWritePointer(io_buffer_phys(stream_buffer_->buffer()) +
current_offset + len);
return ZX_OK;
}
zx_status_t AmlogicVideo::InitRegisters(zx_device_t* parent) {
parent_ = parent;
zx_status_t status =
device_get_protocol(parent_, ZX_PROTOCOL_PLATFORM_DEV, &pdev_);
if (status != ZX_OK) {
DECODE_ERROR("Failed to get parent protocol");
return ZX_ERR_NO_MEMORY;
}
status = device_get_protocol(parent_, ZX_PROTOCOL_CANVAS, &canvas_);
if (status != ZX_OK) {
DECODE_ERROR("Could not get video CANVAS protocol\n");
return status;
}
pdev_device_info_t info;
status = pdev_get_device_info(&pdev_, &info);
if (status != ZX_OK) {
DECODE_ERROR("pdev_get_device_info failed");
return status;
}
switch (info.pid) {
case PDEV_PID_AMLOGIC_S912:
device_type_ = DeviceType::kGXM;
break;
case PDEV_PID_AMLOGIC_S905D2:
device_type_ = DeviceType::kG12A;
break;
default:
DECODE_ERROR("Unknown soc pid: %d\n", info.pid);
return ZX_ERR_INVALID_ARGS;
}
status = pdev_map_mmio_buffer(&pdev_, kCbus, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&mmio_cbus_);
if (status != ZX_OK) {
DECODE_ERROR("Failed map cbus");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_mmio_buffer(&pdev_, kDosbus,
ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio_dosbus_);
if (status != ZX_OK) {
DECODE_ERROR("Failed map dosbus");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_mmio_buffer(&pdev_, kHiubus,
ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio_hiubus_);
if (status != ZX_OK) {
DECODE_ERROR("Failed map hiubus");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_mmio_buffer(&pdev_, kAobus, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&mmio_aobus_);
if (status != ZX_OK) {
DECODE_ERROR("Failed map aobus");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_mmio_buffer(&pdev_, kDmc, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&mmio_dmc_);
if (status != ZX_OK) {
DECODE_ERROR("Failed map dmc");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_interrupt(&pdev_, kParserIrq,
parser_interrupt_handle_.reset_and_get_address());
if (status != ZX_OK) {
DECODE_ERROR("Failed get parser interrupt");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_interrupt(&pdev_, kDosMbox0Irq,
vdec0_interrupt_handle_.reset_and_get_address());
if (status != ZX_OK) {
DECODE_ERROR("Failed get vdec0 interrupt");
return ZX_ERR_NO_MEMORY;
}
status = pdev_map_interrupt(&pdev_, kDosMbox1Irq,
vdec1_interrupt_handle_.reset_and_get_address());
if (status != ZX_OK) {
DECODE_ERROR("Failed get vdec interrupt");
return ZX_ERR_NO_MEMORY;
}
status = pdev_get_bti(&pdev_, 0, bti_.reset_and_get_address());
if (status != ZX_OK) {
DECODE_ERROR("Failed get bti");
return ZX_ERR_NO_MEMORY;
}
cbus_ = std::make_unique<CbusRegisterIo>(io_buffer_virt(&mmio_cbus_));
dosbus_ = std::make_unique<DosRegisterIo>(io_buffer_virt(&mmio_dosbus_));
hiubus_ = std::make_unique<HiuRegisterIo>(io_buffer_virt(&mmio_hiubus_));
aobus_ = std::make_unique<AoRegisterIo>(io_buffer_virt(&mmio_aobus_));
dmc_ = std::make_unique<DmcRegisterIo>(io_buffer_virt(&mmio_dmc_));
int64_t reset_register_offset = 0;
int64_t parser_register_offset = 0;
int64_t demux_register_offset = 0;
if (device_type_ == DeviceType::kG12A) {
// Some portions of the cbus moved in newer versions (TXL and later).
reset_register_offset = (0x0401 - 0x1101);
parser_register_offset = 0x3800 - 0x2900;
demux_register_offset = 0x1800 - 0x1600;
}
auto cbus_base = static_cast<volatile uint32_t*>(io_buffer_virt(&mmio_cbus_));
reset_ = std::make_unique<ResetRegisterIo>(cbus_base + reset_register_offset);
parser_ =
std::make_unique<ParserRegisterIo>(cbus_base + parser_register_offset);
demux_ = std::make_unique<DemuxRegisterIo>(cbus_base + demux_register_offset);
registers_ = std::unique_ptr<MmioRegisters>(new MmioRegisters{
dosbus_.get(), aobus_.get(), dmc_.get(), hiubus_.get(), reset_.get()});
firmware_ = std::make_unique<FirmwareBlob>();
status = firmware_->LoadFirmware(parent_);
if (status != ZX_OK) {
DECODE_ERROR("Failed load firmware\n");
return status;
}
return ZX_OK;
}
void AmlogicVideo::InitializeInterrupts() {
vdec0_interrupt_thread_ = std::thread([this]() {
while (true) {
zx_time_t time;
zx_status_t status =
zx_interrupt_wait(vdec0_interrupt_handle_.get(), &time);
if (status != ZX_OK)
return;
std::lock_guard<std::mutex> lock(video_decoder_lock_);
if (video_decoder_)
video_decoder_->HandleInterrupt();
}
});
vdec1_interrupt_thread_ = std::thread([this]() {
while (true) {
zx_time_t time;
zx_status_t status =
zx_interrupt_wait(vdec1_interrupt_handle_.get(), &time);
if (status == ZX_ERR_CANCELED) {
// expected when zx_interrupt_destroy() is called
return;
}
if (status != ZX_OK) {
// unexpected errors
DECODE_ERROR(
"AmlogicVideo::InitializeInterrupts() zx_interrupt_wait() failed "
"status: %d\n",
status);
return;
}
std::lock_guard<std::mutex> lock(video_decoder_lock_);
if (video_decoder_)
video_decoder_->HandleInterrupt();
}
});
}
zx_status_t AmlogicVideo::InitDecoder() {
InitializeInterrupts();
return ZX_OK;
}