blob: 292b4de27bcf33072146fb6aa06ca69d690e7f39 [file] [edit]
// Copyright 2024 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 "meson-cpu-clock.h"
#include <lib/ddk/platform-defs.h>
#include <lib/driver/logging/cpp/logger.h>
#include <zircon/syscalls/smc.h>
#include <hwreg/bitfields.h>
#include "src/devices/clock/drivers/amlogic-clk/aml-fclk.h"
namespace amlogic_clock {
class SysCpuClkControl : public hwreg::RegisterBase<SysCpuClkControl, uint32_t> {
public:
DEF_BIT(29, busy_cnt);
DEF_BIT(28, busy);
DEF_BIT(26, dyn_enable);
DEF_FIELD(25, 20, mux1_divn_tcnt);
DEF_BIT(18, postmux1);
DEF_FIELD(17, 16, premux1);
DEF_BIT(15, manual_mux_mode);
DEF_BIT(14, manual_mode_post);
DEF_BIT(13, manual_mode_pre);
DEF_BIT(12, force_update_t);
DEF_BIT(11, final_mux_sel);
DEF_BIT(10, final_dyn_mux_sel);
DEF_FIELD(9, 4, mux0_divn_tcnt);
DEF_BIT(3, rev);
DEF_BIT(2, postmux0);
DEF_FIELD(1, 0, premux0);
static auto Get(uint32_t offset) { return hwreg::RegisterAddr<SysCpuClkControl>(offset); }
};
zx_status_t MesonCpuClock::SecSetClk(uint32_t func_id, uint64_t arg1, uint64_t arg2, uint64_t arg3,
uint64_t arg4, uint64_t arg5, uint64_t arg6) {
zx_status_t status;
zx_smc_parameters_t smc_params = {
.func_id = func_id,
.arg1 = arg1,
.arg2 = arg2,
.arg3 = arg3,
.arg4 = arg4,
.arg5 = arg5,
.arg6 = arg6,
};
zx_smc_result_t smc_result;
status = zx_smc_call(smc_.get(), &smc_params, &smc_result);
if (status != ZX_OK) {
FDF_LOG(ERROR, "zx_smc_call failed: %s", zx_status_get_string(status));
}
return status;
}
zx_status_t MesonCpuClock::SecSetCpuClkMux(uint64_t clock_source) {
zx_status_t status = ZX_OK;
status = SecSetClk(kSecureCpuClk, static_cast<uint64_t>(SecPll::kSecidCpuClkSel),
kFinalMuxSelMask, clock_source, 0, 0, 0);
if (status != ZX_OK) {
FDF_LOG(ERROR, "kSecidCpuClkSel failed: %s", zx_status_get_string(status));
}
return status;
}
zx_status_t MesonCpuClock::SecSetSys0DcoPll(const pll_params_table& pll_params) {
zx_status_t status = ZX_OK;
status = SecSetClk(kSecurePllClk, static_cast<uint64_t>(SecPll::kSecidSys0DcoPll), pll_params.m,
pll_params.n, pll_params.od, 0, 0);
if (status != ZX_OK) {
FDF_LOG(ERROR, "kSecidSys0DcoPll failed: %s", zx_status_get_string(status));
}
return status;
}
zx_status_t MesonCpuClock::SecSetCpuClkDyn(const cpu_dyn_table& dyn_params) {
zx_status_t status = ZX_OK;
status = SecSetClk(kSecureCpuClk, static_cast<uint64_t>(SecPll::kSecidCpuClkDyn),
dyn_params.dyn_pre_mux, dyn_params.dyn_post_mux, dyn_params.dyn_div, 0, 0);
if (status != ZX_OK) {
FDF_LOG(ERROR, "kSecidCpuClkDyn failed: %s", zx_status_get_string(status));
}
return status;
}
zx_status_t MesonCpuClock::SetRateA5(const uint32_t hz) {
zx_status_t status;
// CPU clock tree: sys_pll(high clock source), final_dyn_mux(low clock source)
//
// cts_osc_clk ->|->premux0->|->mux0_divn->|->postmux0->|
// fclk_div2 ->| | --------> | |->final_dyn_mux->|
// fclk_div3 ->|->premux1->|->mux1_divn->|->postmux1->| |
// fclk_div2p5 ->| | --------> | | |->final_mux->cpu_clk
// |
// sys_pll ->| --------------------> |
//
if (hz > kFrequencyThresholdHz) {
auto rate = std::ranges::find_if(a5_sys_pll_params_table,
[hz](const pll_params_table& a) { return a.rate == hz; });
if (rate == std::end(a5_sys_pll_params_table)) {
FDF_LOG(ERROR, "Invalid cpu freq");
return ZX_ERR_NOT_SUPPORTED;
}
// Switch to low freq source(cpu_dyn)
status = SecSetCpuClkMux(kFinalMuxSelCpuDyn);
if (status != ZX_OK) {
FDF_LOG(ERROR, "SecSetCpuClkMux failed: %s", zx_status_get_string(status));
return status;
}
// Set clock by sys_pll
status = SecSetSys0DcoPll(*rate);
if (status != ZX_OK) {
FDF_LOG(ERROR, "SecSetSys0DcoPll failed: %s", zx_status_get_string(status));
return status;
}
// Switch to high freq source(sys_pll)
status = SecSetCpuClkMux(kFinalMuxSelSysPll);
if (status != ZX_OK) {
FDF_LOG(ERROR, "SecSetCpuClkMux failed: %s", zx_status_get_string(status));
}
} else {
auto rate = std::ranges::find_if(a5_cpu_dyn_table,
[hz](const cpu_dyn_table& a) { return a.rate == hz; });
if (rate == std::end(a5_cpu_dyn_table)) {
FDF_LOG(ERROR, "Invalid cpu freq");
return ZX_ERR_NOT_SUPPORTED;
}
// Set clock by cpu_dyn
status = SecSetCpuClkDyn(*rate);
if (status != ZX_OK) {
FDF_LOG(ERROR, "SecSetCpuClkDyn failed: %s", zx_status_get_string(status));
return status;
}
// Switch to low freq source(cpu_dyn)
status = SecSetCpuClkMux(kFinalMuxSelCpuDyn);
if (status != ZX_OK) {
FDF_LOG(ERROR, "SecSetCpuClkMux failed: %s", zx_status_get_string(status));
}
}
return status;
}
zx_status_t MesonCpuClock::SetRateA1(const uint32_t hz) {
zx_status_t status;
if (hz > kFrequencyThresholdHz) {
// switch to low freq source
auto sys_cpu_ctrl0 = SysCpuClkControl::Get(offset_).ReadFrom(&*hiu_);
sys_cpu_ctrl0.set_final_mux_sel(kFixedPll).WriteTo(&*hiu_);
status = ConfigureSysPLL(hz);
} else {
status = ConfigCpuFixedPll(hz);
}
return status;
}
zx_status_t MesonCpuClock::SetRate(uint32_t hz) {
if (chip_id_ == PDEV_PID_AMLOGIC_A5) {
if (zx_status_t status = SetRateA5(hz); status != ZX_OK) {
return status;
}
} else if (chip_id_ == PDEV_PID_AMLOGIC_A1) {
if (zx_status_t status = SetRateA1(hz); status != ZX_OK) {
return status;
}
} else {
if (hz > kFrequencyThresholdHz && current_rate_hz_ > kFrequencyThresholdHz) {
// Switching between two frequencies both higher than 1GHz.
// In this case, as per the datasheet it is recommended to change
// to a frequency lower than 1GHz first and then switch to higher
// frequency to avoid glitches.
// Let's first switch to 1GHz
if (zx_status_t status = SetRate(kFrequencyThresholdHz); status != ZX_OK) {
FDF_LOG(ERROR, "%s: failed to set CPU freq to intermediate freq, status = %d", __func__,
status);
return status;
}
// Now let's set SYS_PLL rate to hz.
if (zx_status_t status = ConfigureSysPLL(hz); status != ZX_OK) {
FDF_LOG(ERROR, "Failed to configure sys PLL: %s", zx_status_get_string(status));
return status;
}
} else if (hz > kFrequencyThresholdHz && current_rate_hz_ <= kFrequencyThresholdHz) {
// Switching from a frequency lower than 1GHz to one greater than 1GHz.
// In this case we just need to set the SYS_PLL to required rate and
// then set the final mux to 1 (to select SYS_PLL as the source.)
// Now let's set SYS_PLL rate to hz.
if (zx_status_t status = ConfigureSysPLL(hz); status != ZX_OK) {
FDF_LOG(ERROR, "Failed to configure sys PLL: %s", zx_status_get_string(status));
return status;
}
} else {
// Switching between two frequencies below 1GHz.
// In this case we change the source and dividers accordingly
// to get the required rate from MPLL and do not touch the
// final mux.
if (zx_status_t status = ConfigCpuFixedPll(hz); status != ZX_OK) {
FDF_LOG(ERROR, "Failed to configure CPU fixed PLL: %s", zx_status_get_string(status));
return status;
}
}
}
current_rate_hz_ = hz;
return ZX_OK;
}
zx_status_t MesonCpuClock::ConfigureSysPLL(uint32_t new_rate) {
// This API also validates if the new_rate is valid.
// So no need to validate it here.
zx_status_t status = sys_pll_->SetRate(new_rate);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to set SYS_PLL rate: %s", zx_status_get_string(status));
return status;
}
// Now we need to change the final mux to select input as SYS_PLL.
status = WaitForBusyCpu();
if (status != ZX_OK) {
FDF_LOG(ERROR, "%s: failed to wait for busy, status = %d", __func__, status);
return status;
}
// Select the final mux.
auto sys_cpu_ctrl0 = SysCpuClkControl::Get(offset_).ReadFrom(&*hiu_);
sys_cpu_ctrl0.set_final_mux_sel(kSysPll).WriteTo(&*hiu_);
return status;
}
zx::result<uint64_t> MesonCpuClock::QuerySupportedRate(const uint64_t max_rate) {
// Cpu Clock supported rates fall into two categories based on whether they're below
// or above the 1GHz threshold. This method scans both the syspll and the fclk to
// determine the maximum rate that does not exceed `max_rate`.
zx::result<uint64_t> syspll_rate = zx::error(ZX_ERR_NOT_FOUND);
zx::result<uint64_t> fclk_rate = zx::error(ZX_ERR_NOT_FOUND);
if (chip_id_ == PDEV_PID_AMLOGIC_A5) {
for (const auto& entry : a5_cpu_dyn_table) {
if (entry.rate > fclk_rate && entry.rate <= max_rate) {
fclk_rate = zx::ok(entry.rate);
}
}
for (const auto& entry : a5_sys_pll_params_table) {
if (entry.rate > fclk_rate && entry.rate <= max_rate) {
syspll_rate = zx::ok(entry.rate);
}
}
} else {
syspll_rate = sys_pll_->QuerySupportedRate(max_rate);
const aml_fclk_rate_table_t* fclk_rate_table = s905d2_fclk_get_rate_table();
size_t rate_count = s905d2_fclk_get_rate_table_count();
for (size_t i = 0; i < rate_count; i++) {
if (fclk_rate_table[i].rate > fclk_rate && fclk_rate_table[i].rate <= max_rate) {
fclk_rate = zx::ok(fclk_rate_table[i].rate);
}
}
}
// 4 cases: rate supported by syspll only, rate supported by fclk only
// rate supported by neither or rate supported by both.
if (syspll_rate.is_ok() && fclk_rate.is_error()) {
// Case 1
return syspll_rate;
}
if (syspll_rate.is_error() && fclk_rate.is_ok()) {
// Case 2
return fclk_rate;
}
if (syspll_rate.is_error() && fclk_rate.is_error()) {
// Case 3
return zx::error(ZX_ERR_NOT_FOUND);
}
// Case 4
if (syspll_rate.value() > kFrequencyThresholdHz) {
return syspll_rate;
}
return fclk_rate;
}
zx::result<uint64_t> MesonCpuClock::GetRate() { return zx::ok(current_rate_hz_); }
// NOTE: This block doesn't modify the MPLL, it just programs the muxes &
// dividers to get the new_rate in the sys_pll_div block. Refer fig. 6.6 Multi
// Phase PLLS for A53 & A73 in the datasheet.
zx_status_t MesonCpuClock::ConfigCpuFixedPll(const uint32_t new_rate) {
const aml_fclk_rate_table_t* fclk_rate_table;
size_t rate_count;
size_t i;
if (chip_id_ == PDEV_PID_AMLOGIC_A1) {
fclk_rate_table = a1_fclk_get_rate_table();
rate_count = a1_fclk_get_rate_table_count();
} else {
fclk_rate_table = s905d2_fclk_get_rate_table();
rate_count = s905d2_fclk_get_rate_table_count();
}
// Validate if the new_rate is available
for (i = 0; i < rate_count; i++) {
if (new_rate == fclk_rate_table[i].rate) {
break;
}
}
if (i == rate_count) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t status = WaitForBusyCpu();
if (status != ZX_OK) {
FDF_LOG(ERROR, "%s: failed to wait for busy, status = %d", __func__, status);
return status;
}
auto sys_cpu_ctrl0 = SysCpuClkControl::Get(offset_).ReadFrom(&*hiu_);
if (sys_cpu_ctrl0.final_dyn_mux_sel()) {
// Dynamic mux 1 is in use, we setup dynamic mux 0
sys_cpu_ctrl0.set_final_dyn_mux_sel(0)
.set_mux0_divn_tcnt(fclk_rate_table[i].mux_div)
.set_postmux0(fclk_rate_table[i].postmux)
.set_premux0(fclk_rate_table[i].premux);
} else {
// Dynamic mux 0 is in use, we setup dynamic mux 1
sys_cpu_ctrl0.set_final_dyn_mux_sel(1)
.set_mux1_divn_tcnt(fclk_rate_table[i].mux_div)
.set_postmux1(fclk_rate_table[i].postmux)
.set_premux1(fclk_rate_table[i].premux);
}
// Select the final mux.
sys_cpu_ctrl0.set_final_mux_sel(kFixedPll).WriteTo(&*hiu_);
return ZX_OK;
}
zx_status_t MesonCpuClock::WaitForBusyCpu() {
auto sys_cpu_ctrl0 = SysCpuClkControl::Get(offset_).ReadFrom(&*hiu_);
// Wait till we are not busy.
for (uint32_t i = 0; i < kSysCpuWaitBusyRetries; i++) {
sys_cpu_ctrl0 = SysCpuClkControl::Get(offset_).ReadFrom(&*hiu_);
if (sys_cpu_ctrl0.busy()) {
// Wait a little bit before trying again.
zx_nanosleep(zx_deadline_after(ZX_USEC(kSysCpuWaitBusyTimeoutUs)));
continue;
}
return ZX_OK;
}
return ZX_ERR_TIMED_OUT;
}
} // namespace amlogic_clock