blob: 228f158cf1c193a0be8d47752d98c64245f1e47e [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 "arm-isp-regs.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/metadata.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/unique_ptr.h>
#include <memory>
#include <stdlib.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/types.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;
// ISP memory offsets
constexpr uint32_t kDecompander0PingOffset = 0xAB6C;
constexpr uint32_t kPingConfigSize = 0x17FC0;
constexpr uint32_t kAexpHistStatsOffset = 0x24A8;
constexpr uint32_t kHistSize = 0x2000;
constexpr uint32_t kPingMeteringStatsOffset = 0x44B0;
constexpr uint32_t kPongMeteringStatsOffset = kPingMeteringStatsOffset + kPingConfigSize;
constexpr uint32_t kDecompander0PongOffset = kDecompander0PingOffset + kPingConfigSize;
constexpr uint32_t kMeteringSize = 0x8000;
constexpr uint32_t kLocalBufferSize = (0x18e88 + 0x4000);
constexpr uint32_t kConfigSize = 0x1231C;
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);
}
// Interrupt handler for the ISP.
int ArmIspDevice::IspIrqHandler() {
zxlogf(INFO, "%s start\n", __func__);
zx_status_t status = ZX_OK;
while (running_.load()) {
status = isp_irq_.wait(NULL);
if (status != ZX_OK) {
return status;
}
auto irq_status = IspGlobalInterrupt_StatusVector::Get().ReadFrom(&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_);
if (irq_status.has_errors()) {
zxlogf(ERROR, "%s ISP Error Occured, resetting ISP", __func__);
// TODO(braval) : Handle error case here
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_,
frame_processing_signal_);
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();
// Input port safe start
return SetPort(kSafeStart);
}
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;
}
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;
}
// 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);
}
// 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)
.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_);
// TODO(garratt): Enable this only under test.
status = ArmIspDeviceTester::Create(this);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to create ISP Tester\n", __func__);
return status;
}
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::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;
}
// TODO(braval): This is here only for testing purposes for initial bring up phase
isp_device->InitIsp();
//isp_device->StartStreaming();
status = isp_device->DdkAdd("arm-isp");
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");
}
// isp_device intentionally leaked as it is now held by DevMgr.
__UNUSED auto ptr = isp_device.release();
return status;
}
zx_status_t ArmIspDevice::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
zx_status_t status = fuchsia_hardware_camera_Control_try_dispatch(this, txn, msg, &control_ops);
if (status != ZX_ERR_NOT_SUPPORTED) {
return status;
}
return fuchsia_hardware_camera_Stream_try_dispatch(this, txn, msg, &stream_ops);
}
zx_status_t ArmIspDevice::StartStreaming() {
// At reset we use PING config
IspGlobal_Config3::Get()
.ReadFrom(&isp_mmio_)
.select_config_ping()
.WriteTo(&isp_mmio_);
// Copy current context to ISP
CopyContextInfo(kPing, kCopyToIsp);
// TODO(garratt@) Get the next bufffer
CopyContextInfo(kPong, kCopyToIsp);
zx_status_t status = SetPort(kSafeStart);
if (status != ZX_OK) {
return status;
}
statsMgr_->SensorStartStreaming();
return status;
}
zx_status_t ArmIspDevice::StopStreaming() {
statsMgr_->SensorStopStreaming();
return SetPort(kSafeStop);
}
zx_status_t ArmIspDevice::ReleaseFrame(uint32_t buffer_id) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ArmIspDevice::GetFormats(uint32_t index, fidl_txn_t* txn) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ArmIspDevice::CreateStream(const fuchsia_sysmem_BufferCollectionInfo* buffer_collection,
const fuchsia_hardware_camera_FrameRate* rate,
zx_handle_t stream,
zx_handle_t stream_token) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t ArmIspDevice::GetDeviceInfo(fidl_txn_t* txn) {
return ZX_ERR_NOT_SUPPORTED;
}
ArmIspDevice::~ArmIspDevice() {
free(isp_mmio_local_.get());
running_.store(false);
thrd_join(irq_thread_, NULL);
isp_irq_.destroy();
}
void ArmIspDevice::DdkUnbind() {
ShutDown();
DdkRemove();
}
void ArmIspDevice::DdkRelease() {
delete this;
}
void ArmIspDevice::ShutDown() {
}
static zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
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)