blob: 3e19283ada6fdc38cb8b216b43cd2efd9d4f93ce [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 "registers.h"
#if ENABLE_DECODER_TESTS
#include "tests/test_support.h"
#endif
constexpr uint64_t kStreamBufferSize = PAGE_SIZE * 1024;
extern "C" {
zx_status_t amlogic_video_init(void** out_ctx);
zx_status_t amlogic_video_bind(void* ctx, zx_device_t* parent);
}
static zx_status_t amlogic_video_ioctl(void* ctx, uint32_t op,
const void* in_buf, size_t in_len,
void* out_buf, size_t out_len,
size_t* out_actual) {
// The only IOCTL we support is get channel.
if (op != MEDIA_CODEC_IOCTL_GET_CODEC_FACTORY_CHANNEL) {
return ZX_ERR_NOT_SUPPORTED;
}
if ((out_buf == nullptr) || (out_actual == nullptr) ||
(out_len != sizeof(zx_handle_t))) {
return ZX_ERR_INVALID_ARGS;
}
DeviceCtx* device = reinterpret_cast<DeviceCtx*>(ctx);
zx::channel codec_factory_client_endpoint;
device->device_fidl()->CreateChannelBoundCodecFactory(
&codec_factory_client_endpoint);
return ZX_OK;
}
static zx_protocol_device_t amlogic_video_device_ops = {
DEVICE_OPS_VERSION, .ioctl = amlogic_video_ioctl,
// TODO(jbauman) or TODO(dustingreen): .suspend .resume, maybe .release if
// it would ever be run. Currently ~AmlogicVideo code sets lower power, but
// ~AmlogicVideo doesn't run yet.
};
// Most buffers should be 64-kbyte aligned.
const uint32_t kBufferAlignShift = 4 + 12;
// 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() {
if (parser_interrupt_handle_) {
zx_interrupt_destroy(parser_interrupt_handle_.get());
if (parser_interrupt_thread_.joinable())
parser_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();
DisableVideoPower();
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(&stream_buffer_);
}
void AmlogicVideo::EnableClockGate() {
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::DisableClockGate() {
HhiGclkMpeg2::Get()
.ReadFrom(hiubus_.get())
.set_vpu_interrupt(false)
.WriteTo(hiubus_.get());
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());
}
void AmlogicVideo::EnableVideoPower() {
{
auto temp = AoRtiGenPwrSleep0::Get().ReadFrom(aobus_.get());
temp.set_reg_value(temp.reg_value() & ~0xc);
temp.WriteTo(aobus_.get());
}
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
DosSwReset0::Get().FromValue(0xfffffffc).WriteTo(dosbus_.get());
DosSwReset0::Get().FromValue(0).WriteTo(dosbus_.get());
EnableClockGate();
HhiVdecClkCntl::Get().FromValue(0).set_vdec_en(true).set_vdec_sel(3).WriteTo(
hiubus_.get());
DosGclkEn::Get().FromValue(0x3ff).WriteTo(dosbus_.get());
DosMemPdVdec::Get().FromValue(0).WriteTo(dosbus_.get());
{
auto temp = AoRtiGenPwrIso0::Get().ReadFrom(aobus_.get());
temp.set_reg_value(temp.reg_value() & ~0xc0);
temp.WriteTo(aobus_.get());
}
DosVdecMcrccStallCtrl::Get().FromValue(0).WriteTo(dosbus_.get());
DmcReqCtrl::Get().ReadFrom(dmc_.get()).set_vdec(true).WriteTo(dmc_.get());
MdecPicDcCtrl::Get()
.ReadFrom(dosbus_.get())
.set_bit31(false)
.WriteTo(dosbus_.get());
video_power_enabled_ = true;
}
void AmlogicVideo::DisableVideoPower() {
if (!video_power_enabled_)
return;
video_power_enabled_ = false;
DmcReqCtrl::Get().ReadFrom(dmc_.get()).set_vdec(false).WriteTo(dmc_.get());
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
{
auto temp = AoRtiGenPwrIso0::Get().ReadFrom(aobus_.get());
temp.set_reg_value(temp.reg_value() | 0xc0);
temp.WriteTo(aobus_.get());
}
DosMemPdVdec::Get().FromValue(~0u).WriteTo(dosbus_.get());
HhiVdecClkCntl::Get().FromValue(0).set_vdec_en(false).set_vdec_sel(3).WriteTo(
hiubus_.get());
{
auto temp = AoRtiGenPwrSleep0::Get().ReadFrom(aobus_.get());
temp.set_reg_value(temp.reg_value() | 0xc);
temp.WriteTo(aobus_.get());
}
DisableClockGate();
}
// Wait for a condition to become true, with a timeout.
template <typename DurationType, typename T>
bool WaitForRegister(DurationType timeout, T condition) {
auto start = std::chrono::high_resolution_clock::now();
auto cast_timeout =
std::chrono::duration_cast<std::chrono::high_resolution_clock::duration>(
timeout);
while (!condition()) {
if (std::chrono::high_resolution_clock::now() - start >= cast_timeout) {
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return true;
}
zx_status_t AmlogicVideo::LoadDecoderFirmware(uint8_t* data, uint32_t size) {
Mpsr::Get().FromValue(0).WriteTo(dosbus_.get());
Cpsr::Get().FromValue(0).WriteTo(dosbus_.get());
io_buffer_t firmware_buffer;
const uint32_t kFirmwareSize = 4 * 4096;
zx_status_t status = io_buffer_init_aligned(&firmware_buffer, bti_.get(),
kFirmwareSize, kBufferAlignShift,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make firmware buffer");
return status;
}
memcpy(io_buffer_virt(&firmware_buffer), data, std::min(size, kFirmwareSize));
io_buffer_cache_flush(&firmware_buffer, 0, kFirmwareSize);
ImemDmaAdr::Get()
.FromValue(static_cast<uint32_t>(io_buffer_phys(&firmware_buffer)))
.WriteTo(dosbus_.get());
ImemDmaCount::Get()
.FromValue(kFirmwareSize / sizeof(uint32_t))
.WriteTo(dosbus_.get());
ImemDmaCtrl::Get().FromValue(0x8000 | (7 << 16)).WriteTo(dosbus_.get());
if (!WaitForRegister(std::chrono::milliseconds(100), [this] {
return (ImemDmaCtrl::Get().ReadFrom(dosbus_.get()).reg_value() &
0x8000) == 0;
})) {
DECODE_ERROR("Failed to load microcode.");
return ZX_ERR_TIMED_OUT;
}
io_buffer_release(&firmware_buffer);
return ZX_OK;
}
zx_status_t AmlogicVideo::InitializeStreamBuffer(bool use_parser) {
zx_status_t status = io_buffer_init_aligned(
&stream_buffer_, bti_.get(), kStreamBufferSize, kBufferAlignShift,
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_, 0, kStreamBufferSize);
VldMemVififoControl::Get().FromValue(0).WriteTo(dosbus_.get());
VldMemVififoWrapCount::Get().FromValue(0).WriteTo(dosbus_.get());
DosSwReset0::Get().FromValue(1 << 4).WriteTo(dosbus_.get());
DosSwReset0::Get().FromValue(0).WriteTo(dosbus_.get());
Reset0Register::Get().ReadFrom(reset_.get());
PowerCtlVld::Get().FromValue(1 << 4).WriteTo(dosbus_.get());
uint32_t buffer_address =
static_cast<uint32_t>(io_buffer_phys(&stream_buffer_));
VldMemVififoStartPtr::Get().FromValue(buffer_address).WriteTo(dosbus_.get());
VldMemVififoCurrPtr::Get().FromValue(buffer_address).WriteTo(dosbus_.get());
VldMemVififoEndPtr::Get()
.FromValue(buffer_address + kStreamBufferSize - 8)
.WriteTo(dosbus_.get());
VldMemVififoControl::Get().FromValue(0).set_init(true).WriteTo(dosbus_.get());
VldMemVififoControl::Get().FromValue(0).WriteTo(dosbus_.get());
VldMemVififoBufCntl::Get().FromValue(0).set_manual(true).WriteTo(
dosbus_.get());
VldMemVififoWP::Get().FromValue(buffer_address).WriteTo(dosbus_.get());
VldMemVififoBufCntl::Get()
.FromValue(0)
.set_manual(true)
.set_init(true)
.WriteTo(dosbus_.get());
VldMemVififoBufCntl::Get().FromValue(0).set_manual(true).WriteTo(
dosbus_.get());
auto fifo_control =
VldMemVififoControl::Get().FromValue(0).set_upper(0x11).set_fill_on_level(
true);
if (use_parser) {
fifo_control.set_fill_en(true).set_empty_en(true);
// Parser will do 64-bit endianness conversion.
fifo_control.set_endianness(0);
} else {
// Expect input to be in normal byte order.
fifo_control.set_endianness(7);
}
fifo_control.WriteTo(dosbus_.get());
return ZX_OK;
}
zx_status_t AmlogicVideo::ConfigureCanvas(uint32_t id, uint32_t addr,
uint32_t width, uint32_t height,
uint32_t wrap, uint32_t blockmode) {
// TODO(ZX-2154): Use real canvas driver.
assert(width % 8 == 0);
assert(addr % 8 == 0);
DmcCavLutDatal::Get()
.FromValue(0)
.set_addr(addr >> 3)
.set_width_lower((width / 8) & 7)
.WriteTo(dmc_.get());
uint32_t endianness = 0x7; // 64-bit big-endian to little-endian conversion.
DmcCavLutDatah::Get()
.FromValue(0)
.set_width_upper((width / 8) >> 3)
.set_height(height)
.set_block_mode(blockmode)
.set_endianness(endianness)
.WriteTo(dmc_.get());
DmcCavLutAddr::Get().FromValue(0).set_wr_en(true).set_index(id).WriteTo(
dmc_.get());
// Wait for write to go through.
DmcCavLutDatah::Get().ReadFrom(dmc_.get());
return ZX_OK;
}
// 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_));
ParserVideoStartPtr::Get().FromValue(buffer_address).WriteTo(parser_.get());
ParserVideoEndPtr::Get()
.FromValue(buffer_address + kStreamBufferSize - 8)
.WriteTo(parser_.get());
ParserEsControl::Get()
.ReadFrom(parser_.get())
.set_video_manual_read_ptr_update(false)
.WriteTo(parser_.get());
VldMemVififoBufCntl::Get().FromValue(0).set_init(true).WriteTo(dosbus_.get());
VldMemVififoBufCntl::Get().FromValue(0).WriteTo(dosbus_.get());
DosGenCtrl0::Get().FromValue(0).WriteTo(dosbus_.get());
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.fetch_complete()) {
PfifoRdPtr::Get().FromValue(0).WriteTo(parser_.get());
PfifoWrPtr::Get().FromValue(0).WriteTo(parser_.get());
parser_finished_promise_.set_value();
}
}
});
ParserIntStatus::Get().FromValue(0xffff).WriteTo(parser_.get());
ParserIntEnable::Get().FromValue(0).set_host_en_fetch_complete(true).WriteTo(
parser_.get());
return ZX_OK;
}
void AmlogicVideo::InitializeDecoderInput() {
VldMemVififoBufCntl::Get()
.FromValue(0)
.set_init(true)
.set_manual(true)
.WriteTo(dosbus_.get());
VldMemVififoBufCntl::Get().FromValue(0).set_manual(true).WriteTo(
dosbus_.get());
}
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);
parser_finished_promise_ = std::promise<void>();
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());
auto future_status =
parser_finished_promise_.get_future().wait_for(std::chrono::seconds(1));
if (future_status != std::future_status::ready) {
DECODE_ERROR("Parser timed out\n");
ParserFetchCmd::Get().FromValue(0).WriteTo(parser_.get());
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) {
if (len > kStreamBufferSize) {
DECODE_ERROR("Video too large\n");
return ZX_ERR_OUT_OF_RANGE;
}
memcpy(io_buffer_virt(&stream_buffer_), data, len);
io_buffer_cache_flush(&stream_buffer_, 0, len);
VldMemVififoWP::Get()
.FromValue(len + io_buffer_phys(&stream_buffer_))
.WriteTo(dosbus_.get());
VldMemVififoControl::Get()
.ReadFrom(dosbus_.get())
.set_fill_en(true)
.set_empty_en(true)
.WriteTo(dosbus_.get());
return ZX_OK;
}
void AmlogicVideo::StartDecoding() {
// Delay to ensure previous writes have executed.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(dosbus_.get());
}
DosSwReset0::Get().FromValue((1 << 12) | (1 << 11)).WriteTo(dosbus_.get());
DosSwReset0::Get().FromValue(0).WriteTo(dosbus_.get());
// Delay to ensure previous writes have executed.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(dosbus_.get());
}
Mpsr::Get().FromValue(1).WriteTo(dosbus_.get());
decoding_started_ = true;
}
void AmlogicVideo::StopDecoding() {
if (!decoding_started_)
return;
decoding_started_ = false;
Mpsr::Get().FromValue(0).WriteTo(dosbus_.get());
Cpsr::Get().FromValue(0).WriteTo(dosbus_.get());
if (!WaitForRegister(std::chrono::milliseconds(100), [this] {
return (ImemDmaCtrl::Get().ReadFrom(dosbus_.get()).reg_value() &
0x8000) == 0;
})) {
DECODE_ERROR("Failed to wait for DMA completion");
return;
}
// Delay to ensure previous writes have executed.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(dosbus_.get());
}
DosSwReset0::Get().FromValue((1 << 12) | (1 << 11)).WriteTo(dosbus_.get());
DosSwReset0::Get().FromValue(0).WriteTo(dosbus_.get());
// Delay to ensure previous write have executed.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(dosbus_.get());
}
}
void AmlogicVideo::PowerDownDecoder() {
auto timeout = std::chrono::milliseconds(100);
if (!WaitForRegister(timeout, [this] {
return MdecPicDcStatus::Get().ReadFrom(dosbus_.get()).reg_value() == 0;
})) {
auto temp = MdecPicDcCtrl::Get().ReadFrom(dosbus_.get());
temp.set_reg_value(1 | temp.reg_value());
temp.WriteTo(dosbus_.get());
temp.set_reg_value(~1 & temp.reg_value());
temp.WriteTo(dosbus_.get());
for (uint32_t i = 0; i < 3; i++) {
MdecPicDcStatus::Get().ReadFrom(dosbus_.get());
}
}
if (!WaitForRegister(timeout, [this] {
return DblkStatus::Get().ReadFrom(dosbus_.get()).reg_value() == 0;
})) {
DblkCtrl::Get().FromValue(3).WriteTo(dosbus_.get());
DblkCtrl::Get().FromValue(0).WriteTo(dosbus_.get());
for (uint32_t i = 0; i < 3; i++) {
DblkStatus::Get().ReadFrom(dosbus_.get());
}
}
if (!WaitForRegister(timeout, [this] {
return McStatus0::Get().ReadFrom(dosbus_.get()).reg_value() == 0;
})) {
auto temp = McCtrl1::Get().ReadFrom(dosbus_.get());
temp.set_reg_value(0x9 | temp.reg_value());
temp.WriteTo(dosbus_.get());
temp.set_reg_value(~0x9 & temp.reg_value());
temp.WriteTo(dosbus_.get());
for (uint32_t i = 0; i < 3; i++) {
McStatus0::Get().ReadFrom(dosbus_.get());
}
}
WaitForRegister(timeout, [this] {
return !(DcacDmaCtrl::Get().ReadFrom(dosbus_.get()).reg_value() & 0x8000);
});
}
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;
}
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_, 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);
firmware_ = std::make_unique<FirmwareBlob>();
status = firmware_->LoadFirmware(parent_);
if (status != ZX_OK) {
DECODE_ERROR("Failed load firmware\n");
return status;
}
return ZX_OK;
}
zx_status_t AmlogicVideo::Bind() {
device_add_args_t vc_video_args = {};
vc_video_args.version = DEVICE_ADD_ARGS_VERSION;
vc_video_args.name = "amlogic_video";
vc_video_args.ctx = this;
vc_video_args.ops = &amlogic_video_device_ops;
zx_status_t status = device_add(parent_, &vc_video_args, &device_);
if (status != ZX_OK) {
DECODE_ERROR("Failed to bind device");
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
void AmlogicVideo::InitializeInterrupts() {
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);
if (status == ZX_ERR_BAD_STATE) {
// TODO(dustingreen): We should be able to remove this after fix for
// ZX-2268. Currently this is potentially useful to repro for
// ZX-2268.
DECODE_ERROR("status == ZX_ERR_BAD_STATE - trying to continue\n");
continue;
}
return;
}
video_decoder_->HandleInterrupt();
}
});
}
zx_status_t AmlogicVideo::InitDecoder() {
EnableVideoPower();
zx_status_t status = InitializeStreamBuffer(true);
if (status != ZX_OK)
return status;
InitializeInterrupts();
video_decoder_ = std::make_unique<Mpeg12Decoder>(this);
return video_decoder_->Initialize();
}
extern zx_status_t amlogic_video_init(void** out_ctx) {
DriverCtx* driver_ctx = new DriverCtx();
*out_ctx = reinterpret_cast<void*>(driver_ctx);
return ZX_OK;
}
// ctx is the driver ctx (not device ctx)
zx_status_t amlogic_video_bind(void* ctx, zx_device_t* parent) {
#if ENABLE_DECODER_TESTS
TestSupport::set_parent_device(parent);
TestSupport::RunAllTests();
#endif
DriverCtx* driver = reinterpret_cast<DriverCtx*>(ctx);
std::unique_ptr<DeviceCtx> device = std::make_unique<DeviceCtx>(driver);
AmlogicVideo* video = device->video();
zx_status_t status = video->InitRegisters(parent);
if (status != ZX_OK) {
DECODE_ERROR("Failed to initialize registers");
return status;
}
status = video->InitDecoder();
if (status != ZX_OK) {
DECODE_ERROR("Failed to initialize decoder");
return status;
}
status = video->Bind();
if (status != ZX_OK) {
DECODE_ERROR("Failed to bind device");
return status;
}
// The pointer to DeviceCtx is add_device() ctx now, so intentionally don't
// destruct the DeviceCtx instance.
//
// At least for now, the DeviceCtx stays allocated for the life of the
// devhost process.
device.release();
zxlogf(INFO, "[amlogic_video_bind] bound\n");
return ZX_OK;
}