blob: 3c7f4ea921bf06bb192284b7e1f11abf48eb261c [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 "arm-isp.h"
#include <lib/syslog/global.h>
#include <stdlib.h>
#include <unistd.h>
#include <zircon/types.h>
#include <memory>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/metadata.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include "arm-isp-regs.h"
namespace camera {
namespace {
constexpr uint32_t kHiu = 0;
constexpr uint32_t kPowerDomain = 1;
constexpr uint32_t kMemoryDomain = 2;
constexpr uint32_t kReset = 3;
constexpr uint32_t kIsp = 4;
// CLK Shifts & Masks
constexpr uint32_t kClkMuxMask = 0xfff;
constexpr uint32_t kClockEnableShift = 8;
constexpr uint8_t kPing = 0;
constexpr uint8_t kPong = 1;
constexpr uint8_t kCopyToIsp = 0;
constexpr uint8_t kCopyFromIsp = 1;
constexpr uint8_t kSafeStop = 0;
constexpr uint8_t kSafeStart = 1;
enum {
COMPONENT_PDEV,
COMPONENT_CAMERA_SENSOR,
COMPONENT_COUNT,
};
} // namespace
void ArmIspDevice::IspHWReset(bool reset) {
if (reset) {
reset_mmio_.ClearBits32(1 << 1, RESET4_LEVEL);
} else {
reset_mmio_.SetBits32(1 << 1, RESET4_LEVEL);
}
// Reference code has a sleep in this path.
// TODO(braval@) Double check to look into if
// this sleep is really necessary.
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
}
void ArmIspDevice::PowerUpIsp() {
// set bit[18-19]=0
// TODO(braval@) Double check to look into if
// this sleep is really necessary.
power_mmio_.ClearBits32(1 << 18 | 1 << 19, AO_RTI_GEN_PWR_SLEEP0);
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
// set bit[18-19]=0
power_mmio_.ClearBits32(1 << 18 | 1 << 19, AO_RTI_GEN_PWR_ISO0);
// MEM_PD_REG0 set 0
memory_pd_mmio_.Write32(0, HHI_ISP_MEM_PD_REG0);
// MEM_PD_REG1 set 0
memory_pd_mmio_.Write32(0, HHI_ISP_MEM_PD_REG1);
// Refer to reference source code
hiu_mmio_.Write32(0x5b446585, HHI_CSI_PHY_CNTL0);
hiu_mmio_.Write32(0x803f4321, HHI_CSI_PHY_CNTL1);
// Setup Clocks.
// clear existing values
hiu_mmio_.ClearBits32(kClkMuxMask, HHI_MIPI_ISP_CLK_CNTL);
// set the divisor = 1 (writing (1-1) to div field)
// source for the unused mux = S905D2_FCLK_DIV3 = 3 // 666.7 MHz
hiu_mmio_.SetBits32(((1 << kClockEnableShift) | 4 << 9), HHI_MIPI_ISP_CLK_CNTL);
}
void ArmIspDevice::HandleDmaError() {
auto global_mon_status = IspGlobalMonitor_Status::Get().ReadFrom(&isp_mmio_);
auto global_mon_failures = IspGlobalMonitor_Failures::Get().ReadFrom(&isp_mmio_);
global_mon_status.Print();
global_mon_failures.Print();
IspGlobalMonitor_ClearError::Get()
.ReadFrom(&isp_mmio_)
.set_output_dma_clr_alarm(1)
.set_temper_dma_clr_alarm(1)
.WriteTo(&isp_mmio_);
IspGlobalMonitor_ClearError::Get()
.ReadFrom(&isp_mmio_)
.set_output_dma_clr_alarm(1)
.WriteTo(&isp_mmio_);
// Now read the alarms:
global_mon_status = IspGlobalMonitor_Status::Get().ReadFrom(&isp_mmio_);
global_mon_failures = IspGlobalMonitor_Failures::Get().ReadFrom(&isp_mmio_);
global_mon_status.Print();
global_mon_failures.Print();
zxlogf(INFO, "DMA Writer statuses:\n");
full_resolution_dma_->PrintStatus(&isp_mmio_);
downscaled_dma_->PrintStatus(&isp_mmio_);
zxlogf(INFO, "Clearing dma alarm\n");
IspGlobalMonitor_ClearError::Get()
.ReadFrom(&isp_mmio_)
.set_output_dma_clr_alarm(0)
.set_temper_dma_clr_alarm(0)
.WriteTo(&isp_mmio_);
IspGlobalMonitor_ClearError::Get()
.ReadFrom(&isp_mmio_)
.set_output_dma_clr_alarm(0)
.WriteTo(&isp_mmio_);
}
zx_status_t ArmIspDevice::ErrorRoutine() {
// Mask all IRQs
IspGlobalInterrupt_MaskVector::Get().ReadFrom(&isp_mmio_).mask_all().WriteTo(&isp_mmio_);
zx_status_t status = SetPort(kSafeStop);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Stopping ISP failed \n", __func__);
return status;
}
IspGlobal_Config0::Get().ReadFrom(&isp_mmio_).set_global_fsm_reset(1).WriteTo(&isp_mmio_);
IspGlobal_Config0::Get().ReadFrom(&isp_mmio_).set_global_fsm_reset(0).WriteTo(&isp_mmio_);
IspGlobalInterrupt_MaskVector::Get()
.ReadFrom(&isp_mmio_)
.set_isp_start(0)
.set_ctx_management_error(0)
.set_broken_frame_error(0)
.set_wdg_timer_timed_out(0)
.set_frame_collision_error(0)
.set_dma_error_interrupt(0)
.set_fr_y_dma_write_done(0)
.set_fr_uv_dma_write_done(0)
.set_ds_y_dma_write_done(0)
.set_ds_uv_dma_write_done(0)
.WriteTo(&isp_mmio_);
status = SetPort(kSafeStart);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Starting ISP failed \n", __func__);
return status;
}
return status;
}
// Interrupt handler for the ISP.
int ArmIspDevice::IspIrqHandler() {
zxlogf(INFO, "%s start\n", __func__);
zx_status_t status = ZX_OK;
while (running_.load()) {
zx::time time;
status = isp_irq_.wait(&time);
if (status != ZX_OK) {
return status;
}
auto irq_status = IspGlobalInterrupt_StatusVector::Get().ReadFrom(&isp_mmio_);
IspGlobalInterrupt_ClearVector::Get()
.ReadFrom(&isp_mmio_)
.set_reg_value(0xFFFFFFFF)
.WriteTo(&isp_mmio_);
// Clear IRQ Vector
IspGlobalInterrupt_Clear::Get().ReadFrom(&isp_mmio_).set_value(0).WriteTo(&isp_mmio_);
IspGlobalInterrupt_Clear::Get().ReadFrom(&isp_mmio_).set_value(1).WriteTo(&isp_mmio_);
IspGlobalInterrupt_Clear::Get().ReadFrom(&isp_mmio_).set_value(0).WriteTo(&isp_mmio_);
if (irq_status.has_errors()) {
zxlogf(ERROR, "%s ISP Error Occured, resetting ISP\n", __func__);
if (irq_status.dma_error_interrupt()) {
HandleDmaError();
} else {
status = ErrorRoutine();
if (status != ZX_OK) {
return status;
}
}
continue;
}
// Currently only handling Frame Start Interrupt.
if (irq_status.isp_start()) {
// Frame Start Interrupt
auto current_config = IspGlobal_Config4::Get().ReadFrom(&isp_mmio_);
if (current_config.is_pong()) {
// Use PING for next frame
IspGlobal_Config3::Get().ReadFrom(&isp_mmio_).select_config_ping().WriteTo(&isp_mmio_);
if (IsFrameProcessingInProgress()) {
// TODO: (braval): Handle dropped frame
} else {
// Copy Config from local memory to ISP PING Config space
CopyContextInfo(kPing, kCopyToIsp);
// Copy Metering Info from ISP to Local Memory
CopyMeteringInfo(kPing);
// Start processing this new frame.
sync_completion_signal(&frame_processing_signal_);
}
} else {
// CURRENT CONFIG IS PING
// Use PONG for next frame
IspGlobal_Config3::Get().ReadFrom(&isp_mmio_).select_config_pong().WriteTo(&isp_mmio_);
if (IsFrameProcessingInProgress()) {
// TODO: (braval): Handle dropped frame
} else {
// Copy Config from local memory to ISP PING Config space
CopyContextInfo(kPong, kCopyToIsp);
// Copy Metering Info from ISP to Local Memory
CopyMeteringInfo(kPong);
// Start processing this new frame.
sync_completion_signal(&frame_processing_signal_);
}
}
}
}
return status;
}
bool ArmIspDevice::IsFrameProcessingInProgress() {
return sync_completion_signaled(&frame_processing_signal_);
}
// Note: We have only one copy of local config and
// metering info, so assign the correct device_offset
// depending if it is PING or PONG context
// before we copy the data to/from the ISP.
void ArmIspDevice::CopyContextInfo(uint8_t config_space, uint8_t direction) {
zx_off_t device_offset;
if (config_space == kPing) {
device_offset = kDecompander0PingOffset;
} else {
// PONG Context
device_offset = kDecompander0PongOffset;
}
if (direction == kCopyToIsp) {
// Copy to ISP from Local Config Buffer
isp_mmio_.CopyFrom32(isp_mmio_local_, kDecompander0PingOffset, device_offset, kConfigSize / 4);
} else {
// Copy from ISP to Local Config Buffer
isp_mmio_local_.CopyFrom32(isp_mmio_, device_offset, kDecompander0PingOffset, kConfigSize / 4);
}
}
void ArmIspDevice::CopyMeteringInfo(uint8_t config_space) {
zx_off_t device_offset;
if (config_space == kPing) {
// PING Context
device_offset = kPingMeteringStatsOffset;
} else {
// PONG Context
device_offset = kPongMeteringStatsOffset;
}
// Copy from ISP to Local Config Buffer
isp_mmio_local_.CopyFrom32(isp_mmio_, kAexpHistStatsOffset, kAexpHistStatsOffset, kHistSize / 4);
isp_mmio_local_.CopyFrom32(isp_mmio_, device_offset, kPingMeteringStatsOffset, kMeteringSize / 4);
}
zx_status_t ArmIspDevice::IspContextInit() {
// This is actually writing to the HW
IspLoadSeq_settings();
// This is being written to the local_config_buffer_
IspLoadSeq_settings_context();
statsMgr_ = camera::StatsManager::Create(isp_mmio_.View(0), isp_mmio_local_, camera_sensor_);
if (statsMgr_ == nullptr) {
zxlogf(ERROR, "%s: Unable to start StatsManager \n", __func__);
return ZX_ERR_NO_MEMORY;
}
// We are setting up assuming kWDR_MODE_LINEAR as default mode
IspLoadSeq_linear();
// Call custom_init()
IspLoadCustomSequence();
// Initialize Gamma.
gamma_rgb_fr_regs_.Init();
gamma_rgb_ds_regs_.Init();
// Input port safe start
return SetPort(kSafeStart);
}
ArmIspRegisterDump ArmIspDevice::DumpRegisters() {
ArmIspRegisterDump dump;
// First dump the global registers:
for (size_t i = 0; i < kGlobalConfigSize; i++) {
dump.global_config[i] = isp_mmio_.Read<uint32_t>(4 * i);
}
// Then ping and pong:
for (size_t i = 0; i < kContextConfigSize; i++) {
dump.ping_config[i] = isp_mmio_.Read<uint32_t>(kPingContextConfigOffset + 4 * i);
dump.pong_config[i] = isp_mmio_.Read<uint32_t>(kPongContextConfigOffset + 4 * i);
}
return dump;
}
zx_status_t ArmIspDevice::InitIsp() {
// The ISP and MIPI module is in same power domain.
// So if we don't call the power sequence of ISP, the mipi module
// won't work and it will block accesses to the mipi register block.
PowerUpIsp();
IspHWReset(true);
// Start ISP Interrupt Handling Thread.
auto start_thread = [](void* arg) -> int {
return static_cast<ArmIspDevice*>(arg)->IspIrqHandler();
};
sync_completion_reset(&frame_processing_signal_);
running_.store(true);
int rc = thrd_create_with_name(&irq_thread_, start_thread, this, "isp_irq_thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
// Start frame processing thread
auto frame_processing_func = [](void* arg) -> int {
return static_cast<ArmIspDevice*>(arg)->FrameProcessingThread();
};
running_frame_processing_.store(true);
rc = thrd_create_with_name(&frame_processing_thread_, frame_processing_func, this,
"frame_processing thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
IspHWReset(false);
// validate the ISP product ID
if (Id_Product::Get().ReadFrom(&isp_mmio_).value() != PRODUCT_ID_DEFAULT) {
zxlogf(ERROR, "%s: Unknown product ID\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status = DmaManager::Create(bti_, isp_mmio_local_, DmaManager::Stream::FullResolution,
&full_resolution_dma_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Unable to start Full Resolution DMA Module \n", __func__);
return status;
}
status =
DmaManager::Create(bti_, isp_mmio_local_, DmaManager::Stream::Downscaled, &downscaled_dma_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Unable to start Downscaled DMA Module \n", __func__);
return status;
}
return ZX_OK;
}
zx_status_t ArmIspDevice::SetupIspConfig() {
// Mask all IRQs
IspGlobalInterrupt_MaskVector::Get().ReadFrom(&isp_mmio_).mask_all().WriteTo(&isp_mmio_);
// Now copy all ping config settings & metering settings and store it.
CopyContextInfo(kPing, kCopyFromIsp);
zx_status_t status = IspContextInit();
if (status != ZX_OK) {
zxlogf(ERROR, "%s: IspContextInit failed %d\n", __func__, status);
return status;
}
// Copy current context to ISP
CopyContextInfo(kPing, kCopyToIsp);
CopyContextInfo(kPong, kCopyToIsp);
while (IspGlobalInterrupt_StatusVector::Get().ReadFrom(&isp_mmio_).reg_value()) {
// driver is initialized. we can start processing interrupts
// wait until irq mask is cleared and start processing
IspGlobalInterrupt_Clear::Get().ReadFrom(&isp_mmio_).set_value(0).WriteTo(&isp_mmio_);
IspGlobalInterrupt_Clear::Get().ReadFrom(&isp_mmio_).set_value(1).WriteTo(&isp_mmio_);
}
IspGlobalInterrupt_MaskVector::Get()
.ReadFrom(&isp_mmio_)
.set_isp_start(0)
.set_ctx_management_error(0)
.set_broken_frame_error(0)
.set_wdg_timer_timed_out(0)
.set_frame_collision_error(0)
.set_dma_error_interrupt(0)
.set_fr_y_dma_write_done(0)
.set_fr_uv_dma_write_done(0)
.set_ds_y_dma_write_done(0)
.set_ds_uv_dma_write_done(0)
.WriteTo(&isp_mmio_);
// put ping pong in slave mode
// SW only mode
IspGlobal_Config3::Get()
.ReadFrom(&isp_mmio_)
.set_mcu_override_config_select(1)
.WriteTo(&isp_mmio_);
return ZX_OK;
}
zx_status_t ArmIspDevice::SetPort(uint8_t kMode) {
constexpr uint32_t kTimeout = ZX_MSEC(30);
constexpr uint32_t kDeadline = ZX_USEC(500);
// Input port safe stop or stop
InputPort_Config3::Get().ReadFrom(&isp_mmio_).set_mode_request(kMode).WriteTo(&isp_mmio_);
// timeout 100ms
zx_time_t deadline = zx_deadline_after(kTimeout);
do {
if (InputPort_ModeStatus::Get().ReadFrom(&isp_mmio_).value() == kMode) {
return ZX_OK;
}
zx_nanosleep(zx_deadline_after(kDeadline));
} while (zx_clock_get_monotonic() < deadline);
return ZX_ERR_TIMED_OUT;
}
// static
zx_status_t ArmIspDevice::Init(void** ctx_out) {
ZX_ASSERT(ctx_out);
*ctx_out = nullptr;
fx_logger_config_t config{};
config.min_severity = FX_LOG_INFO;
config.console_fd = STDERR_FILENO;
return fx_log_init_with_config(&config); // Used by submodules
}
// static
zx_status_t ArmIspDevice::Create(void* ctx, zx_device_t* parent) {
ddk::CompositeProtocolClient composite(parent);
if (!composite.is_valid()) {
zxlogf(ERROR, "%s could not get composite protocoln", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
zx_device_t* components[COMPONENT_COUNT];
size_t actual;
composite.GetComponents(components, COMPONENT_COUNT, &actual);
if (actual != COMPONENT_COUNT) {
zxlogf(ERROR, "%s Could not get components\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
ddk::PDev pdev(components[COMPONENT_PDEV]);
if (!pdev.is_valid()) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available\n", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
ddk::CameraSensorProtocolClient camera_sensor(components[COMPONENT_CAMERA_SENSOR]);
if (!camera_sensor.is_valid()) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_CAMERA_SENSOR not available\n", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> hiu_mmio;
zx_status_t status = pdev.MapMmio(kHiu, &hiu_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
std::optional<ddk::MmioBuffer> power_mmio;
status = pdev.MapMmio(kPowerDomain, &power_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
std::optional<ddk::MmioBuffer> memory_pd_mmio;
status = pdev.MapMmio(kMemoryDomain, &memory_pd_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
std::optional<ddk::MmioBuffer> reset_mmio;
status = pdev.MapMmio(kReset, &reset_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
std::optional<ddk::MmioBuffer> isp_mmio;
status = pdev.MapMmio(kIsp, &isp_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d\n", __func__, status);
return status;
}
zx::interrupt isp_irq;
status = pdev.GetInterrupt(0, &isp_irq);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.GetInterrupt failed %d\n", __func__, status);
return status;
}
// Get our bti.
zx::bti bti;
status = pdev.GetBti(0, &bti);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not obtain bti: %d\n", __func__, status);
return status;
}
// Allocate buffers for ISP SW configuration and metering information.
fbl::AllocChecker ac;
mmio_buffer_t local_mmio_buffer;
local_mmio_buffer.vaddr =
new (static_cast<std::align_val_t>(alignof(uint32_t)), &ac) char[kLocalBufferSize];
local_mmio_buffer.size = kLocalBufferSize;
local_mmio_buffer.vmo = ZX_HANDLE_INVALID;
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto isp_device = std::unique_ptr<ArmIspDevice>(new (&ac) ArmIspDevice(
parent, std::move(*hiu_mmio), std::move(*power_mmio), std::move(*memory_pd_mmio),
std::move(*reset_mmio), std::move(*isp_mmio), local_mmio_buffer, std::move(isp_irq),
std::move(bti), components[COMPONENT_CAMERA_SENSOR]));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = isp_device->InitIsp();
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to Initialize ISP\n", __func__);
return status;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_PROTO, 0, ZX_PROTOCOL_ISP},
};
status = isp_device->DdkAdd("arm-isp", 0, props, countof(props));
if (status != ZX_OK) {
zxlogf(ERROR, "arm-isp: Could not create arm-isp device: %d\n", status);
return status;
} else {
zxlogf(INFO, "arm-isp: Added arm-isp device\n");
}
// TODO(garratt): Enable this only under test.
// Hold the unbind lock so we do not become unbound while the
// ArmIspDeviceTester is being created:
fbl::AutoLock guard(&isp_device->unbind_lock_);
status = ArmIspDeviceTester::Create(isp_device.get(), &(isp_device->on_isp_unbind_));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to create ISP Tester\n", __func__);
return status;
}
// isp_device intentionally leaked as it is now held by DevMgr.
__UNUSED auto ptr = isp_device.release();
return status;
}
DmaManager* ArmIspDevice::GetStream(stream_type_t type) {
// Ignore anything that is not FullFrame or Downscaled:
switch (type) {
case STREAM_TYPE_FULL_RESOLUTION:
return full_resolution_dma_.get();
case STREAM_TYPE_DOWNSCALED:
return downscaled_dma_.get();
default:
zxlogf(ERROR, "%s: Invalid stream type\n", __func__);
}
return nullptr;
}
// Functions that the isp_stream_protocol calls:
zx_status_t ArmIspDevice::ReleaseFrame(uint32_t buffer_id, stream_type_t type) {
auto stream = GetStream(type);
if (!stream) {
return ZX_ERR_INVALID_ARGS;
}
return stream->ReleaseFrame(buffer_id);
}
// A call to either stream type to start will get the isp to start running.
zx_status_t ArmIspDevice::StartStream(stream_type_t type) {
auto stream = GetStream(type);
if (!stream) {
return ZX_ERR_INVALID_ARGS;
}
stream->Enable();
if (!streaming_) {
auto status = StartStreaming();
if (status == ZX_OK) {
streaming_ = true;
}
return status;
}
return ZX_OK;
}
// The ISP will only stop streaming when both streams are stopped.
zx_status_t ArmIspDevice::StopStream(stream_type_t type) {
auto stream = GetStream(type);
if (!stream) {
return ZX_ERR_INVALID_ARGS;
}
stream->Disable();
if (streaming_ && !full_resolution_dma_->enabled() && !downscaled_dma_->enabled()) {
auto status = StopStreaming();
if (status == ZX_OK) {
streaming_ = false;
}
return status;
}
return ZX_OK;
}
zx_status_t ArmIspDevice::StartStreaming() {
if (streaming_) {
return ZX_OK;
}
// At reset we use PING config
IspGlobal_Config3::Get().ReadFrom(&isp_mmio_).select_config_ping().WriteTo(&isp_mmio_);
// Grab a new frame for whichever dma is streaming:
downscaled_dma_->OnNewFrame();
full_resolution_dma_->OnNewFrame();
// Copy current context to ISP
CopyContextInfo(kPing, kCopyToIsp);
// TODO(Garratt): Test if we need to load pong configuration now.
full_resolution_dma_->OnNewFrame();
downscaled_dma_->OnNewFrame();
CopyContextInfo(kPong, kCopyToIsp);
zx_status_t status = SetPort(kSafeStart);
if (status != ZX_OK) {
return status;
}
statsMgr_->SensorStartStreaming();
streaming_ = true;
return ZX_OK;
}
zx_status_t ArmIspDevice::StopStreaming() {
if (!streaming_) {
return ZX_OK;
}
statsMgr_->SensorStopStreaming();
zx_status_t status = SetPort(kSafeStop);
if (status != ZX_OK) {
return status;
}
streaming_ = false;
return ZX_OK;
}
zx_status_t ArmIspDevice::IspCreateOutputStream(const buffer_collection_info_t* buffer_collection,
const frame_rate_t* rate, stream_type_t type,
const output_stream_callback_t* stream,
output_stream_protocol_t* out_s) {
ZX_ASSERT(stream && stream->frame_ready);
if (!IsIspConfigInitialized()) {
zx_status_t status = SetupIspConfig();
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to Setup ISP\n", __func__);
return status;
}
initialized_ = true;
}
// TODO(CAM-79): Set frame rate in sensor
auto frame_ready_callback = [stream = *stream](fuchsia_camera_common_FrameAvailableEvent event) {
// TODO(CAM-80): change the output_stream_callback_t so it uses all the
// frame available info
if (event.frame_status == fuchsia_camera_common_FrameStatus_OK) {
stream.frame_ready(stream.ctx, event.buffer_id);
}
};
// Set the control interface:
out_s->ctx = this;
switch (type) {
case STREAM_TYPE_FULL_RESOLUTION:
out_s->ops->start = [](void* ctx) {
return reinterpret_cast<ArmIspDevice*>(ctx)->StartStream(STREAM_TYPE_FULL_RESOLUTION);
};
out_s->ops->stop = [](void* ctx) {
return reinterpret_cast<ArmIspDevice*>(ctx)->StopStream(STREAM_TYPE_FULL_RESOLUTION);
};
out_s->ops->release_frame = [](void* ctx, uint32_t buffer_id) {
return reinterpret_cast<ArmIspDevice*>(ctx)->ReleaseFrame(buffer_id,
STREAM_TYPE_FULL_RESOLUTION);
};
return full_resolution_dma_->Configure(*buffer_collection, frame_ready_callback);
case STREAM_TYPE_DOWNSCALED:
out_s->ops->start = [](void* ctx) {
return reinterpret_cast<ArmIspDevice*>(ctx)->StartStream(STREAM_TYPE_DOWNSCALED);
};
out_s->ops->stop = [](void* ctx) {
return reinterpret_cast<ArmIspDevice*>(ctx)->StopStream(STREAM_TYPE_DOWNSCALED);
};
out_s->ops->release_frame = [](void* ctx, uint32_t buffer_id) {
return reinterpret_cast<ArmIspDevice*>(ctx)->ReleaseFrame(buffer_id,
STREAM_TYPE_DOWNSCALED);
};
return downscaled_dma_->Configure(*buffer_collection, frame_ready_callback);
case STREAM_TYPE_SCALAR:
return ZX_ERR_NOT_SUPPORTED;
case STREAM_TYPE_INVALID:
default:
return ZX_ERR_INVALID_ARGS;
}
return ZX_ERR_INVALID_ARGS;
}
int ArmIspDevice::FrameProcessingThread() {
bool fr_frame_written = false;
while (running_frame_processing_.load()) {
sync_completion_wait(&frame_processing_signal_, ZX_TIME_INFINITE);
// Currently this is called only on the new frame signal, so we maintain
// a variable to tell if we need to finish processing the last frame.
if (first_frame_) {
first_frame_ = false;
} else {
// Each of these calls has it's own interrupt, that it could be
// attached to:
if (fr_frame_written) {
full_resolution_dma_->OnFrameWritten();
}
downscaled_dma_->OnFrameWritten();
}
// Now for the actions we should take on new frame:
fr_frame_written = full_resolution_dma_->OnNewFrame();
downscaled_dma_->OnNewFrame();
// Reset the signal
sync_completion_reset(&frame_processing_signal_);
}
return ZX_OK;
}
ArmIspDevice::~ArmIspDevice() {
free(isp_mmio_local_.get());
running_.store(false);
running_frame_processing_.store(false);
thrd_join(irq_thread_, NULL);
thrd_join(frame_processing_thread_, NULL);
isp_irq_.destroy();
}
void ArmIspDevice::DdkUnbindNew(ddk::UnbindTxn txn) {
// Make sure we don't unbind while the ArmIspTester is being constructed:
fbl::AutoLock guard(&unbind_lock_);
if (on_isp_unbind_) {
on_isp_unbind_();
}
ShutDown();
txn.Reply();
}
void ArmIspDevice::DdkRelease() { delete this; }
void ArmIspDevice::ShutDown() {}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.init = ArmIspDevice::Init;
ops.bind = ArmIspDevice::Create;
return ops;
}();
} // namespace camera
// clang-format off
ZIRCON_DRIVER_BEGIN(arm-isp, camera::driver_ops, "arm-isp", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_ARM),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_ISP),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_ARM_MALI_IV009),
ZIRCON_DRIVER_END(arm-isp)