blob: 2b57f4f8a867ad6fca8921886b7b521a1dd7e986 [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 "as370-clk.h"
#include <fuchsia/hardware/clockimpl/c/banjo.h>
#include <fuchsia/hardware/platform/bus/c/banjo.h>
#include <fuchsia/hardware/platform/bus/cpp/banjo.h>
#include <fuchsia/hardware/platform/device/c/banjo.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/pdev.h>
#include <numeric>
#include <fbl/auto_lock.h>
#include <hwreg/bitfields.h>
#include <soc/as370/as370-audio-regs.h>
#include <soc/as370/as370-clk-regs.h>
#include <soc/as370/as370-clk.h>
#include "src/devices/clock/drivers/as370-clk/syn_clk_bind.h"
namespace clk {
zx_status_t As370Clk::Create(void* ctx, zx_device_t* parent) {
ddk::PDev pdev(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "%s: failed to get pdev", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> global_mmio, avio_mmio, cpu_mmio;
auto status = pdev.MapMmio(0, &global_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to map mmio index 0 %d", __FILE__, status);
return status;
}
status = pdev.MapMmio(1, &avio_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to map mmio index 1 %d", __FILE__, status);
return status;
}
status = pdev.MapMmio(2, &cpu_mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to map mmio index 2 %d", __FILE__, status);
return status;
}
std::unique_ptr<As370Clk> device(
new As370Clk(parent, *std::move(global_mmio), *std::move(avio_mmio), *std::move(cpu_mmio)));
status = device->DdkAdd("synaptics-clk");
if (status != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed %d", __FILE__, status);
return status;
}
// Intentially leak, devmgr owns the memory now.
__UNUSED auto* unused = device.release();
return ZX_OK;
}
zx_status_t As370Clk::AvpllClkEnable(bool avpll0, bool enable) {
uint32_t id = avpll0 ? 0 : 1;
fbl::AutoLock lock(&lock_);
// TODO(andresoportus): Manage dependencies between AVPLLs, avioSysClk and SYSPLL.
// For now make sure things get enabled.
if (enable) {
// Enable/disable AVIO clk and keep SYSPLL DIV3 as source.
avioSysClk_ctrl::Get().ReadFrom(&global_mmio_).set_ClkEn(enable).WriteTo(&global_mmio_);
// Enable sysPll by disabling power down (or vice versa).
sysPll_ctrl::Get().ReadFrom(&global_mmio_).set_PD(!enable).WriteTo(&global_mmio_);
}
if (avpll0) {
// Enable/disable AVPLL0.
avioGbl_AVPLLA_CLK_EN::Get().ReadFrom(&avio_mmio_).set_ctrl_AVPLL0(enable).WriteTo(&avio_mmio_);
} else {
// Enable/disable AVPLL1.
avioGbl_AVPLLA_CLK_EN::Get().ReadFrom(&avio_mmio_).set_ctrl_AVPLL1(enable).WriteTo(&avio_mmio_);
}
// Enable/disable AVPLLx clock.
avioGbl_AVPLLx_WRAP_AVPLL_CLK1_CTRL::Get(id)
.ReadFrom(&avio_mmio_)
.set_clkEn(enable)
.WriteTo(&avio_mmio_);
return ZX_OK;
}
zx_status_t As370Clk::CpuSetRate(uint64_t rate) {
if (rate < 100'000'000 || rate > 1'800'000'000) {
return ZX_ERR_INVALID_ARGS;
}
if (rate > 400'000'000) {
auto dn = static_cast<uint32_t>(rate) / 1'000 * 72 / 1'800'000;
CPU_WRP_PLL_REG_ctrl::Get()
.FromValue(0)
.set_FRAC_READY(1)
.set_MODE(0)
.set_DN(dn)
.set_DM(1)
.set_RESETN(1)
.WriteTo(&cpu_mmio_);
CPU_WRP_PLL_REG_ctrl1::Get().FromValue(0).set_FRAC(0).WriteTo(&cpu_mmio_);
CPU_WRP_PLL_REG_ctrl3::Get()
.FromValue(0)
.set_DP1(1)
.set_PDDP1(0)
.set_DP(1)
.set_PDDP(0)
.set_SLOPE(0)
.WriteTo(&cpu_mmio_);
zxlogf(DEBUG, "%s %luHz dn %u dm %u dp %u", __FILE__, rate, dn, 1, 1);
} else {
auto dn = static_cast<uint32_t>(rate) / 1'000 * 48 / 400'000;
CPU_WRP_PLL_REG_ctrl::Get()
.FromValue(0)
.set_FRAC_READY(1)
.set_MODE(0)
.set_DN(dn)
.set_DM(1)
.set_RESETN(1)
.WriteTo(&cpu_mmio_);
CPU_WRP_PLL_REG_ctrl1::Get().FromValue(0).set_FRAC(0).WriteTo(&cpu_mmio_);
CPU_WRP_PLL_REG_ctrl3::Get()
.FromValue(0)
.set_DP1(1)
.set_PDDP1(0)
.set_DP(3)
.set_PDDP(0)
.set_SLOPE(0)
.WriteTo(&cpu_mmio_);
zxlogf(DEBUG, "%s %luHz dn %u dm %u dp %u", __FILE__, rate, dn, 1, 3);
}
return ZX_OK;
}
zx_status_t As370Clk::AvpllSetRate(bool avpll0, uint64_t rate) {
// rate = (frac / (max_frac+1) + dn) * ref_clk / dm / dp.
// frac = (rate * dp * dm / ref_clk - dn) * (max_frac+1).
// For 48KHz we need APLL = 196.608MHz.
// 196.608MHz / 8 = 24.576MHz (MCLK) / 8 = 3.072MHz (BCLK) / 64 = 48KHz (FSYNC).
// APLL rate = [frac (842887) / 16777216 + dn (55)] * ref_clk (25MHz) / dp (7) = 196.608MHz.
// For 44.1KHz we need APLL = 180.633600MHz.
// 180.633600MHz / 8 = 22.579200MHz (MCLK) / 8 = 2.822400MHz (BCLK) / 64 = 44.1KHz (FSYNC).
// APLL rate = [frac (9687298) / 16777216 + dn (50)] * ref_clk (25MHz) / dp (7) = 180.6336MHz.
uint32_t id = avpll0 ? 0 : 1;
constexpr uint64_t max_rate = 800'000'000; // HW envelope limit.
if (rate > max_rate) {
return ZX_ERR_INVALID_ARGS;
}
// TODO(andresoportus): Make relative to parent once available in clock framework.
constexpr uint64_t parent_rate = 25'000'000; // Main oscilator at 25MHz.
constexpr uint32_t max_dn = 0x7ff;
constexpr uint32_t max_frac = 0xffffff;
constexpr uint32_t dp = 7;
constexpr uint32_t dm = 1;
uint32_t dn = static_cast<uint32_t>(rate * dm * dp / parent_rate);
if (dn > max_dn) {
return ZX_ERR_INTERNAL; // Should not happen.
}
// It is ok for this calculation to be slow, only done once per PLL.
uint32_t frac = static_cast<uint32_t>(((static_cast<double>(rate) * dp * dm / parent_rate) - dn) *
(max_frac + 1));
if (frac > max_frac) {
return ZX_ERR_INTERNAL; // Should not happen.
}
zxlogf(DEBUG, "%s frac %u dn %u dm %u dp %u", __FILE__, frac, dn, dm, dp);
zxlogf(DEBUG, "%s requested: %fMHz expected: %fMHz", __FILE__,
static_cast<double>(rate) / 1'000'000.,
((static_cast<double>(frac) / (max_frac + 1)) + dn) * 25. / dp / dm);
fbl::AutoLock lock(&lock_);
if (avpll0) {
avioGbl_AVPLLA_CLK_EN::Get().ReadFrom(&avio_mmio_).set_ctrl_AVPLL0(0).WriteTo(&avio_mmio_);
} else {
avioGbl_AVPLLA_CLK_EN::Get().ReadFrom(&avio_mmio_).set_ctrl_AVPLL1(0).WriteTo(&avio_mmio_);
}
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl4::Get(id)
.ReadFrom(&avio_mmio_)
.set_BYPASS(1)
.WriteTo(&avio_mmio_);
// PLL power down.
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl3::Get(id)
.ReadFrom(&avio_mmio_)
.set_PDDP(1)
.WriteTo(&avio_mmio_);
if (frac != 0) {
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl::Get(id)
.ReadFrom(&avio_mmio_)
.set_RESETN(0)
.WriteTo(&avio_mmio_);
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl1::Get(id)
.ReadFrom(&avio_mmio_)
.set_FRAC(frac)
.WriteTo(&avio_mmio_);
}
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl::Get(id)
.ReadFrom(&avio_mmio_)
.set_DN(dn)
.set_DM(dm)
.WriteTo(&avio_mmio_);
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl3::Get(id)
.ReadFrom(&avio_mmio_)
.set_DP(dp)
.WriteTo(&avio_mmio_);
zx_nanosleep(zx_deadline_after(ZX_USEC(2)));
if (frac != 0) {
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl::Get(id)
.ReadFrom(&avio_mmio_)
.set_RESETN(1)
.WriteTo(&avio_mmio_);
}
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl3::Get(id)
.ReadFrom(&avio_mmio_)
.set_PDDP(0)
.WriteTo(&avio_mmio_);
// TODO(andresoportus): Wait for PLL lock instead of arbitrary delay.
zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
avioGbl_AVPLLx_WRAP_AVPLL_vsipll_ctrl4::Get(id)
.ReadFrom(&avio_mmio_)
.set_BYPASS(0)
.WriteTo(&avio_mmio_);
if (avpll0) {
avioGbl_AVPLLA_CLK_EN::Get().ReadFrom(&avio_mmio_).set_ctrl_AVPLL0(1).WriteTo(&avio_mmio_);
} else {
avioGbl_AVPLLA_CLK_EN::Get().ReadFrom(&avio_mmio_).set_ctrl_AVPLL1(1).WriteTo(&avio_mmio_);
}
return ZX_OK;
}
zx_status_t As370Clk::ClockImplEnable(uint32_t index) {
switch (index) {
case as370::kClkAvpll0:
return AvpllClkEnable(true, true);
case as370::kClkAvpll1:
return AvpllClkEnable(false, true);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplDisable(uint32_t index) {
switch (index) {
case as370::kClkAvpll0:
return AvpllClkEnable(true, false);
case as370::kClkAvpll1:
return AvpllClkEnable(false, false);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplIsEnabled(uint32_t id, bool* out_enabled) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplQuerySupportedRate(uint32_t id, uint64_t max_rate,
uint64_t* out_best_rate) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplGetRate(uint32_t id, uint64_t* out_current_rate) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplSetRate(uint32_t index, uint64_t hz) {
switch (index) {
case as370::kClkAvpll0:
return AvpllSetRate(true, hz);
case as370::kClkAvpll1:
return AvpllSetRate(false, hz);
case as370::kClkCpu:
return CpuSetRate(hz);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplSetInput(uint32_t id, uint32_t idx) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t As370Clk::ClockImplGetNumInputs(uint32_t id, uint32_t* out) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t As370Clk::ClockImplGetInput(uint32_t id, uint32_t* out) { return ZX_ERR_NOT_SUPPORTED; }
void As370Clk::DdkUnbind(ddk::UnbindTxn txn) {
fbl::AutoLock lock(&lock_);
global_mmio_.reset();
avio_mmio_.reset();
cpu_mmio_.reset();
txn.Reply();
}
void As370Clk::DdkRelease() { delete this; }
} // namespace clk
static constexpr zx_driver_ops_t syn_clk_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = clk::As370Clk::Create;
return ops;
}();
ZIRCON_DRIVER(syn_clk, syn_clk_driver_ops, "zircon", "0.1");