feat(nxp-clk): add ARM PLL enablement
Add the low-level implementation to enable the ARM PLL oscillator, which
is disabled by default when booting the SoC. It will be used by PLL
diviers, for which support will be added later.
Change-Id: I964fa7374ea9a08c695009176eade01003c1d6c2
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
diff --git a/drivers/nxp/clk/s32cc/include/s32cc-clk-regs.h b/drivers/nxp/clk/s32cc/include/s32cc-clk-regs.h
index 7ae9624..8014216 100644
--- a/drivers/nxp/clk/s32cc/include/s32cc-clk-regs.h
+++ b/drivers/nxp/clk/s32cc/include/s32cc-clk-regs.h
@@ -8,6 +8,7 @@
#include <lib/utils_def.h>
#define FXOSC_BASE_ADDR (0x40050000UL)
+#define ARMPLL_BASE_ADDR (0x40038000UL)
/* FXOSC */
#define FXOSC_CTRL(FXOSC) ((FXOSC) + 0x0UL)
@@ -26,4 +27,35 @@
#define FXOSC_STAT(FXOSC) ((FXOSC) + 0x4UL)
#define FXOSC_STAT_OSC_STAT BIT_32(31U)
+/* PLL */
+#define PLLDIG_PLLCR(PLL) ((PLL) + 0x0UL)
+#define PLLDIG_PLLCR_PLLPD BIT_32(31U)
+
+#define PLLDIG_PLLSR(PLL) ((PLL) + 0x4UL)
+#define PLLDIG_PLLSR_LOCK BIT_32(2U)
+
+#define PLLDIG_PLLDV(PLL) ((PLL) + 0x8UL)
+#define PLLDIG_PLLDV_RDIV_OFFSET 12U
+#define PLLDIG_PLLDV_RDIV_MASK GENMASK_32(14U, PLLDIG_PLLDV_RDIV_OFFSET)
+#define PLLDIG_PLLDV_RDIV_SET(VAL) (PLLDIG_PLLDV_RDIV_MASK & \
+ ((VAL) << PLLDIG_PLLDV_RDIV_OFFSET))
+#define PLLDIG_PLLDV_MFI_MASK GENMASK_32(7U, 0U)
+#define PLLDIG_PLLDV_MFI(DIV) (PLLDIG_PLLDV_MFI_MASK & (DIV))
+
+#define PLLDIG_PLLFD(PLL) ((PLL) + 0x10UL)
+#define PLLDIG_PLLFD_SMDEN BIT_32(30U)
+#define PLLDIG_PLLFD_MFN_MASK GENMASK_32(14U, 0U)
+#define PLLDIG_PLLFD_MFN_SET(VAL) (PLLDIG_PLLFD_MFN_MASK & (VAL))
+
+#define PLLDIG_PLLCLKMUX(PLL) ((PLL) + 0x20UL)
+
+#define PLLDIG_PLLODIV(PLL, N) ((PLL) + 0x80UL + ((N) * 0x4UL))
+#define PLLDIG_PLLODIV_DE BIT_32(31U)
+#define PLLDIG_PLLODIV_DIV_OFFSET 16U
+#define PLLDIG_PLLODIV_DIV_MASK GENMASK_32(23U, PLLDIG_PLLODIV_DIV_OFFSET)
+#define PLLDIG_PLLODIV_DIV(VAL) (((VAL) & PLLDIG_PLLODIV_DIV_MASK) >> \
+ PLLDIG_PLLODIV_DIV_OFFSET)
+#define PLLDIG_PLLODIV_DIV_SET(VAL) (PLLDIG_PLLODIV_DIV_MASK & ((VAL) << \
+ PLLDIG_PLLODIV_DIV_OFFSET))
+
#endif /* S32CC_CLK_REGS_H */
diff --git a/drivers/nxp/clk/s32cc/s32cc_clk_drv.c b/drivers/nxp/clk/s32cc/s32cc_clk_drv.c
index 9ea55e3..b1cffee 100644
--- a/drivers/nxp/clk/s32cc/s32cc_clk_drv.c
+++ b/drivers/nxp/clk/s32cc/s32cc_clk_drv.c
@@ -10,13 +10,18 @@
#include <common/debug.h>
#include <drivers/clk.h>
#include <lib/mmio.h>
+#include <s32cc-clk-ids.h>
#include <s32cc-clk-modules.h>
#include <s32cc-clk-utils.h>
#define MAX_STACK_DEPTH (15U)
+/* This is used for floating-point precision calculations. */
+#define FP_PRECISION (100000000UL)
+
struct s32cc_clk_drv {
uintptr_t fxosc_base;
+ uintptr_t armpll_base;
};
static int update_stack_depth(unsigned int *depth)
@@ -33,6 +38,7 @@
{
static struct s32cc_clk_drv driver = {
.fxosc_base = FXOSC_BASE_ADDR,
+ .armpll_base = ARMPLL_BASE_ADDR,
};
return &driver;
@@ -67,6 +73,37 @@
return -EINVAL;
}
+static int get_base_addr(enum s32cc_clk_source id, const struct s32cc_clk_drv *drv,
+ uintptr_t *base)
+{
+ int ret = 0;
+
+ switch (id) {
+ case S32CC_FXOSC:
+ *base = drv->fxosc_base;
+ break;
+ case S32CC_ARM_PLL:
+ *base = drv->armpll_base;
+ break;
+ case S32CC_CGM1:
+ ret = -ENOTSUP;
+ break;
+ case S32CC_FIRC:
+ break;
+ case S32CC_SIRC:
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret != 0) {
+ ERROR("Unknown clock source id: %u\n", id);
+ }
+
+ return ret;
+}
+
static void enable_fxosc(const struct s32cc_clk_drv *drv)
{
uintptr_t fxosc_base = drv->fxosc_base;
@@ -121,6 +158,189 @@
return ret;
}
+static int get_pll_mfi_mfn(unsigned long pll_vco, unsigned long ref_freq,
+ uint32_t *mfi, uint32_t *mfn)
+
+{
+ unsigned long vco;
+ unsigned long mfn64;
+
+ /* FRAC-N mode */
+ *mfi = (uint32_t)(pll_vco / ref_freq);
+
+ /* MFN formula : (double)(pll_vco % ref_freq) / ref_freq * 18432.0 */
+ mfn64 = pll_vco % ref_freq;
+ mfn64 *= FP_PRECISION;
+ mfn64 /= ref_freq;
+ mfn64 *= 18432UL;
+ mfn64 /= FP_PRECISION;
+
+ if (mfn64 > UINT32_MAX) {
+ return -EINVAL;
+ }
+
+ *mfn = (uint32_t)mfn64;
+
+ vco = ((unsigned long)*mfn * FP_PRECISION) / 18432UL;
+ vco += (unsigned long)*mfi * FP_PRECISION;
+ vco *= ref_freq;
+ vco /= FP_PRECISION;
+
+ if (vco != pll_vco) {
+ ERROR("Failed to find MFI and MFN settings for PLL freq %lu. Nearest freq = %lu\n",
+ pll_vco, vco);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct s32cc_clkmux *get_pll_mux(const struct s32cc_pll *pll)
+{
+ const struct s32cc_clk_obj *source = pll->source;
+ const struct s32cc_clk *clk;
+
+ if (source == NULL) {
+ ERROR("Failed to identify PLL's parent\n");
+ return NULL;
+ }
+
+ if (source->type != s32cc_clk_t) {
+ ERROR("The parent of the PLL isn't a clock\n");
+ return NULL;
+ }
+
+ clk = s32cc_obj2clk(source);
+
+ if (clk->module == NULL) {
+ ERROR("The clock isn't connected to a module\n");
+ return NULL;
+ }
+
+ source = clk->module;
+
+ if ((source->type != s32cc_clkmux_t) &&
+ (source->type != s32cc_shared_clkmux_t)) {
+ ERROR("The parent of the PLL isn't a MUX\n");
+ return NULL;
+ }
+
+ return s32cc_obj2clkmux(source);
+}
+
+static void disable_odiv(uintptr_t pll_addr, uint32_t div_index)
+{
+ mmio_clrbits_32(PLLDIG_PLLODIV(pll_addr, div_index), PLLDIG_PLLODIV_DE);
+}
+
+static void disable_odivs(uintptr_t pll_addr, uint32_t ndivs)
+{
+ uint32_t i;
+
+ for (i = 0; i < ndivs; i++) {
+ disable_odiv(pll_addr, i);
+ }
+}
+
+static void enable_pll_hw(uintptr_t pll_addr)
+{
+ /* Enable the PLL. */
+ mmio_write_32(PLLDIG_PLLCR(pll_addr), 0x0);
+
+ /* Poll until PLL acquires lock. */
+ while ((mmio_read_32(PLLDIG_PLLSR(pll_addr)) & PLLDIG_PLLSR_LOCK) == 0U) {
+ }
+}
+
+static void disable_pll_hw(uintptr_t pll_addr)
+{
+ mmio_write_32(PLLDIG_PLLCR(pll_addr), PLLDIG_PLLCR_PLLPD);
+}
+
+static int program_pll(const struct s32cc_pll *pll, uintptr_t pll_addr,
+ const struct s32cc_clk_drv *drv, uint32_t sclk_id,
+ unsigned long sclk_freq)
+{
+ uint32_t rdiv = 1, mfi, mfn;
+ int ret;
+
+ ret = get_pll_mfi_mfn(pll->vco_freq, sclk_freq, &mfi, &mfn);
+ if (ret != 0) {
+ return -EINVAL;
+ }
+
+ /* Disable ODIVs*/
+ disable_odivs(pll_addr, pll->ndividers);
+
+ /* Disable PLL */
+ disable_pll_hw(pll_addr);
+
+ /* Program PLLCLKMUX */
+ mmio_write_32(PLLDIG_PLLCLKMUX(pll_addr), sclk_id);
+
+ /* Program VCO */
+ mmio_clrsetbits_32(PLLDIG_PLLDV(pll_addr),
+ PLLDIG_PLLDV_RDIV_MASK | PLLDIG_PLLDV_MFI_MASK,
+ PLLDIG_PLLDV_RDIV_SET(rdiv) | PLLDIG_PLLDV_MFI(mfi));
+
+ mmio_write_32(PLLDIG_PLLFD(pll_addr),
+ PLLDIG_PLLFD_MFN_SET(mfn) | PLLDIG_PLLFD_SMDEN);
+
+ enable_pll_hw(pll_addr);
+
+ return ret;
+}
+
+static int enable_pll(const struct s32cc_clk_obj *module,
+ const struct s32cc_clk_drv *drv,
+ unsigned int *depth)
+{
+ const struct s32cc_pll *pll = s32cc_obj2pll(module);
+ const struct s32cc_clkmux *mux;
+ uintptr_t pll_addr = UL(0x0);
+ unsigned long sclk_freq;
+ uint32_t sclk_id;
+ int ret;
+
+ ret = update_stack_depth(depth);
+ if (ret != 0) {
+ return ret;
+ }
+
+ mux = get_pll_mux(pll);
+ if (mux == NULL) {
+ return -EINVAL;
+ }
+
+ if (pll->instance != mux->module) {
+ ERROR("MUX type is not in sync with PLL ID\n");
+ return -EINVAL;
+ }
+
+ ret = get_base_addr(pll->instance, drv, &pll_addr);
+ if (ret != 0) {
+ ERROR("Failed to detect PLL instance\n");
+ return ret;
+ }
+
+ switch (mux->source_id) {
+ case S32CC_CLK_FIRC:
+ sclk_freq = 48U * MHZ;
+ sclk_id = 0;
+ break;
+ case S32CC_CLK_FXOSC:
+ sclk_freq = 40U * MHZ;
+ sclk_id = 1;
+ break;
+ default:
+ ERROR("Invalid source selection for PLL 0x%lx\n",
+ pll_addr);
+ return -EINVAL;
+ };
+
+ return program_pll(pll, pll_addr, drv, sclk_id, sclk_freq);
+}
+
static int enable_module(const struct s32cc_clk_obj *module, unsigned int *depth)
{
const struct s32cc_clk_drv *drv = get_drv();
@@ -142,16 +362,18 @@
case s32cc_clk_t:
ret = enable_clk_module(module, drv, depth);
break;
+ case s32cc_pll_t:
+ ret = enable_pll(module, drv, depth);
+ break;
case s32cc_clkmux_t:
ret = -ENOTSUP;
break;
case s32cc_shared_clkmux_t:
ret = -ENOTSUP;
break;
- case s32cc_pll_t:
+ case s32cc_pll_out_div_t:
ret = -ENOTSUP;
break;
- case s32cc_pll_out_div_t:
case s32cc_fixed_div_t:
ret = -ENOTSUP;
break;
diff --git a/drivers/nxp/clk/s32cc/s32cc_early_clks.c b/drivers/nxp/clk/s32cc/s32cc_early_clks.c
index 659cef3..2a64922 100644
--- a/drivers/nxp/clk/s32cc/s32cc_early_clks.c
+++ b/drivers/nxp/clk/s32cc/s32cc_early_clks.c
@@ -54,5 +54,10 @@
return ret;
}
+ ret = clk_enable(S32CC_CLK_ARM_PLL_VCO);
+ if (ret != 0) {
+ return ret;
+ }
+
return ret;
}