blob: 88e3ac1e419b07fa281c02239039f51d3264a8c6 [file] [log] [blame]
/*
* OMAP4-specific DPLL control functions
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Rajendra Nayak
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <plat/cpu.h>
#include <plat/clock.h>
#include "clock.h"
#include "clock44xx.h"
#include "cm1_44xx.h"
#include "cm-regbits-44xx.h"
#define OMAP_1GHz 1000000000
#define OMAP_920MHz 920000000
#define OMAP_748MHz 748000000
#define OMAP_1GHz 1000000000
#define OMAP_920MHz 920000000
#define OMAP_748MHz 748000000
/* Supported only on OMAP4 */
int omap4_dpllmx_gatectrl_read(struct clk *clk)
{
u32 v;
u32 mask;
if (!clk || !clk->clksel_reg || !cpu_is_omap44xx())
return -EINVAL;
mask = clk->flags & CLOCK_CLKOUTX2 ?
OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK :
OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK;
v = __raw_readl(clk->clksel_reg);
v &= mask;
v >>= __ffs(mask);
return v;
}
void omap4_dpllmx_allow_gatectrl(struct clk *clk)
{
u32 v;
u32 mask;
if (!clk || !clk->clksel_reg || !cpu_is_omap44xx())
return;
mask = clk->flags & CLOCK_CLKOUTX2 ?
OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK :
OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK;
v = __raw_readl(clk->clksel_reg);
/* Clear the bit to allow gatectrl */
v &= ~mask;
__raw_writel(v, clk->clksel_reg);
}
void omap4_dpllmx_deny_gatectrl(struct clk *clk)
{
u32 v;
u32 mask;
if (!clk || !clk->clksel_reg || !cpu_is_omap44xx())
return;
mask = clk->flags & CLOCK_CLKOUTX2 ?
OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK :
OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK;
v = __raw_readl(clk->clksel_reg);
/* Set the bit to deny gatectrl */
v |= mask;
__raw_writel(v, clk->clksel_reg);
}
const struct clkops clkops_omap4_dpllmx_ops = {
.allow_idle = omap4_dpllmx_allow_gatectrl,
.deny_idle = omap4_dpllmx_deny_gatectrl,
};
/**
* omap4_dpll_regm4xen_recalc - compute DPLL rate, considering REGM4XEN bit
* @clk: struct clk * of the DPLL to compute the rate for
*
* Compute the output rate for the OMAP4 DPLL represented by @clk.
* Takes the REGM4XEN bit into consideration, which is needed for the
* OMAP4 ABE DPLL. Returns the DPLL's output rate (before M-dividers)
* upon success, or 0 upon error.
*/
unsigned long omap4_dpll_regm4xen_recalc(struct clk *clk)
{
u32 v;
unsigned long rate;
struct dpll_data *dd;
if (!clk || !clk->dpll_data)
return 0;
dd = clk->dpll_data;
rate = omap2_get_dpll_rate(clk);
/* regm4xen adds a multiplier of 4 to DPLL calculations */
v = __raw_readl(dd->control_reg);
if (v & OMAP4430_DPLL_REGM4XEN_MASK)
rate *= OMAP4430_REGM4XEN_MULT;
return rate;
}
/**
* omap4_dpll_regm4xen_round_rate - round DPLL rate, considering REGM4XEN bit
* @clk: struct clk * of the DPLL to round a rate for
* @target_rate: the desired rate of the DPLL
*
* Compute the rate that would be programmed into the DPLL hardware
* for @clk if set_rate() were to be provided with the rate
* @target_rate. Takes the REGM4XEN bit into consideration, which is
* needed for the OMAP4 ABE DPLL. Returns the rounded rate (before
* M-dividers) upon success, -EINVAL if @clk is null or not a DPLL, or
* ~0 if an error occurred in omap2_dpll_round_rate().
*/
long omap4_dpll_regm4xen_round_rate(struct clk *clk, unsigned long target_rate)
{
u32 v;
struct dpll_data *dd;
long r;
if (!clk || !clk->dpll_data)
return -EINVAL;
dd = clk->dpll_data;
/* regm4xen adds a multiplier of 4 to DPLL calculations */
v = __raw_readl(dd->control_reg) & OMAP4430_DPLL_REGM4XEN_MASK;
if (v)
target_rate = target_rate / OMAP4430_REGM4XEN_MULT;
r = omap2_dpll_round_rate(clk, target_rate);
if (r == ~0)
return r;
if (v)
clk->dpll_data->last_rounded_rate *= OMAP4430_REGM4XEN_MULT;
return clk->dpll_data->last_rounded_rate;
}
static int omap4460_dcc(struct clk *clk, unsigned long rate)
{
struct dpll_data *dd;
u32 v;
if (!clk || !rate || !clk->parent)
return -EINVAL;
dd = clk->parent->dpll_data;
if (!dd)
return -EINVAL;
v = __raw_readl(dd->mult_div1_reg);
if (rate < OMAP_1GHz) {
/* If DCC is enabled, disable it */
if (v & OMAP4460_DCC_EN_MASK) {
v &= ~OMAP4460_DCC_EN_MASK;
__raw_writel(v, dd->mult_div1_reg);
}
} else {
v &= ~OMAP4460_DCC_COUNT_MAX_MASK;
v |= (5 << OMAP4460_DCC_COUNT_MAX_SHIFT);
v |= OMAP4460_DCC_EN_MASK;
__raw_writel(v, dd->mult_div1_reg);
}
return 0;
}
int omap4460_mpu_dpll_set_rate(struct clk *clk, unsigned long rate)
{
struct dpll_data *dd;
u32 v;
unsigned long dpll_rate;
if (!clk || !rate || !clk->parent)
return -EINVAL;
dd = clk->parent->dpll_data;
if (!dd)
return -EINVAL;
if (!clk->parent->set_rate)
return -EINVAL;
/*
* On OMAP4460, to obtain MPU DPLL frequency higher
* than 1GHz, DCC (Duty Cycle Correction) needs to
* be enabled.
* And needs to be kept disabled for < 1 Ghz.
*/
dpll_rate = omap2_get_dpll_rate(clk->parent);
v = __raw_readl(dd->mult_div1_reg);
if (v & OMAP4460_DCC_EN_MASK)
dpll_rate *= 2;
// pr_err("omap4460_mpu_dpll_set_rate: old rate %ld, new rate %ld\n",
// dpll_rate, rate);
if (rate < OMAP_1GHz) {
omap4460_dcc(clk, rate);
if (clk->parent->set_rate(clk->parent, rate)) {
omap4460_dcc(clk, dpll_rate);
return -EINVAL;
}
} else {
/*
* On 4460, the MPU clk for frequencies higher than 1Ghz
* is sourced from CLKOUTX2_M3, instead of CLKOUT_M2, while
* value of M3 is fixed to 1. Hence for frequencies higher
* than 1 Ghz, lock the DPLL at half the rate so the
* CLKOUTX2_M3 then matches the requested rate.
*/
if (clk->parent->set_rate(clk->parent, rate/2)) {
omap4460_dcc(clk, dpll_rate);
return -EINVAL;
}
omap4460_dcc(clk, rate);
}
clk->rate = rate;
/*
* The interconnect frequency to EMIF should
* be switched between MPU clk divide by 4 (for
* frequencies higher than 920Mhz) and MPU clk divide
* by 2 (for frequencies lower than or equal to 920Mhz)
* Also the async bridge to ABE must be MPU clk divide
* by 8 for MPU clk > 748Mhz and MPU clk divide by 4
* for lower frequencies.
*/
v = __raw_readl(OMAP4430_CM_MPU_MPU_CLKCTRL);
if (rate > OMAP_920MHz)
v |= OMAP4460_CLKSEL_EMIF_DIV_MODE_MASK;
else
v &= ~OMAP4460_CLKSEL_EMIF_DIV_MODE_MASK;
if (rate > OMAP_748MHz)
v |= OMAP4460_CLKSEL_ABE_DIV_MODE_MASK;
else
v &= ~OMAP4460_CLKSEL_ABE_DIV_MODE_MASK;
__raw_writel(v, OMAP4430_CM_MPU_MPU_CLKCTRL);
return 0;
}
long omap4460_mpu_dpll_round_rate(struct clk *clk, unsigned long rate)
{
if (!clk || !rate || !clk->parent)
return -EINVAL;
if (clk->parent->round_rate)
return clk->parent->round_rate(clk, rate);
else
return 0;
}
unsigned long omap4460_mpu_dpll_recalc(struct clk *clk)
{
struct dpll_data *dd;
u32 v;
if (!clk || !clk->parent)
return -EINVAL;
dd = clk->parent->dpll_data;
if (!dd)
return -EINVAL;
v = __raw_readl(dd->mult_div1_reg);
if (v & OMAP4460_DCC_EN_MASK)
return omap2_get_dpll_rate(clk->parent) * 2;
else
return omap2_get_dpll_rate(clk->parent);
}