Skip to content

Commit

Permalink
clk: mmp2: Add support for PLL clock sources
Browse files Browse the repository at this point in the history
The clk-of-mmp2 driver pretends that the clock outputs from the PLLs are
constant, but in fact they are configurable.

Add logic for obtaining the actual clock rates on MMP2 as well as MMP3.
There is no documentation for either SoC, but the "systemsetting" drivers
from Marvell GPL code dump provide some clue as far as MPMU registers on
MMP2 [1] and MMP3 [2] go.

[1] https://git.kernel.org/pub/scm/linux/kernel/git/lkundrak/linux-mmp3-dell-ariel.git/tree/drivers/char/mmp2_systemsetting.c
[2] https://git.kernel.org/pub/scm/linux/kernel/git/lkundrak/linux-mmp3-dell-ariel.git/tree/drivers/char/mmp3_systemsetting.c

A separate commit will adjust the clk-of-mmp2 driver.

Tested on a MMP3-based Dell Wyse 3020 as well as MMP2-based OLPC
XO-1.75 laptop.

Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
Link: https://lkml.kernel.org/r/20200309194254.29009-5-lkundrak@v3.sk
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
  • Loading branch information
Lubomir Rintel authored and Stephen Boyd committed Mar 21, 2020
1 parent 7de0b8b commit 5d34d0b
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 1 deletion.
2 changes: 1 addition & 1 deletion drivers/clk/mmp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ obj-y += clk-apbc.o clk-apmu.o clk-frac.o clk-mix.o clk-gate.o clk.o
obj-$(CONFIG_RESET_CONTROLLER) += reset.o

obj-$(CONFIG_MACH_MMP_DT) += clk-of-pxa168.o clk-of-pxa910.o
obj-$(CONFIG_COMMON_CLK_MMP2) += clk-of-mmp2.o
obj-$(CONFIG_COMMON_CLK_MMP2) += clk-of-mmp2.o clk-pll.o

obj-$(CONFIG_CPU_PXA168) += clk-pxa168.o
obj-$(CONFIG_CPU_PXA910) += clk-pxa910.o
Expand Down
139 changes: 139 additions & 0 deletions drivers/clk/mmp/clk-pll.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* MMP PLL clock rate calculation
*
* Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk>
*/

#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/io.h>

#include "clk.h"

#define to_clk_mmp_pll(hw) container_of(hw, struct mmp_clk_pll, hw)

struct mmp_clk_pll {
struct clk_hw hw;
unsigned long default_rate;
void __iomem *enable_reg;
u32 enable;
void __iomem *reg;
u8 shift;

unsigned long input_rate;
void __iomem *postdiv_reg;
u8 postdiv_shift;
};

static int mmp_clk_pll_is_enabled(struct clk_hw *hw)
{
struct mmp_clk_pll *pll = to_clk_mmp_pll(hw);
u32 val;

val = readl_relaxed(pll->enable_reg);
if ((val & pll->enable) == pll->enable)
return 1;

/* Some PLLs, if not software controlled, output default clock. */
if (pll->default_rate > 0)
return 1;

return 0;
}

static unsigned long mmp_clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct mmp_clk_pll *pll = to_clk_mmp_pll(hw);
u32 fbdiv, refdiv, postdiv;
u64 rate;
u32 val;

val = readl_relaxed(pll->enable_reg);
if ((val & pll->enable) != pll->enable)
return pll->default_rate;

if (pll->reg) {
val = readl_relaxed(pll->reg);
fbdiv = (val >> pll->shift) & 0x1ff;
refdiv = (val >> (pll->shift + 9)) & 0x1f;
} else {
fbdiv = 2;
refdiv = 1;
}

