1
0
Fork 0
alistair23-linux/drivers/clk/s32/s32v234/clk-plldig.c

241 lines
5.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015-2016 Freescale Semiconductor, Inc.
* Copyright 2017-2018 NXP
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/err.h>
#include "clk.h"
/*
* struct clk_plldig - S32 PLLDIG clock
* @clk_hw: clock source
* @base: base address of PLL registers
* @plldv_mfd: multiplication loop factor divider
* @plldv_rfdphi: PHI reduced frequency divider
* @plldv_rfdphi1: PHI reduced frequency divider
*
* PLLDIG clock version 1, found on S32 series.
*/
struct clk_plldig {
struct clk_hw hw;
void __iomem *base;
enum s32v234_plldig_type type;
u32 plldv_mfd;
u32 pllfd_mfn;
u32 plldv_rfdphi;
u32 plldv_rfdphi1;
};
#define to_clk_plldig(_hw) container_of(_hw, struct clk_plldig, hw)
static unsigned long get_pllx_max_vco_rate(enum s32v234_plldig_type plltype)
{
switch (plltype) {
case S32_PLLDIG_PERIPH:
return PERIPHPLL_MAX_VCO_RATE;
default:
pr_warn("Unsupported PLL.\n");
return -EINVAL;
}
}
static unsigned long get_pllx_phiy_max_rate(enum s32v234_plldig_type plltype,
unsigned int phino)
{
switch (plltype) {
case S32_PLLDIG_PERIPH:
switch (phino) {
case 0:
return PERIPHPLL_MAX_PHI0_MAX_RATE;
case 1:
return PERIPHPLL_MAX_PHI1_MAX_RATE;
default:
break;
}
break;
default:
pr_warn("Unsupported PLL.\n");
break;
}
return -EINVAL;
}
static int clk_plldig_prepare(struct clk_hw *hw)
{
return 0;
}
static void clk_plldig_unprepare(struct clk_hw *hw)
{
}
static unsigned long clk_plldig_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_plldig *pll = to_clk_plldig(hw);
u32 plldv = readl_relaxed(PLLDIG_PLLDV(pll->base));
u32 pllfd = readl_relaxed(PLLDIG_PLLFD(pll->base));
u32 prediv, mfd, mfn, vco;
prediv = (plldv & PLLDIG_PLLDV_PREDIV_MASK)
>> PLLDIG_PLLDV_PREDIV_OFFSET;
mfd = (plldv & PLLDIG_PLLDV_MFD_MASK);
mfn = (pllfd & PLLDIG_PLLFD_MFN_MASK);
if (prediv == 0)
prediv = 1;
/*
* This formula is from platform reference manual
* (Rev. 1, 6/2015), PLLDIG chapter.
*/
vco = (parent_rate / prediv) * (mfd + mfn / 20480);
return vco;
}
static long clk_plldig_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_plldig *pll = to_clk_plldig(hw);
unsigned long max_allowed_rate = get_pllx_max_vco_rate(pll->type);
if (rate > max_allowed_rate)
rate = max_allowed_rate;
else if (rate < MIN_VCO_RATE)
rate = MIN_VCO_RATE;
return rate;
}
static int clk_plldig_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_plldig *pll = to_clk_plldig(hw);
u32 pllfd, prediv;
unsigned long max_allowed_rate = get_pllx_max_vco_rate(pll->type);
unsigned long phi0_max_rate = get_pllx_phiy_max_rate(pll->type, 0);
unsigned long phi1_max_rate = get_pllx_phiy_max_rate(pll->type, 1);
if (rate < MIN_VCO_RATE || rate > max_allowed_rate)
return -EINVAL;
if (((rate / pll->plldv_rfdphi) > phi0_max_rate) ||
((rate / pll->plldv_rfdphi) > phi1_max_rate))
return -EINVAL;
pllfd = readl_relaxed(PLLDIG_PLLFD(pll->base));
prediv = (parent_rate / rate) *
(pll->plldv_mfd + pll->pllfd_mfn / 20480);
writel_relaxed(PLLDIG_PLLDV_RFDPHI1_SET(pll->plldv_rfdphi1) |
PLLDIG_PLLDV_RFDPHI_SET(pll->plldv_rfdphi) |
PLLDIG_PLLDV_PREDIV_SET(prediv) |
PLLDIG_PLLDV_MFD_SET(pll->plldv_mfd),
PLLDIG_PLLDV(pll->base));
writel_relaxed(pllfd | PLLDIG_PLLFD_MFN_SET(pll->pllfd_mfn),
PLLDIG_PLLFD(pll->base));
/*
* To be implemented the wait_lock or an equivalent state
* return clk_plldig_wait_lock(pll);
*/
return 0;
}
static const struct clk_ops clk_plldig_ops = {
.prepare = clk_plldig_prepare,
.unprepare = clk_plldig_unprepare,
.recalc_rate = clk_plldig_recalc_rate,
.round_rate = clk_plldig_round_rate,
.set_rate = clk_plldig_set_rate,
};
struct clk *s32v234_clk_plldig_phi(enum s32v234_plldig_type type,
const char *name, const char *parent,
void __iomem *base, u32 phi)
{
u32 plldv, rfd_phi;
if (!base)
return ERR_PTR(-ENOMEM);
plldv = readl_relaxed(PLLDIG_PLLDV(base));
switch (phi) {
/* PHI0 */
case 0:
rfd_phi = (plldv & PLLDIG_PLLDV_RFDPHI_MASK)
>> PLLDIG_PLLDV_RFDPHI_OFFSET;
break;
/* PHI1 */
case 1:
rfd_phi = (plldv & PLLDIG_PLLDV_RFDPHI1_MASK)
>> PLLDIG_PLLDV_RFDPHI1_OFFSET;
if (rfd_phi == 0)
rfd_phi = 1;
break;
default:
return ERR_PTR(-EINVAL);
}
return clk_register_fixed_factor(NULL, name, parent,
CLK_SET_RATE_PARENT, 1, rfd_phi);
}
struct clk *s32v234_clk_plldig(enum s32v234_plldig_type type, const char *name,
const char *parent_name, void __iomem *base,
u32 plldv_mfd, u32 pllfd_mfn,
u32 plldv_rfdphi, u32 plldv_rfdphi1)
{
struct clk_plldig *pll;
const struct clk_ops *ops;
struct clk *clk;
struct clk_init_data init;
if (plldv_rfdphi > PLLDIG_PLLDV_RFDPHI_MAXVALUE)
return ERR_PTR(-EINVAL);
if (plldv_rfdphi1 > PLLDIG_PLLDV_RFDPHI1_MAXVALUE)
return ERR_PTR(-EINVAL);
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
ops = &clk_plldig_ops;
pll->base = base;
pll->type = type;
pll->plldv_mfd = plldv_mfd;
pll->pllfd_mfn = pllfd_mfn;
pll->plldv_rfdphi = plldv_rfdphi;
pll->plldv_rfdphi1 = plldv_rfdphi1;
init.name = name;
init.ops = ops;
init.flags = 0;
init.parent_names = &parent_name;
init.num_parents = 1;
pll->hw.init = &init;
clk = clk_register(NULL, &pll->hw);
if (IS_ERR(clk))
kfree(pll);
return clk;
}