zephyr/soc/microchip/sam/common/clk-sam9x60-pll.c
Tony Han 21da37b400 soc: microchip: sam: add code for sama7g5 clocks
Add code for sama7g5 Generic Clock, Main Clock, Main System Bus Clock,
Peripheral Clock, Programmable Clock and PLL Clock.

Signed-off-by: Tony Han <tony.han@microchip.com>
2025-05-28 08:14:08 +02:00

525 lines
13 KiB
C

/*
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <pmc.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/spinlock.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(clk_pll, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
#define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0)
#define PMC_PLL_CTRL1_MUL_MSK GENMASK(31, 24)
#define PMC_PLL_CTRL1_FRACR_MSK GENMASK(21, 0)
# define do_div(n, base) ({ \
uint32_t __base = (base); \
uint32_t __rem; \
__rem = ((uint64_t)(n)) % __base; \
(n) = ((uint64_t)(n)) / __base; \
__rem; \
})
#define DIV_ROUND_CLOSEST_ULL(x, divisor)( \
{ \
__typeof__(divisor) __d = divisor; \
unsigned long long _tmp = (x) + (__d) / 2; \
do_div(_tmp, __d); \
_tmp; \
} \
)
/* Calculate "x * n / d" without unnecessary overflow or loss of precision. */
#define mult_frac(x, n, d) \
({ \
__typeof__(x) x_ = (x); \
__typeof__(n) n_ = (n); \
__typeof__(d) d_ = (d); \
\
__typeof__(x_) q = x_ / d_; \
__typeof__(x_) r = x_ % d_; \
q * n_ + r * n_ / d_; \
})
#define PLL_MAX_ID 7
struct sam9x60_pll_core {
pmc_registers_t *pmc;
struct k_spinlock *lock;
const struct clk_pll_characteristics *characteristics;
const struct clk_pll_layout *layout;
struct device clk;
const struct device *parent;
uint8_t id;
};
struct sam9x60_frac {
struct sam9x60_pll_core core;
uint32_t frac;
uint16_t mul;
};
struct sam9x60_div {
struct sam9x60_pll_core core;
uint8_t div;
};
#define to_sam9x60_pll_core(ptr) CONTAINER_OF(ptr, struct sam9x60_pll_core, clk)
#define to_sam9x60_frac(ptr) CONTAINER_OF(ptr, struct sam9x60_frac, core)
#define to_sam9x60_div(ptr) CONTAINER_OF(ptr, struct sam9x60_div, core)
static struct sam9x60_frac clocks_frac[7];
static uint32_t clocks_frac_idx;
static struct sam9x60_div clocks_div[8];
static uint32_t clocks_div_idx;
static inline bool sam9x60_pll_ready(pmc_registers_t *pmc, int id)
{
unsigned int status;
status = pmc->PMC_PLL_ISR0;
return !!(status & BIT(id));
}
static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
{
struct sam9x60_frac *frac = to_sam9x60_frac(core);
pmc_registers_t *pmc = core->pmc;
uint32_t val, cfrac, cmul;
k_spinlock_key_t key;
key = k_spin_lock(core->lock);
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
val = pmc->PMC_PLL_CTRL1;
cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
if (sam9x60_pll_ready(pmc, core->id) &&
(cmul == frac->mul && cfrac == frac->frac)) {
goto unlock;
}
/* Recommended value for PMC_PLL_ACR */
if (core->characteristics->upll) {
val = AT91_PMC_PLL_ACR_DEFAULT_UPLL;
} else {
val = AT91_PMC_PLL_ACR_DEFAULT_PLLA;
}
pmc->PMC_PLL_ACR = val;
pmc->PMC_PLL_CTRL1 = (frac->mul << core->layout->mul_shift) |
(frac->frac << core->layout->frac_shift);
#ifdef PMC_PLL_ACR_UTMIBG_Msk
if (core->characteristics->upll) {
/* Enable the UTMI internal bandgap */
val |= PMC_PLL_ACR_UTMIBG_Msk;
pmc->PMC_PLL_ACR = val;
k_busy_wait(10);
/* Enable the UTMI internal regulator */
val |= PMC_PLL_ACR_UTMIVR_Msk;
pmc->PMC_PLL_ACR = val;
k_busy_wait(10);
}
#endif
reg_update_bits(pmc->PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
PMC_PLL_UPDT_UPDATE_Msk | core->id);
pmc->PMC_PLL_CTRL0 |= PMC_PLL_CTRL0_ENLOCK_Msk | PMC_PLL_CTRL0_ENPLL_Msk;
reg_update_bits(pmc->PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
PMC_PLL_UPDT_UPDATE_Msk | core->id);
while (!sam9x60_pll_ready(pmc, core->id)) {
k_busy_wait(1);
}
unlock:
k_spin_unlock(core->lock, key);
return 0;
}
static int sam9x60_clk_frac_pll_on(const struct device *dev, clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
return sam9x60_frac_pll_set(core);
}
static int sam9x60_clk_frac_pll_off(const struct device *dev, clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
pmc_registers_t *pmc = core->pmc;
k_spinlock_key_t key;
key = k_spin_lock(core->lock);
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
reg_update_bits(pmc->PMC_PLL_CTRL0, PMC_PLL_CTRL0_ENPLL_Msk, 0);
#ifdef PMC_PLL_ACR_UTMIBG_Msk
if (core->characteristics->upll) {
reg_update_bits(pmc->PMC_PLL_ACR,
PMC_PLL_ACR_UTMIBG_Msk | PMC_PLL_ACR_UTMIVR_Msk, 0);
}
#endif
reg_update_bits(pmc->PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
PMC_PLL_UPDT_UPDATE_Msk | core->id);
k_spin_unlock(core->lock, key);
return 0;
}
static enum clock_control_status sam9x60_clk_frac_pll_get_status(const struct device *dev,
clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
if (sam9x60_pll_ready(core->pmc, core->id)) {
return CLOCK_CONTROL_STATUS_ON;
} else {
return CLOCK_CONTROL_STATUS_OFF;
}
}
static long sam9x60_frac_pll_compute_mul_frac(struct sam9x60_pll_core *core,
unsigned long rate,
unsigned long parent_rate,
bool update)
{
struct sam9x60_frac *frac = to_sam9x60_frac(core);
unsigned long tmprate, remainder;
unsigned long nmul = 0;
unsigned long nfrac = 0;
if (rate < core->characteristics->core_output[0].min ||
rate > core->characteristics->core_output[0].max) {
return -ERANGE;
}
/*
* Calculate the multiplier associated with the current
* divider that provide the closest rate to the requested one.
*/
nmul = mult_frac(rate, 1, parent_rate);
tmprate = mult_frac(parent_rate, nmul, 1);
remainder = rate - tmprate;
if (remainder) {
nfrac = DIV_ROUND_CLOSEST_ULL((uint64_t)remainder * (1 << 22),
parent_rate);
tmprate += DIV_ROUND_CLOSEST_ULL((uint64_t)nfrac * parent_rate,
(1 << 22));
}
/* Check if resulted rate is a valid. */
if (tmprate < core->characteristics->core_output[0].min ||
tmprate > core->characteristics->core_output[0].max) {
return -ERANGE;
}
if (update) {
frac->mul = nmul - 1;
frac->frac = nfrac;
}
return tmprate;
}
static int sam9x60_clk_frac_pll_get_rate(const struct device *dev,
clock_control_subsys_t sys, uint32_t *rate)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
struct sam9x60_frac *frac = to_sam9x60_frac(core);
int retval = 0;
retval = clock_control_get_rate(frac->core.parent, NULL, rate);
if (retval) {
LOG_ERR("get parent clock rate failed.");
*rate = 0;
} else {
*rate = *rate * (frac->mul + 1) +
DIV_ROUND_CLOSEST_ULL((uint64_t)*rate * frac->frac, (1<<22));
}
LOG_DBG("clk %s: rate %d", dev->name, *rate);
return retval;
}
static DEVICE_API(clock_control, frac_pll_api) = {
.on = sam9x60_clk_frac_pll_on,
.off = sam9x60_clk_frac_pll_off,
.get_rate = sam9x60_clk_frac_pll_get_rate,
.get_status = sam9x60_clk_frac_pll_get_status,
};
int sam9x60_clk_register_frac_pll(pmc_registers_t *const pmc, struct k_spinlock *lock,
const char *name,
const struct device *parent, uint8_t id,
const struct clk_pll_characteristics *characteristics,
const struct clk_pll_layout *layout, struct device **clk)
{
struct sam9x60_frac *frac;
uint32_t parent_rate;
k_spinlock_key_t key;
unsigned int val;
int ret = 0;
if (id > PLL_MAX_ID || !lock || !parent) {
return -EINVAL;
}
frac = &clocks_frac[clocks_frac_idx++];
if (clocks_frac_idx > ARRAY_SIZE(clocks_frac)) {
LOG_ERR("Array for PLL frac clock not enough");
return -ENOMEM;
}
*clk = &frac->core.clk;
(*clk)->name = name;
(*clk)->api = &frac_pll_api;
frac->core.parent = parent;
frac->core.id = id;
frac->core.characteristics = characteristics;
frac->core.layout = layout;
frac->core.pmc = pmc;
frac->core.lock = lock;
key = k_spin_lock(frac->core.lock);
if (sam9x60_pll_ready(pmc, id)) {
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, id);
val = pmc->PMC_PLL_CTRL1;
frac->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
frac->frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val);
} else {
/*
* This means the PLL is not setup by bootloaders. In this
* case we need to set the minimum rate for it. Otherwise
* a clock child of this PLL may be enabled before setting
* its rate leading to enabling this PLL with unsupported
* rate. This will lead to PLL not being locked at all.
*/
ret = clock_control_get_rate(parent, NULL, &parent_rate);
if (ret) {
LOG_ERR("get parent clock rate failed.");
goto free;
}
long tmp = sam9x60_frac_pll_compute_mul_frac(&frac->core,
characteristics->core_output[0].min,
parent_rate, true);
if (tmp < 0) {
ret = -ENOTSUP;
goto free;
}
}
k_spin_unlock(frac->core.lock, key);
return ret;
free:
k_spin_unlock(frac->core.lock, key);
k_free(frac);
return ret;
}
/* This function should be called with spinlock acquired. */
static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, uint32_t div, bool enable)
{
pmc_registers_t *pmc = core->pmc;
uint32_t ena_msk = enable ? core->layout->endiv_mask : 0;
uint32_t ena_val = enable ? (1 << core->layout->endiv_shift) : 0;
reg_update_bits(pmc->PMC_PLL_CTRL0,
core->layout->div_mask | ena_msk,
(div << core->layout->div_shift) | ena_val);
reg_update_bits(pmc->PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
PMC_PLL_UPDT_UPDATE_Msk | core->id);
while (!sam9x60_pll_ready(pmc, core->id)) {
k_busy_wait(1);
}
}
static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
{
struct sam9x60_div *div = to_sam9x60_div(core);
pmc_registers_t *pmc = core->pmc;
k_spinlock_key_t key;
uint32_t val, cdiv;
key = k_spin_lock(core->lock);
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
val = pmc->PMC_PLL_CTRL0;
cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
/* Stop if enabled an nothing changed. */
if (!!(val & core->layout->endiv_mask) && cdiv == div->div) {
goto unlock;
}
sam9x60_div_pll_set_div(core, div->div, 1);
unlock:
k_spin_unlock(core->lock, key);
return 0;
}
static int sam9x60_clk_div_pll_on(const struct device *dev, clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
return sam9x60_div_pll_set(core);
}
static int sam9x60_clk_div_pll_off(const struct device *dev, clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
pmc_registers_t *pmc = core->pmc;
k_spinlock_key_t key;
key = k_spin_lock(core->lock);
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
reg_update_bits(pmc->PMC_PLL_CTRL0, core->layout->endiv_mask, 0);
reg_update_bits(pmc->PMC_PLL_UPDT,
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
PMC_PLL_UPDT_UPDATE_Msk | core->id);
k_spin_unlock(core->lock, key);
return 0;
}
static enum clock_control_status sam9x60_clk_div_pll_get_status(const struct device *dev,
clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
pmc_registers_t *pmc = core->pmc;
k_spinlock_key_t key;
uint32_t val;
key = k_spin_lock(core->lock);
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
val = pmc->PMC_PLL_CTRL0;
k_spin_unlock(core->lock, key);
if (!!(val & core->layout->endiv_mask)) {
return CLOCK_CONTROL_STATUS_ON;
} else {
return CLOCK_CONTROL_STATUS_OFF;
}
}
static int sam9x60_clk_div_pll_get_rate(const struct device *dev,
clock_control_subsys_t sys, uint32_t *rate)
{
ARG_UNUSED(sys);
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
struct sam9x60_div *div = to_sam9x60_div(core);
int retval = 0;
retval = clock_control_get_rate(div->core.parent, NULL, rate);
if (retval) {
LOG_ERR("get parent clock rate failed.");
*rate = 0;
} else {
*rate = DIV_ROUND_CLOSEST_ULL(*rate, div->div + 1);
}
LOG_DBG("clk %s: rate %d", dev->name, *rate);
return retval;
}
static DEVICE_API(clock_control, div_pll_api) = {
.on = sam9x60_clk_div_pll_on,
.off = sam9x60_clk_div_pll_off,
.get_rate = sam9x60_clk_div_pll_get_rate,
.get_status = sam9x60_clk_div_pll_get_status,
};
int sam9x60_clk_register_div_pll(pmc_registers_t *const pmc, struct k_spinlock *lock,
const char *name,
const struct device *parent, uint8_t id,
const struct clk_pll_characteristics *characteristics,
const struct clk_pll_layout *layout,
struct device **clk)
{
struct sam9x60_div *div;
k_spinlock_key_t key;
uint32_t val;
/* We only support one changeable PLL. */
if (id > PLL_MAX_ID || !lock || !parent) {
return -EINVAL;
}
div = &clocks_div[clocks_div_idx++];
if (clocks_div_idx > ARRAY_SIZE(clocks_div)) {
LOG_ERR("Array for PLL div clock not enough");
return -ENOMEM;
}
*clk = &div->core.clk;
(*clk)->name = name;
(*clk)->api = &div_pll_api;
div->core.parent = parent;
div->core.id = id;
div->core.characteristics = characteristics;
div->core.layout = layout;
div->core.pmc = pmc;
div->core.lock = lock;
key = k_spin_lock(div->core.lock);
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, id);
val = pmc->PMC_PLL_CTRL0;
div->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val);
k_spin_unlock(div->core.lock, key);
return 0;
}