if (pll->postdiv_reg) {
/* MMP3 clock rate calculation */
static const u8 postdivs[] = {2, 3, 4, 5, 6, 8, 10, 12, 16};

val = readl_relaxed(pll->postdiv_reg);
postdiv = (val >> pll->postdiv_shift) & 0x7;

rate = pll->input_rate;
rate *= 2 * fbdiv;
do_div(rate, refdiv);
do_div(rate, postdivs[postdiv]);
} else {
/* MMP2 clock rate calculation */
if (refdiv == 3) {
rate = 19200000;
} else if (refdiv == 4) {
rate = 26000000;
} else {
pr_err("bad refdiv: %d (0x%08x)\n", refdiv, val);
return 0;
}

rate *= fbdiv + 2;
do_div(rate, refdiv + 2);
}

return (unsigned long)rate;
}

static const struct clk_ops mmp_clk_pll_ops = {
.is_enabled = mmp_clk_pll_is_enabled,
.recalc_rate = mmp_clk_pll_recalc_rate,
};

struct clk *mmp_clk_register_pll(char *name,
unsigned long default_rate,
void __iomem *enable_reg, u32 enable,
void __iomem *reg, u8 shift,
unsigned long input_rate,
void __iomem *postdiv_reg, u8 postdiv_shift)
{
struct mmp_clk_pll *pll;
struct clk *clk;
struct clk_init_data init;

pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);

init.name = name;
init.ops = &mmp_clk_pll_ops;
init.flags = 0;
init.parent_names = NULL;
init.num_parents = 0;

pll->default_rate = default_rate;
pll->enable_reg = enable_reg;
pll->enable = enable;
pll->reg = reg;
pll->shift = shift;

pll->input_rate = input_rate;
pll->postdiv_reg = postdiv_reg;
pll->postdiv_shift = postdiv_shift;

pll->hw.init = &init;

clk = clk_register(NULL, &pll->hw);

if (IS_ERR(clk))
kfree(pll);

return clk;
}
31 changes: 31 additions & 0 deletions drivers/clk/mmp/clk.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,37 @@ void mmp_register_div_clks(struct mmp_clk_unit *unit,
}
}

void mmp_register_pll_clks(struct mmp_clk_unit *unit,
struct mmp_param_pll_clk *clks,
void __iomem *base, int size)
{
struct clk *clk;
int i;

for (i = 0; i < size; i++) {
void __iomem *reg = NULL;

if (clks[i].offset)
reg = base + clks[i].offset;

clk = mmp_clk_register_pll(clks[i].name,
clks[i].default_rate,
base + clks[i].enable_offset,
clks[i].enable,
reg, clks[i].shift,
clks[i].input_rate,
base + clks[i].postdiv_offset,
clks[i].postdiv_shift);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n",
__func__, clks[i].name);
continue;
}
if (clks[i].id)
unit->clk_table[clks[i].id] = clk;
}
}

void mmp_clk_add(struct mmp_clk_unit *unit, unsigned int id,
struct clk *clk)
{
Expand Down
24 changes: 24 additions & 0 deletions drivers/clk/mmp/clk.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,30 @@ void mmp_register_div_clks(struct mmp_clk_unit *unit,
struct mmp_param_div_clk *clks,
void __iomem *base, int size);

struct mmp_param_pll_clk {
unsigned int id;
char *name;
unsigned long default_rate;
unsigned long enable_offset;
u32 enable;
unsigned long offset;
u8 shift;
/* MMP3 specific: */
unsigned long input_rate;
unsigned long postdiv_offset;
unsigned long postdiv_shift;
};
void mmp_register_pll_clks(struct mmp_clk_unit *unit,
struct mmp_param_pll_clk *clks,
void __iomem *base, int size);

extern struct clk *mmp_clk_register_pll(char *name,
unsigned long default_rate,
void __iomem *enable_reg, u32 enable,
void __iomem *reg, u8 shift,
unsigned long input_rate,
void __iomem *postdiv_reg, u8 postdiv_shift);

#define DEFINE_MIX_REG_INFO(w_d, s_d, w_m, s_m, fc) \
{ \
.width_div = (w_d), \
Expand Down

0 comments on commit 5d34d0b

Please sign in to comment.