blob: 3ba5875771acb524f756080856ef68124727cc51 [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 "msm8x53-clk.h"
#include <fuchsia/hardware/clock/c/fidl.h>
#include <lib/device-protocol/pdev.h>
#include <ddk/binding.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/clockimpl.h>
#include <ddk/protocol/platform/bus.h>
#include <ddk/protocol/platform/device.h>
#include <ddktl/protocol/platform/bus.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <hwreg/bitfields.h>
#include "msm8x53-clk-regs.h"
namespace clk {
namespace {
constexpr char kMsmClkName[] = "msm-clk";
constexpr uint32_t kRcgUpdateTimeoutUsec = 500;
constexpr uint64_t kRcgRateUnset = 0;
constexpr uint32_t kCfgRcgrDivMask = (0x1f << 0);
constexpr uint32_t kCfgRcgrSrcSelMask = (0x7 << 8);
} // namespace
zx_status_t Msm8x53Clk::Create(void* ctx, zx_device_t* parent) {
zx_status_t status;
ddk::PDev pdev(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "msm-clk: failed to get pdev protocol");
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> mmio;
status = pdev.MapMmio(0, &mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "msm-clk: failed to map cc_base mmio, st = %d", status);
return status;
}
std::unique_ptr<Msm8x53Clk> device(new Msm8x53Clk(parent, *std::move(mmio)));
status = device->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "msm-clk: failed to initialize, st = %d", status);
return status;
}
status = device->DdkAdd(kMsmClkName);
if (status != ZX_OK) {
zxlogf(ERROR, "msm-clk: DdkAdd failed, st = %d", status);
return status;
}
// Intentially leak, devmgr owns the memory now.
__UNUSED auto* unused = device.release();
return ZX_OK;
}
zx_status_t Msm8x53Clk::Init() {
fbl::AutoLock lock(&rcg_rates_lock_);
for (size_t i = 0; i < msm8x53::kRcgClkCount; i++) {
rcg_rates_[i] = kRcgRateUnset;
}
return ZX_OK;
}
zx_status_t Msm8x53Clk::ClockImplEnable(uint32_t index) {
// Extract the index and the type of the clock from the argument.
const uint32_t clock_id = msm8x53::MsmClkIndex(index);
const msm8x53::msm_clk_type clock_type = msm8x53::MsmClkType(index);
switch (clock_type) {
case msm8x53::msm_clk_type::kGate:
return GateClockEnable(clock_id);
case msm8x53::msm_clk_type::kBranch:
return BranchClockEnable(clock_id);
case msm8x53::msm_clk_type::kVoter:
return VoterClockEnable(clock_id);
case msm8x53::msm_clk_type::kRcg:
return RcgClockEnable(clock_id);
}
// Unimplemented clock type?
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplDisable(uint32_t index) {
// Extract the index and the type of the clock from the argument.
const uint32_t clock_id = msm8x53::MsmClkIndex(index);
const msm8x53::msm_clk_type clock_type = msm8x53::MsmClkType(index);
switch (clock_type) {
case msm8x53::msm_clk_type::kGate:
return GateClockDisable(clock_id);
case msm8x53::msm_clk_type::kBranch:
return BranchClockDisable(clock_id);
case msm8x53::msm_clk_type::kVoter:
return VoterClockDisable(clock_id);
case msm8x53::msm_clk_type::kRcg:
return RcgClockDisable(clock_id);
}
// Unimplemented clock type?
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplIsEnabled(uint32_t id, bool* out_enabled) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplSetRate(uint32_t id, uint64_t hz) {
const uint32_t index = msm8x53::MsmClkIndex(id);
const msm8x53::msm_clk_type clock_type = msm8x53::MsmClkType(id);
switch (clock_type) {
case msm8x53::msm_clk_type::kRcg: {
fbl::AutoLock rcg_rates_lock(&rcg_rates_lock_);
return RcgClockSetRate(index, hz);
}
default:
zxlogf(WARNING, "msm_clk: unsupported clock type: %u", (uint16_t)clock_type);
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplSetInput(uint32_t id, uint32_t idx) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplGetNumInputs(uint32_t id, uint32_t* out) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplGetInput(uint32_t id, uint32_t* out) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplQuerySupportedRate(uint32_t id, uint64_t max_rate,
uint64_t* out_best_rate) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::ClockImplGetRate(uint32_t id, uint64_t* out_current_rate) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Msm8x53Clk::AwaitBranchClock(Toggle status, const uint32_t cbcr_reg) {
// In case the status check register and the clock control register cross
// a boundary.
hw_mb();
// clang-format off
constexpr uint32_t kReadyMask = 0xf0000000;
constexpr uint32_t kBranchEnableVal = 0x0;
constexpr uint32_t kBranchDisableVal = 0x80000000;
constexpr uint32_t kBranchNocFsmEnableVal = 0x20000000;
// clang-format on
constexpr uint32_t kMaxAttempts = 500;
for (uint32_t attempts = 0; attempts < kMaxAttempts; attempts++) {
const uint32_t val = mmio_.Read32(cbcr_reg) & kReadyMask;
switch (status) {
case Toggle::Enabled:
if ((val == kBranchEnableVal) || (val == kBranchNocFsmEnableVal)) {
return ZX_OK;
}
break;
case Toggle::Disabled:
if (val == kBranchDisableVal) {
return ZX_OK;
}
break;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
}
return ZX_ERR_TIMED_OUT;
}
zx_status_t Msm8x53Clk::VoterClockEnable(uint32_t index) {
if (unlikely(index >= countof(kMsmClkVoters))) {
return ZX_ERR_OUT_OF_RANGE;
}
const struct clk::msm_clk_voter& clk = kMsmClkVoters[index];
lock_.Acquire();
mmio_.SetBits32(clk.bit, clk.vote_reg);
lock_.Release();
return AwaitBranchClock(Toggle::Enabled, clk.cbcr_reg);
}
zx_status_t Msm8x53Clk::VoterClockDisable(uint32_t index) {
if (unlikely(index >= countof(kMsmClkVoters))) {
return ZX_ERR_OUT_OF_RANGE;
}
const struct clk::msm_clk_voter& clk = kMsmClkVoters[index];
lock_.Acquire();
mmio_.ClearBits32(clk.bit, clk.vote_reg);
lock_.Release();
return ZX_OK;
}
zx_status_t Msm8x53Clk::BranchClockEnable(uint32_t index) {
if (unlikely(index >= countof(kMsmClkBranches))) {
return ZX_ERR_OUT_OF_RANGE;
}
const struct clk::msm_clk_branch& clk = kMsmClkBranches[index];
lock_.Acquire();
mmio_.SetBits32(kBranchEnable, clk.reg);
lock_.Release();
return AwaitBranchClock(Toggle::Enabled, clk.reg);
}
zx_status_t Msm8x53Clk::BranchClockDisable(uint32_t index) {
if (unlikely(index >= countof(kMsmClkBranches))) {
return ZX_ERR_OUT_OF_RANGE;
}
const struct msm_clk_branch& clk = kMsmClkBranches[index];
lock_.Acquire();
mmio_.ClearBits32(kBranchEnable, clk.reg);
lock_.Release();
return AwaitBranchClock(Toggle::Disabled, clk.reg);
}
zx_status_t Msm8x53Clk::GateClockEnable(uint32_t index) {
if (unlikely(index >= countof(kMsmClkGates))) {
return ZX_ERR_OUT_OF_RANGE;
}
const msm_clk_gate_t& clk = kMsmClkGates[index];
lock_.Acquire();
mmio_.SetBits32((1u << clk.bit), clk.reg);
lock_.Release();
if (clk.delay_us) {
zx_nanosleep(zx_deadline_after(ZX_USEC(clk.delay_us)));
}
return ZX_OK;
}
zx_status_t Msm8x53Clk::GateClockDisable(uint32_t index) {
if (unlikely(index > countof(kMsmClkGates))) {
return ZX_ERR_OUT_OF_RANGE;
}
const msm_clk_gate_t& clk = kMsmClkGates[index];
lock_.Acquire();
mmio_.ClearBits32(clk.bit, clk.reg);
lock_.Release();
if (clk.delay_us) {
zx_nanosleep(zx_deadline_after(ZX_USEC(clk.delay_us)));
}
return ZX_OK;
}
zx_status_t Msm8x53Clk::RcgClockEnable(uint32_t index) {
if (unlikely(index > countof(kMsmClkRcgs))) {
return ZX_ERR_OUT_OF_RANGE;
}
const MsmClkRcg& clk = kMsmClkRcgs[index];
// Check to see if frequency has been set.
fbl::AutoLock lock(&rcg_rates_lock_);
if (rcg_rates_[index] == kRcgRateUnset) {
zxlogf(ERROR, "Attempted to enable RCG %u before setting rate", index);
return ZX_ERR_BAD_STATE;
}
zx_status_t st;
st = ToggleRcgForceEnable(clk.CmdReg(), Toggle::Enabled);
if (st != ZX_OK) {
return st;
}
st = RcgClockSetRate(index, rcg_rates_[index]);
if (st != ZX_OK) {
return st;
}
st = ToggleRcgForceEnable(clk.CmdReg(), Toggle::Disabled);
if (st != ZX_OK) {
return st;
}
return st;
}
zx_status_t Msm8x53Clk::RcgClockDisable(uint32_t index) {
// This is a NOP for all clocks that we support.
// It only needs to be implemented for clocks with non-local children.
return ZX_OK;
}
zx_status_t Msm8x53Clk::RcgClockSetRate(uint32_t index, uint64_t rate) {
if (unlikely(index >= countof(kMsmClkRcgs))) {
return ZX_ERR_OUT_OF_RANGE;
}
const MsmClkRcg& clk = kMsmClkRcgs[index];
// Clocks with non-local children or nonlocal control timeouts are
// currently unimplemented.
// Clocks with source frequencies that are not fixed are also currently
// unimplemented.
if (clk.Unsupported()) {
zxlogf(ERROR,
"Attempted to set rate for clock %u which is currently "
"unimplemented\n",
index);
return ZX_ERR_NOT_SUPPORTED;
}
// Search for the requested frequency in the clock's frequency table.
const RcgFrequencyTable* table = nullptr;
for (size_t i = 0; i < clk.TableCount(); i++) {
if (rate == clk.Table()[i].rate()) {
table = &clk.Table()[i];
break;
}
}
if (table == nullptr) {
// This clock frequency is not supported.
zxlogf(WARNING, "unsupported clock frequency, clk = %u, rate = %lu", index, rate);
return ZX_ERR_NOT_SUPPORTED;
}
{ // Nested scope for scoped locking
fbl::AutoLock lock(&lock_);
switch (clk.Type()) {
case RcgDividerType::HalfInteger:
RcgSetRateHalfInteger(clk, table);
break;
case RcgDividerType::Mnd:
RcgSetRateMnd(clk, table);
break;
}
}
// Update the frequency that we have listed in the RCG table.
rcg_rates_[index] = rate;
return ZX_OK;
}
zx_status_t Msm8x53Clk::LatchRcgConfig(const MsmClkRcg& clk) {
// Whack the config update bit and wait for it to stabilize.
constexpr uint32_t kCmdRcgrConfigUpdateBit = (0x1 << 0);
mmio_.SetBits32(kCmdRcgrConfigUpdateBit, clk.CmdReg());
constexpr uint32_t kMaxAttempts = 500;
for (uint32_t i = 0; i < kMaxAttempts; i++) {
const uint32_t cmd_reg = mmio_.Read32(clk.CmdReg());
if ((cmd_reg & kCmdRcgrConfigUpdateBit) == 0) {
return ZX_OK;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
}
zxlogf(WARNING, "Failed to latch RCG config");
return ZX_ERR_TIMED_OUT;
}
zx_status_t Msm8x53Clk::RcgSetRateHalfInteger(const MsmClkRcg& clk,
const RcgFrequencyTable* table) {
uint32_t val;
val = mmio_.Read32(clk.CfgReg());
val &= ~(kCfgRcgrDivMask | kCfgRcgrSrcSelMask);
val |= table->predev_parent();
mmio_.Write32(val, clk.CfgReg());
return LatchRcgConfig(clk);
}
zx_status_t Msm8x53Clk::RcgSetRateMnd(const MsmClkRcg& clk, const RcgFrequencyTable* table) {
uint32_t cfg = mmio_.Read32(clk.CfgReg());
constexpr uint32_t kMndModeMask = (0x3 << 12);
constexpr uint32_t kMndDualEdgeMode = (0x2 << 12);
mmio_.Write32(table->m(), clk.MReg());
mmio_.Write32(table->n(), clk.NReg());
mmio_.Write32(table->d(), clk.DReg());
cfg = mmio_.Read32(clk.CfgReg());
cfg &= ~(kCfgRcgrDivMask | kCfgRcgrSrcSelMask);
cfg |= table->predev_parent();
cfg &= ~kMndModeMask;
if (table->n() != 0) {
cfg |= kMndDualEdgeMode;
}
mmio_.Write32(cfg, clk.CfgReg());
return LatchRcgConfig(clk);
}
zx_status_t Msm8x53Clk::ToggleRcgForceEnable(uint32_t rcgr_cmd_offset, Toggle toggle) {
constexpr uint32_t kRcgForceDisableDelayUSeconds = 100;
constexpr uint32_t kRcgRootEnableBit = (1 << 1);
zx_status_t result = ZX_OK;
switch (toggle) {
case Toggle::Enabled:
lock_.Acquire();
mmio_.SetBits32(kRcgRootEnableBit, rcgr_cmd_offset);
result = AwaitRcgEnableLocked(rcgr_cmd_offset);
lock_.Release();
break;
case Toggle::Disabled:
lock_.Acquire();
mmio_.ClearBits32(kRcgRootEnableBit, rcgr_cmd_offset);
lock_.Release();
zx_nanosleep(zx_deadline_after(ZX_USEC(kRcgForceDisableDelayUSeconds)));
break;
}
return result;
}
zx_status_t Msm8x53Clk::AwaitRcgEnableLocked(uint32_t rcgr_cmd_offset) {
for (uint32_t i = 0; i < kRcgUpdateTimeoutUsec; i++) {
auto rcg_ctrl = RcgClkCmd::Read(rcgr_cmd_offset).ReadFrom(&mmio_);
if (rcg_ctrl.root_status() == 0) {
return ZX_OK;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
}
return ZX_ERR_TIMED_OUT;
}
zx_status_t Msm8x53Clk::Bind() { return ZX_OK; }
void Msm8x53Clk::DdkUnbind(ddk::UnbindTxn txn) {
fbl::AutoLock lock(&lock_);
mmio_.reset();
txn.Reply();
}
void Msm8x53Clk::DdkRelease() { delete this; }
} // namespace clk
static constexpr zx_driver_ops_t msm8x53_clk_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = clk::Msm8x53Clk::Create;
return ops;
}();
// clang-format off
ZIRCON_DRIVER_BEGIN(msm8x53_clk, msm8x53_clk_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_QUALCOMM),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_QUALCOMM_CLOCK),
ZIRCON_DRIVER_END(msm8x53_clk)