Skip to content

Commit

Permalink
[ARM] omap: add support for bypassing DPLLs
Browse files Browse the repository at this point in the history
This roughly corresponds with OMAP commits: 7d06c48, 3241b19,
88b5d9b, 18a5500, 9c909ac, 5c6497b, 8b1f0bd, 2ac1da8.

For both OMAP2 and OMAP3, we note the reference and bypass clocks in
the DPLL data structure.  Whenever we modify the DPLL rate, we first
ensure that both the reference and bypass clocks are enabled.  Then,
we decide whether to use the reference and DPLL, or the bypass clock
if the desired rate is identical to the bypass rate, and program the
DPLL appropriately.  Finally, we update the clock's parent, and then
disable the unused clocks.

This keeps the parents correctly balanced, and more importantly ensures
that the bypass clock is running whenever we reprogram the DPLL.  This
is especially important because the procedure for reprogramming the DPLL
involves switching to the bypass clock.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
  • Loading branch information
Russell King authored and Russell King committed Feb 19, 2009
1 parent 8b9dbc1 commit c0bf313
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 199 deletions.
47 changes: 37 additions & 10 deletions arch/arm/mach-omap2/clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,52 @@ void omap2_init_clksel_parent(struct clk *clk)
return;
}

/* Returns the DPLL rate */
/**
* omap2_get_dpll_rate - returns the current DPLL CLKOUT rate
* @clk: struct clk * of a DPLL
*
* DPLLs can be locked or bypassed - basically, enabled or disabled.
* When locked, the DPLL output depends on the M and N values. When
* bypassed, on OMAP2xxx, the output rate is either the 32KiHz clock
* or sys_clk. Bypass rates on OMAP3 depend on the DPLL: DPLLs 1 and
* 2 are bypassed with dpll1_fclk and dpll2_fclk respectively
* (generated by DPLL3), while DPLL 3, 4, and 5 bypass rates are sys_clk.
* Returns the current DPLL CLKOUT rate (*not* CLKOUTX2) if the DPLL is
* locked, or the appropriate bypass rate if the DPLL is bypassed, or 0
* if the clock @clk is not a DPLL.
*/
u32 omap2_get_dpll_rate(struct clk *clk)
{
long long dpll_clk;
u32 dpll_mult, dpll_div, dpll;
u32 dpll_mult, dpll_div, v;
struct dpll_data *dd;

dd = clk->dpll_data;
/* REVISIT: What do we return on error? */
if (!dd)
return 0;

dpll = __raw_readl(dd->mult_div1_reg);
dpll_mult = dpll & dd->mult_mask;
/* Return bypass rate if DPLL is bypassed */
v = __raw_readl(dd->control_reg);
v &= dd->enable_mask;
v >>= __ffs(dd->enable_mask);

if (cpu_is_omap24xx()) {
if (v == OMAP2XXX_EN_DPLL_LPBYPASS ||
v == OMAP2XXX_EN_DPLL_FRBYPASS)
return dd->clk_bypass->rate;
} else if (cpu_is_omap34xx()) {
if (v == OMAP3XXX_EN_DPLL_LPBYPASS ||
v == OMAP3XXX_EN_DPLL_FRBYPASS)
return dd->clk_bypass->rate;
}

v = __raw_readl(dd->mult_div1_reg);
dpll_mult = v & dd->mult_mask;
dpll_mult >>= __ffs(dd->mult_mask);
dpll_div = dpll & dd->div1_mask;
dpll_div = v & dd->div1_mask;
dpll_div >>= __ffs(dd->div1_mask);

dpll_clk = (long long)clk->parent->rate * dpll_mult;
dpll_clk = (long long)dd->clk_ref->rate * dpll_mult;
do_div(dpll_clk, dpll_div + 1);

return dpll_clk;
Expand Down Expand Up @@ -930,7 +957,7 @@ long omap2_dpll_round_rate(struct clk *clk, unsigned long target_rate)
pr_debug("clock: starting DPLL round_rate for clock %s, target rate "
"%ld\n", clk->name, target_rate);

scaled_rt_rp = target_rate / (clk->parent->rate / DPLL_SCALE_FACTOR);
scaled_rt_rp = target_rate / (dd->clk_ref->rate / DPLL_SCALE_FACTOR);
scaled_max_m = dd->max_multiplier * DPLL_SCALE_FACTOR;

dd->last_rounded_rate = 0;
Expand All @@ -957,7 +984,7 @@ long omap2_dpll_round_rate(struct clk *clk, unsigned long target_rate)
break;

r = _dpll_test_mult(&m, n, &new_rate, target_rate,
clk->parent->rate);
dd->clk_ref->rate);

