blob: da7e91d8f7287202d93bed36b81f949a41485740 [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 "src/camera/drivers/bus/aml_mipicsi/aml_mipi.h"
#include <lib/ddk/debug.h>
#include <stdint.h>
#include <threads.h>
#include <zircon/types.h>
#include <memory>
#include "src/camera/drivers/bus/aml_mipicsi/aml_mipi_regs.h"
#include "src/camera/drivers/bus/aml_mipicsi/bind.h"
// NOTE: A lot of magic numbers, they come from vendor
// source code.
namespace camera {
namespace {
// MMIO Indexes.
constexpr uint32_t kCsiPhy0 = 0;
constexpr uint32_t kAphy0 = 1;
constexpr uint32_t kCsiHost0 = 2;
constexpr uint32_t kMipiAdap = 3;
constexpr uint32_t kHiu = 4;
// CLK Shifts & Masks
constexpr uint32_t kClkMuxMask = 0xfff;
constexpr uint32_t kClockEnableShift = 8;
} // namespace
void AmlMipiDevice::InitMipiClock() {
// clear existing values
hiu_mmio_->ClearBits32(kClkMuxMask, HHI_MIPI_CSI_PHY_CLK_CNTL);
// set the divisor = 2 (writing (2-1) to div field)
// source for the unused mux = S905D2_FCLK_DIV5 = 6 // 400 MHz
hiu_mmio_->SetBits32(((1 << kClockEnableShift) | 6 << 9 | 1), HHI_MIPI_CSI_PHY_CLK_CNTL);
// TODO(braval@) Double check to look into if
// this sleep is really necessary.
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
}
zx_status_t AmlMipiDevice::InitPdev(zx_device_t* /*parent*/) {
if (!pdev_.is_valid()) {
return ZX_ERR_NO_RESOURCES;
}
zx_status_t status = pdev_.MapMmio(kCsiPhy0, &csi_phy0_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d", __func__, status);
return status;
}
status = pdev_.MapMmio(kAphy0, &aphy0_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d", __func__, status);
return status;
}
status = pdev_.MapMmio(kCsiHost0, &csi_host0_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d", __func__, status);
return status;
}
status = pdev_.MapMmio(kMipiAdap, &mipi_adap_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d", __func__, status);
return status;
}
status = pdev_.MapMmio(kHiu, &hiu_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_.MapMmio failed %d", __func__, status);
return status;
}
// Get our bti.
status = pdev_.GetBti(0, &bti_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not obtain bti: %d", __func__, status);
return status;
}
// Get adapter interrupt.
status = pdev_.GetInterrupt(0, &adap_irq_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not obtain adapter interrupt %d", __func__, status);
return status;
}
return status;
}
void AmlMipiDevice::MipiPhyReset() {
uint32_t data32 = 0x1f; // disable lanes digital clock
data32 |= 0x1 << 31; // soft reset bit
csi_phy0_mmio_->Write32(data32, MIPI_PHY_CTRL);
}
void AmlMipiDevice::MipiCsi2Reset() {
csi_host0_mmio_->Write32(0, MIPI_CSI_PHY_SHUTDOWNZ); // enable power
csi_host0_mmio_->Write32(0, MIPI_CSI_DPHY_RSTZ); // release DPHY reset
csi_host0_mmio_->Write32(0, MIPI_CSI_CSI2_RESETN); // csi2 reset
}
void AmlMipiDevice::MipiPhyInit(const mipi_info_t* info) {
if (info->ui_value <= 1) {
aphy0_mmio_->Write32(0x0b440585, HI_CSI_PHY_CNTL0);
} else {
aphy0_mmio_->Write32(0x0b440581, HI_CSI_PHY_CNTL0);
}
aphy0_mmio_->Write32(0x803f0000, HI_CSI_PHY_CNTL1);
aphy0_mmio_->Write32(0x02, HI_CSI_PHY_CNTL3);
// 3d8 :continue mode
csi_phy0_mmio_->Write32(0x3d8, MIPI_PHY_CLK_LANE_CTRL);
// clck miss = 50 ns --(x< 60 ns)
csi_phy0_mmio_->Write32(0x9, MIPI_PHY_TCLK_MISS);
// clck settle = 160 ns --(95ns< x < 300 ns)
csi_phy0_mmio_->Write32(0x1f, MIPI_PHY_TCLK_SETTLE);
// hs exit = 160 ns --(x>100ns)
csi_phy0_mmio_->Write32(0x1f, MIPI_PHY_THS_EXIT);
// hs skip = 55 ns --(40ns<x<55ns+4*UI)
csi_phy0_mmio_->Write32(0xa, MIPI_PHY_THS_SKIP);
// No documentation for this regisgter.
// hs settle = 160 ns --(85 ns + 6*UI<x<145 ns + 10*UI)
uint32_t settle = ((85 + 145 + (16 * info->ui_value)) / 2) / 5;
csi_phy0_mmio_->Write32(settle, MIPI_PHY_THS_SETTLE);
csi_phy0_mmio_->Write32(0x4e20, MIPI_PHY_TINIT); // >100us
csi_phy0_mmio_->Write32(0x100, MIPI_PHY_TMBIAS);
csi_phy0_mmio_->Write32(0x1000, MIPI_PHY_TULPS_C);
csi_phy0_mmio_->Write32(0x100, MIPI_PHY_TULPS_S);
csi_phy0_mmio_->Write32(0x0c, MIPI_PHY_TLP_EN_W);
csi_phy0_mmio_->Write32(0x100, MIPI_PHY_TLPOK);
csi_phy0_mmio_->Write32(0x400000, MIPI_PHY_TWD_INIT);
csi_phy0_mmio_->Write32(0x400000, MIPI_PHY_TWD_HS);
csi_phy0_mmio_->Write32(0x0, MIPI_PHY_DATA_LANE_CTRL);
// enable data lanes pipe line and hs sync bit err.
csi_phy0_mmio_->Write32((0x3 | (0x1f << 2) | (0x3 << 7)), MIPI_PHY_DATA_LANE_CTRL1);
csi_phy0_mmio_->Write32(0x00000123, MIPI_PHY_MUX_CTRL0);
csi_phy0_mmio_->Write32(0x00000123, MIPI_PHY_MUX_CTRL1);
// NOTE: Possible bug in reference code. Leaving it here for future reference.
// uint32_t data32 = ((~(info->channel)) & 0xf) | (0 << 4); //enable lanes
// digital clock data32 |= ((0x10 | info->channel) << 5); //mipi_chpu
// to analog
csi_phy0_mmio_->Write32(0, MIPI_PHY_CTRL);
}
void AmlMipiDevice::MipiCsi2Init(const mipi_info_t* info) {
// csi2 reset
csi_host0_mmio_->Write32(0, MIPI_CSI_CSI2_RESETN);
// release csi2 reset
csi_host0_mmio_->Write32(0xffffffff, MIPI_CSI_CSI2_RESETN);
// release DPHY reset
csi_host0_mmio_->Write32(0xffffffff, MIPI_CSI_DPHY_RSTZ);
// set lanes
csi_host0_mmio_->Write32((info->lanes - 1) & 3, MIPI_CSI_N_LANES);
// enable power
csi_host0_mmio_->Write32(0xffffffff, MIPI_CSI_PHY_SHUTDOWNZ);
}
zx_status_t AmlMipiDevice::MipiCsiInit(const mipi_info_t* mipi_info,
const mipi_adap_info_t* adap_info) {
// Setup MIPI CSI PHY CLK to 200MHz.
// Setup MIPI ISP CLK to 667MHz.
InitMipiClock();
// Initialize the PHY.
MipiPhyInit(mipi_info);
// Initialize the CSI Host.
MipiCsi2Init(mipi_info);
// Initialize the MIPI Adapter.
zx_status_t status = MipiAdapInit(adap_info);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: MipiAdapInit failed %d", __func__, status);
return status;
}
// Start the MIPI Adapter.
MipiAdapStart(adap_info);
return status;
}
zx_status_t AmlMipiDevice::MipiCsiDeInit() {
MipiPhyReset();
MipiCsi2Reset();
MipiAdapReset();
return ZX_OK;
}
void AmlMipiDevice::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
void AmlMipiDevice::DdkRelease() { delete this; }
// static
zx_status_t AmlMipiDevice::Create(zx_device_t* parent) {
auto mipi_device = std::make_unique<AmlMipiDevice>(parent);
zx_status_t status = mipi_device->InitPdev(parent);
if (status != ZX_OK) {
return status;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_PROTO, 0, ZX_PROTOCOL_MIPI_CSI},
};
status = mipi_device->DdkAdd(ddk::DeviceAddArgs("aml-mipi").set_props(props));
if (status != ZX_OK) {
zxlogf(ERROR, "aml-mipi driver failed to get added");
return status;
}
zxlogf(INFO, "aml-mipi driver added");
// mipi_device intentionally leaked as it is now held by DevMgr.
__UNUSED auto ptr = mipi_device.release();
return status;
}
AmlMipiDevice::~AmlMipiDevice() {
adap_irq_.destroy();
running_.store(false);
thrd_join(irq_thread_, nullptr);
}
zx_status_t aml_mipi_bind(void* /*ctx*/, zx_device_t* device) {
return camera::AmlMipiDevice::Create(device);
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = aml_mipi_bind;
return ops;
}();
} // namespace camera
// clang-format off
ZIRCON_DRIVER(aml_mipi, camera::driver_ops, "aml-mipi-csi2", "0.1");