blob: 503e7f4ba85f9694d71a8f8b5f36da257d00bfdc [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gdc.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include <hw/reg.h>
#include <stdint.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <memory>
#include "gdc-regs.h"
namespace gdc {
namespace {
constexpr uint32_t kHiu = 0;
constexpr uint32_t kGdc = 1;
} // namespace
void GdcDevice::InitClocks() {
// First reset the clocks.
GdcClkCntl::Get()
.ReadFrom(&clock_mmio_)
.reset_axi()
.reset_core()
.WriteTo(&clock_mmio_);
// Set the clocks to 8Mhz
// Source XTAL
// Clock divisor = 3
GdcClkCntl::Get()
.ReadFrom(&clock_mmio_)
.set_axi_clk_div(3)
.set_axi_clk_en(1)
.set_axi_clk_sel(0)
.set_core_clk_div(3)
.set_core_clk_en(1)
.set_core_clk_sel(0)
.WriteTo(&clock_mmio_);
// Enable GDC Power domain.
GdcMemPowerDomain::Get()
.ReadFrom(&clock_mmio_)
.set_gdc_pd(0)
.WriteTo(&clock_mmio_);
}
zx_status_t GdcDevice::GdcInitTask(
const buffer_collection_info_t* input_buffer_collection,
const buffer_collection_info_t* output_buffer_collection,
zx::vmo config_vmo, const gdc_callback_t* callback,
uint32_t* out_task_index) {
if (out_task_index == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
std::unique_ptr<Task> task;
zx_status_t status =
gdc::Task::Create(input_buffer_collection, output_buffer_collection,
config_vmo, callback, bti_, &task);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: Task Creation Failed %d\n", __func__, status);
return status;
}
// Put an entry in the hashmap.
task_map_[next_task_index_] = std::move(task);
*out_task_index = next_task_index_;
next_task_index_++;
return ZX_OK;
}
void GdcDevice::ProcessTask(TaskInfo& info) {
auto task = info.task;
auto input_buffer_index = info.input_buffer_index;
// TODO(CAM-33): Add Processing of the frame implementation here.
zx_port_packet_t packet;
ZX_ASSERT(ZX_OK == WaitForInterrupt(&packet));
gdc_irq_.ack();
// Currently there is only type of event coming in at this port.
// We could possibly add more, like one to terminate the process.
if (packet.key == kPortKeyIrqMsg) {
// Invoke the callback function and tell about the output buffer index
// which is ready to be used.
// TODO(CAM-33): pass actual output buffer index instead of
// input_buffer_index.
task->callback()->frame_ready(task->callback()->ctx, input_buffer_index);
}
}
int GdcDevice::FrameProcessingThread() {
FX_LOGF(INFO, "", "%s: start \n", __func__);
while (running_.load()) {
// Waiting for the event when a task is queued.
sync_completion_wait(&frame_processing_signal_, ZX_TIME_INFINITE);
bool pending_task = false;
// Dequeing the entire deque till it drains.
do {
TaskInfo info;
{
fbl::AutoLock lock(&deque_lock_);
if (!processing_queue_.empty()) {
info = processing_queue_.back();
processing_queue_.pop_back();
pending_task = true;
} else {
pending_task = false;
}
}
if (pending_task) {
ProcessTask(info);
}
} while (pending_task);
// Now that the deque is drained we reset the signal.
sync_completion_reset(&frame_processing_signal_);
}
return 0;
}
zx_status_t GdcDevice::GdcProcessFrame(uint32_t task_index,
uint32_t input_buffer_index) {
// 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.task = task_entry->second.get();
info.input_buffer_index = input_buffer_index;
// Put the task on queue.
fbl::AutoLock lock(&deque_lock_);
processing_queue_.push_front(std::move(info));
sync_completion_signal(&frame_processing_signal_);
return ZX_OK;
}
zx_status_t GdcDevice::StartThread() {
running_.store(true);
return thrd_status_to_zx_status(thrd_create_with_name(
&processing_thread_,
[](void* arg) -> int {
return reinterpret_cast<GdcDevice*>(arg)->FrameProcessingThread();
},
this, "gdc-processing-thread"));
}
zx_status_t GdcDevice::StopThread() {
running_.store(false);
// Signal the thread waiting on this signal
sync_completion_signal(&frame_processing_signal_);
gdc_irq_.destroy();
JoinThread();
return ZX_OK;
}
zx_status_t GdcDevice::WaitForInterrupt(zx_port_packet_t* packet) {
return port_.wait(zx::time::infinite(), packet);
}
void GdcDevice::GdcRemoveTask(uint32_t task_index) {
// Find the entry in hashmap.
auto task_entry = task_map_.find(task_index);
ZX_ASSERT(task_entry != task_map_.end());
// Remove map entry.
task_map_.erase(task_entry);
}
void GdcDevice::GdcReleaseFrame(uint32_t task_index, uint32_t buffer_index) {
// TODO(CAM-33): Implement this.
}
// static
zx_status_t GdcDevice::Setup(void* ctx, zx_device_t* parent,
std::unique_ptr<GdcDevice>* out) {
ddk::PDev pdev(parent);
if (!pdev.is_valid()) {
FX_LOGF(ERROR, "", "%s: ZX_PROTOCOL_PDEV not available\n", __func__);
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> clk_mmio;
zx_status_t status = pdev.MapMmio(kHiu, &clk_mmio);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
std::optional<ddk::MmioBuffer> gdc_mmio;
status = pdev.MapMmio(kGdc, &gdc_mmio);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
zx::interrupt gdc_irq;
status = pdev.GetInterrupt(0, &gdc_irq);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: pdev_.GetInterrupt failed %d\n", __func__, status);
return status;
}
zx::port port;
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: port create failed %d\n", __func__, status);
return status;
}
status = gdc_irq.bind(port, kPortKeyIrqMsg, 0 /*options*/);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: interrupt bind failed %d\n", __func__, status);
return status;
}
zx::bti bti;
status = pdev.GetBti(0, &bti);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: could not obtain bti: %d\n", __func__, status);
return status;
}
fbl::AllocChecker ac;
auto gdc_device = std::unique_ptr<GdcDevice>(
new (&ac) GdcDevice(parent, std::move(*clk_mmio), std::move(*gdc_mmio),
std::move(gdc_irq), std::move(bti), std::move(port)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
gdc_device->InitClocks();
status = gdc_device->StartThread();
*out = std::move(gdc_device);
return status;
}
void GdcDevice::DdkUnbind() {
ShutDown();
DdkRemove();
}
void GdcDevice::DdkRelease() {
StopThread();
delete this;
}
void GdcDevice::ShutDown() {}
zx_status_t GdcBind(void* ctx, zx_device_t* device) {
std::unique_ptr<GdcDevice> gdc_device;
zx_status_t status = gdc::GdcDevice::Setup(ctx, device, &gdc_device);
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: Could not setup gdc device: %d\n", __func__, status);
return status;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_PROTO, 0, ZX_PROTOCOL_GDC},
};
// Run the unit tests for this device
// TODO(braval): CAM-44 (Run only when build flag enabled)
// This needs to be replaced with run unittests hooks when
// the framework is available.
#if 0
status = gdc::GdcDeviceTester::RunTests(gdc_device.get());
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: Device Unit Tests Failed \n", __func__);
return status;
}
#endif
status = gdc_device->DdkAdd("gdc", 0, props, countof(props));
if (status != ZX_OK) {
FX_LOGF(ERROR, "%s: Could not add gdc device: %d\n", __func__, status);
return status;
} else {
FX_LOGF(INFO, "", "%s: gdc driver added\n", __func__);
}
// gdc device intentionally leaked as it is now held by DevMgr.
__UNUSED auto* dev = gdc_device.release();
return status;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = GdcBind;
return ops;
}();
} // namespace gdc
// clang-format off
ZIRCON_DRIVER_BEGIN(gdc, gdc::driver_ops, "gdc", "0.1", 3)
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_ARM),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GDC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_ARM_MALI_IV010),
ZIRCON_DRIVER_END(gdc)