/* m can't be set low enough for this n - try with a larger n */
if (r == DPLL_MULT_UNDERFLOW)
Expand Down Expand Up @@ -988,7 +1015,7 @@ long omap2_dpll_round_rate(struct clk *clk, unsigned long target_rate)

dd->last_rounded_m = min_e_m;
dd->last_rounded_n = min_e_n;
dd->last_rounded_rate = _dpll_compute_new_rate(clk->parent->rate,
dd->last_rounded_rate = _dpll_compute_new_rate(dd->clk_ref->rate,
min_e_m, min_e_n);

pr_debug("clock: final least error: e = %d, m = %d, n = %d\n",
Expand Down
15 changes: 15 additions & 0 deletions arch/arm/mach-omap2/clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@
/* The maximum error between a target DPLL rate and the rounded rate in Hz */
#define DEFAULT_DPLL_RATE_TOLERANCE 50000

/* CM_CLKSEL2_PLL.CORE_CLK_SRC bits (2XXX) */
#define CORE_CLK_SRC_32K 0x0
#define CORE_CLK_SRC_DPLL 0x1
#define CORE_CLK_SRC_DPLL_X2 0x2

/* OMAP2xxx CM_CLKEN_PLL.EN_DPLL bits - for omap2_get_dpll_rate() */
#define OMAP2XXX_EN_DPLL_LPBYPASS 0x1
#define OMAP2XXX_EN_DPLL_FRBYPASS 0x2
#define OMAP2XXX_EN_DPLL_LOCKED 0x3

/* OMAP3xxx CM_CLKEN_PLL*.EN_*_DPLL bits - for omap2_get_dpll_rate() */
#define OMAP3XXX_EN_DPLL_LPBYPASS 0x5
#define OMAP3XXX_EN_DPLL_FRBYPASS 0x6
#define OMAP3XXX_EN_DPLL_LOCKED 0x7

int omap2_clk_init(void);
int omap2_clk_enable(struct clk *clk);
void omap2_clk_disable(struct clk *clk);
Expand Down
39 changes: 26 additions & 13 deletions arch/arm/mach-omap2/clock24xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,19 +236,32 @@ static struct clk *sclk;
* Omap24xx specific clock functions
*-------------------------------------------------------------------------*/

/* This actually returns the rate of core_ck, not dpll_ck. */
static u32 omap2_get_dpll_rate_24xx(struct clk *tclk)
/**
* omap2xxx_clk_get_core_rate - return the CORE_CLK rate
* @clk: pointer to the combined dpll_ck + core_ck (currently "dpll_ck")
*
* Returns the CORE_CLK rate. CORE_CLK can have one of three rate
* sources on OMAP2xxx: the DPLL CLKOUT rate, DPLL CLKOUTX2, or 32KHz
* (the latter is unusual). This currently should be called with
* struct clk *dpll_ck, which is a composite clock of dpll_ck and
* core_ck.
*/
static unsigned long omap2xxx_clk_get_core_rate(struct clk *clk)
{
long long dpll_clk;
u8 amult;
long long core_clk;
u32 v;

dpll_clk = omap2_get_dpll_rate(tclk);
core_clk = omap2_get_dpll_rate(clk);

amult = cm_read_mod_reg(PLL_MOD, CM_CLKSEL2);
amult &= OMAP24XX_CORE_CLK_SRC_MASK;
dpll_clk *= amult;
v = cm_read_mod_reg(PLL_MOD, CM_CLKSEL2);
v &= OMAP24XX_CORE_CLK_SRC_MASK;

if (v == CORE_CLK_SRC_32K)
core_clk = 32768;
else
core_clk *= v;

return dpll_clk;
return core_clk;
}

static int omap2_enable_osc_ck(struct clk *clk)
Expand Down Expand Up @@ -371,7 +384,7 @@ static long omap2_dpllcore_round_rate(unsigned long target_rate)

static unsigned long omap2_dpllcore_recalc(struct clk *clk)
{
return omap2_get_dpll_rate_24xx(clk);
return omap2xxx_clk_get_core_rate(clk);
}

static int omap2_reprogram_dpllcore(struct clk *clk, unsigned long rate)
Expand All @@ -381,7 +394,7 @@ static int omap2_reprogram_dpllcore(struct clk *clk, unsigned long rate)
struct prcm_config tmpset;
const struct dpll_data *dd;

cur_rate = omap2_get_dpll_rate_24xx(&dpll_ck);
cur_rate = omap2xxx_clk_get_core_rate(&dpll_ck);
mult = cm_read_mod_reg(PLL_MOD, CM_CLKSEL2);
mult &= OMAP24XX_CORE_CLK_SRC_MASK;

Expand Down Expand Up @@ -516,7 +529,7 @@ static int omap2_select_table_rate(struct clk *clk, unsigned long rate)
}

curr_prcm_set = prcm;
cur_rate = omap2_get_dpll_rate_24xx(&dpll_ck);
cur_rate = omap2xxx_clk_get_core_rate(&dpll_ck);

if (prcm->dpll_speed == cur_rate / 2) {
omap2xxx_sdrc_reprogram(CORE_CLK_SRC_DPLL, 1);
Expand Down Expand Up @@ -728,7 +741,7 @@ int __init omap2_clk_init(void)
}

/* Check the MPU rate set by bootloader */
clkrate = omap2_get_dpll_rate_24xx(&dpll_ck);
clkrate = omap2xxx_clk_get_core_rate(&dpll_ck);
for (prcm = rate_table; prcm->mpu_speed; prcm++) {
if (!(prcm->flags & cpu_mask))
continue;
Expand Down
4 changes: 4 additions & 0 deletions arch/arm/mach-omap2/clock24xx.h
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,10 @@ static struct dpll_data dpll_dd = {
.mult_div1_reg = OMAP_CM_REGADDR(PLL_MOD, CM_CLKSEL1),
.mult_mask = OMAP24XX_DPLL_MULT_MASK,
.div1_mask = OMAP24XX_DPLL_DIV_MASK,
.clk_bypass = &sys_ck,
.clk_ref = &sys_ck,
.control_reg = OMAP_CM_REGADDR(PLL_MOD, CM_CLKEN),
.enable_mask = OMAP24XX_EN_DPLL_MASK,
.max_multiplier = 1024,
.min_divider = 1,
.max_divider = 16,
Expand Down
90 changes: 71 additions & 19 deletions arch/arm/mach-omap2/clock34xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ static struct omap_clk omap34xx_clks[] = {
CLK(NULL, "omap_96m_alwon_fck", &omap_96m_alwon_fck, CK_343X),
CLK(NULL, "omap_96m_fck", &omap_96m_fck, CK_343X),
CLK(NULL, "cm_96m_fck", &cm_96m_fck, CK_343X),
CLK(NULL, "virt_omap_54m_fck", &virt_omap_54m_fck, CK_343X),
CLK(NULL, "omap_54m_fck", &omap_54m_fck, CK_343X),
CLK(NULL, "omap_48m_fck", &omap_48m_fck, CK_343X),
CLK(NULL, "omap_12m_fck", &omap_12m_fck, CK_343X),
Expand All @@ -110,7 +109,6 @@ static struct omap_clk omap34xx_clks[] = {
CLK(NULL, "emu_per_alwon_ck", &emu_per_alwon_ck, CK_343X),
CLK(NULL, "dpll5_ck", &dpll5_ck, CK_3430ES2),
CLK(NULL, "dpll5_m2_ck", &dpll5_m2_ck, CK_3430ES2),
CLK(NULL, "omap_120m_fck", &omap_120m_fck, CK_3430ES2),
CLK(NULL, "clkout2_src_ck", &clkout2_src_ck, CK_343X),
CLK(NULL, "sys_clkout2", &sys_clkout2, CK_343X),
CLK(NULL, "corex2_fck", &corex2_fck, CK_343X),
Expand Down Expand Up @@ -344,7 +342,7 @@ static u16 _omap3_dpll_compute_freqsel(struct clk *clk, u8 n)
unsigned long fint;
u16 f = 0;

fint = clk->parent->rate / (n + 1);
fint = clk->dpll_data->clk_ref->rate / (n + 1);

pr_debug("clock: fint is %lu\n", fint);

Expand Down Expand Up @@ -411,7 +409,7 @@ static int _omap3_noncore_dpll_lock(struct clk *clk)
}

/*
* omap3_noncore_dpll_bypass - instruct a DPLL to bypass and wait for readiness
* _omap3_noncore_dpll_bypass - instruct a DPLL to bypass and wait for readiness
* @clk: pointer to a DPLL struct clk
*
* Instructs a non-CORE DPLL to enter low-power bypass mode. In
Expand Down Expand Up @@ -501,14 +499,25 @@ static int _omap3_noncore_dpll_stop(struct clk *clk)
static int omap3_noncore_dpll_enable(struct clk *clk)
{
int r;
struct dpll_data *dd;

if (clk == &dpll3_ck)
return -EINVAL;

if (clk->parent->rate == omap2_get_dpll_rate(clk))
dd = clk->dpll_data;
if (!dd)
return -EINVAL;

if (clk->rate == dd->clk_bypass->rate) {
WARN_ON(clk->parent != dd->clk_bypass);
r = _omap3_noncore_dpll_bypass(clk);
else
} else {
WARN_ON(clk->parent != dd->clk_ref);
r = _omap3_noncore_dpll_lock(clk);
}
/* FIXME: this is dubious - if clk->rate has changed, what about propagating? */
if (!r)
clk->rate = omap2_get_dpll_rate(clk);

return r;
}
Expand Down Expand Up @@ -583,13 +592,18 @@ static int omap3_noncore_dpll_program(struct clk *clk, u16 m, u8 n, u16 freqsel)
* @clk: struct clk * of DPLL to set
* @rate: rounded target rate
*
* Program the DPLL with the rounded target rate. Returns -EINVAL upon
* error, or 0 upon success.
* Set the DPLL CLKOUT to the target rate. If the DPLL can enter
* low-power bypass, and the target rate is the bypass source clock
* rate, then configure the DPLL for bypass. Otherwise, round the
* target rate if it hasn't been done already, then program and lock
* the DPLL. Returns -EINVAL upon error, or 0 upon success.
*/
static int omap3_noncore_dpll_set_rate(struct clk *clk, unsigned long rate)
{
struct clk *new_parent = NULL;
u16 freqsel;
struct dpll_data *dd;
int ret;

if (!clk || !rate)
return -EINVAL;
Expand All @@ -601,18 +615,56 @@ static int omap3_noncore_dpll_set_rate(struct clk *clk, unsigned long rate)
if (rate == omap2_get_dpll_rate(clk))
return 0;

if (dd->last_rounded_rate != rate)
omap2_dpll_round_rate(clk, rate);
/*
* Ensure both the bypass and ref clocks are enabled prior to
* doing anything; we need the bypass clock running to reprogram
* the DPLL.
*/
omap2_clk_enable(dd->clk_bypass);
omap2_clk_enable(dd->clk_ref);

if (dd->clk_bypass->rate == rate &&
(clk->dpll_data->modes & (1 << DPLL_LOW_POWER_BYPASS))) {
pr_debug("clock: %s: set rate: entering bypass.\n", clk->name);

if (dd->last_rounded_rate == 0)
return -EINVAL;
ret = _omap3_noncore_dpll_bypass(clk);
if (!ret)
new_parent = dd->clk_bypass;
} else {
if (dd->last_rounded_rate != rate)
omap2_dpll_round_rate(clk, rate);

if (dd->last_rounded_rate == 0)
return -EINVAL;

freqsel = _omap3_dpll_compute_freqsel(clk, dd->last_rounded_n);
if (!freqsel)
WARN_ON(1);

freqsel = _omap3_dpll_compute_freqsel(clk, dd->last_rounded_n);
if (!freqsel)
WARN_ON(1);
pr_debug("clock: %s: set rate: locking rate to %lu.\n",
clk->name, rate);

omap3_noncore_dpll_program(clk, dd->last_rounded_m, dd->last_rounded_n,
freqsel);
ret = omap3_noncore_dpll_program(clk, dd->last_rounded_m,
dd->last_rounded_n, freqsel);
if (!ret)
new_parent = dd->clk_ref;
}
if (!ret) {
/*
* Switch the parent clock in the heirarchy, and make sure
* that the new parent's usecount is correct. Note: we
* enable the new parent before disabling the old to avoid
* any unnecessary hardware disable->enable transitions.
*/
if (clk->usecount) {
omap2_clk_enable(new_parent);
omap2_clk_disable(clk->parent);
}
clk_reparent(clk, new_parent);
clk->rate = rate;
}
omap2_clk_disable(dd->clk_ref);
omap2_clk_disable(dd->clk_bypass);

return 0;
}
Expand Down Expand Up @@ -804,11 +856,11 @@ static unsigned long omap3_clkoutx2_recalc(struct clk *clk)

dd = pclk->dpll_data;

WARN_ON(!dd->control_reg || !dd->enable_mask);
WARN_ON(!dd->enable_mask);

v = __raw_readl(dd->control_reg) & dd->enable_mask;
v >>= __ffs(dd->enable_mask);
if (v != DPLL_LOCKED)
if (v != OMAP3XXX_EN_DPLL_LOCKED)
rate = clk->parent->rate;
else
rate = clk->parent->rate * 2;
Expand Down
Loading

0 comments on commit c0bf313

Please sign in to comment.