summaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
authorRobert Lee <robert.lee@freescale.com>2010-04-27 11:51:31 -0500
committerRobert Lee <robert.lee@freescale.com>2010-04-27 18:34:09 -0500
commita5421893d57a5937962f9ca7807f85da33acba9c (patch)
tree79e3bfb78165d14dd21b0e59fc6c61e829e465df /arch
parent9031b09563616b9cfb83a5131284a14588fc7fb4 (diff)
ENGR00122948 MX23: clock driver can cause cpu_clk and h_clk violations
The current method of changing the cpu FRAC and integer dividers can cause short-term unexpectedly high frequencies. Also, h_clk temporarily runs higher than expected as well. Signed-off-by: Robert Lee <robert.lee@freescale.com>
Diffstat (limited to 'arch')
-rw-r--r--arch/arm/mach-mx23/clock.c211
-rw-r--r--arch/arm/plat-mxs/cpufreq.c6
-rw-r--r--arch/arm/plat-mxs/include/mach/clock.h6
3 files changed, 194 insertions, 29 deletions
diff --git a/arch/arm/mach-mx23/clock.c b/arch/arm/mach-mx23/clock.c
index 769960f9f34c..ee353e42818a 100644
--- a/arch/arm/mach-mx23/clock.c
+++ b/arch/arm/mach-mx23/clock.c
@@ -38,9 +38,47 @@
/* external clock input */
static struct clk xtal_clk[];
static unsigned long xtal_clk_rate[3] = { 24000000, 24000000, 32000 };
+static struct clk pll_clk;
+static struct clk ref_xtal_clk;
+
static unsigned long enet_mii_phy_rate;
+static inline int clk_is_busy(struct clk *clk)
+{
+ if ((clk->parent == &ref_xtal_clk) && (clk->xtal_busy_bits))
+ return __raw_readl(clk->busy_reg) & (1 << clk->xtal_busy_bits);
+ else if (clk->busy_bits && clk->busy_reg)
+ return __raw_readl(clk->busy_reg) & (1 << clk->busy_bits);
+ else {
+ printk(KERN_ERR "WARNING: clock has no assigned busy \
+ register or bits\n");
+ udelay(10);
+ return 0;
+ }
+}
+
+static inline int clk_busy_wait(struct clk *clk)
+{
+ int i;
+
+ /* pll clock requires up to 10us to
+ * become stable
+ */
+ if (clk == &pll_clk) {
+ udelay(10);
+ return 0;
+ }
+
+ for (i = 10000000; i; i--)
+ if (!clk_is_busy(clk))
+ break;
+ if (!i)
+ return -ETIMEDOUT;
+ else
+ return 0;
+}
+
static int mx23_raw_enable(struct clk *clk)
{
unsigned int reg;
@@ -179,6 +217,8 @@ static struct clk ref_cpu_clk = {
.enable_bits = BM_CLKCTRL_FRAC_CLKGATECPU,
.scale_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC,
.scale_bits = BP_CLKCTRL_FRAC_CPUFRAC,
+ .busy_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU,
+ .busy_bits = 28,
};
static unsigned long ref_emi_get_rate(struct clk *clk)
@@ -469,18 +509,66 @@ static int cpu_set_rate(struct clk *clk, unsigned long rate)
{
unsigned long root_rate =
clk->parent->parent->get_rate(clk->parent->parent);
- int i;
+ int ret = -EINVAL;
u32 clkctrl_cpu = 1;
u32 c = clkctrl_cpu;
u32 clkctrl_frac = 1;
u32 val;
- u32 reg_val;
+ u32 reg_val, hclk_reg;
+
+ /* make sure the cpu div_xtal is 1 */
+ reg_val = __raw_readl(CLKCTRL_BASE_ADDR+HW_CLKCTRL_CPU);
+ reg_val &= ~(BM_CLKCTRL_CPU_DIV_XTAL);
+ reg_val |= (1 << BP_CLKCTRL_CPU_DIV_XTAL);
+ __raw_writel(reg_val, CLKCTRL_BASE_ADDR+HW_CLKCTRL_CPU);
if (rate < 24000000)
return -EINVAL;
else if (rate == 24000000) {
+
/* switch to the 24M source */
clk_set_parent(clk, &ref_xtal_clk);
+
+ /* to avoid bus starvation issues, we'll go ahead
+ * and change hbus clock divider to 1 now. Cpufreq
+ * or other clock management can lower it later if
+ * desired for power savings or other reasons, but
+ * there should be no need to with hbus autoslow
+ * functionality enabled.
+ */
+
+ ret = clk_busy_wait(&cpu_clk);
+ if (ret) {
+ printk(KERN_ERR "* couldn't set\
+ up CPU divisor\n");
+ return ret;
+ }
+
+ ret = clk_busy_wait(&h_clk);
+ if (ret) {
+ printk(KERN_ERR "* H_CLK busy timeout\n");
+ return ret;
+ }
+
+ hclk_reg = __raw_readl(CLKCTRL_BASE_ADDR+HW_CLKCTRL_HBUS);
+ hclk_reg &= ~(BM_CLKCTRL_HBUS_DIV);
+ hclk_reg |= (1 << BP_CLKCTRL_HBUS_DIV);
+
+ __raw_writel(hclk_reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_HBUS_SET);
+
+ ret = clk_busy_wait(&cpu_clk);
+ if (ret) {
+ printk(KERN_ERR "** couldn't set\
+ up CPU divisor\n");
+ return ret;
+ }
+
+ ret = clk_busy_wait(&h_clk);
+ if (ret) {
+ printk(KERN_ERR "** CLK busy timeout\n");
+ return ret;
+ }
+
} else {
for ( ; c < 0x40; c++) {
u32 f = ((root_rate/1000)*18/c + (rate/1000)/2) /
@@ -505,33 +593,119 @@ static int cpu_set_rate(struct clk *clk, unsigned long rate)
if ((abs(d) > 100) || (clkctrl_frac < 18) ||
(clkctrl_frac > 35))
return -EINVAL;
- }
+ }
- /* Set Frac div */
+ /* prepare Frac div */
val = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC);
val &= ~(BM_CLKCTRL_FRAC_CPUFRAC << BP_CLKCTRL_FRAC_CPUFRAC);
val |= clkctrl_frac;
- __raw_writel(val, CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC);
- /* Do not gate */
- __raw_writel(BM_CLKCTRL_FRAC_CLKGATECPU, CLKCTRL_BASE_ADDR +
- HW_CLKCTRL_FRAC_CLR);
- /* write clkctrl_cpu */
+ /* prepare clkctrl_cpu div*/
reg_val = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU);
reg_val &= ~0x3F;
reg_val |= clkctrl_cpu;
+ /* set safe hbus clock divider. A divider of 3 ensure that
+ * the Vddd voltage required for the cpuclk is sufficiently
+ * high for the hbus clock and under 24MHz cpuclk conditions,
+ * a divider of at least 3 ensures hbusclk doesn't remain
+ * uneccesarily low which hurts performance
+ */
+ hclk_reg = __raw_readl(CLKCTRL_BASE_ADDR+HW_CLKCTRL_HBUS);
+ hclk_reg &= ~(BM_CLKCTRL_HBUS_DIV);
+ hclk_reg |= (3 << BP_CLKCTRL_HBUS_DIV);
+
+ /* if the pll was OFF, we need to turn it ON.
+ * Even if it was ON, we want to temporarily
+ * increment it by 1 to avoid turning off
+ * in the upcoming parent clock change to xtal. This
+ * avoids waiting an extra 10us for every cpu clock
+ * change between ref_cpu sourced frequencies.
+ */
+
+ if (pll_clk.ref < 1) {
+ mx23_raw_enable(&pll_clk);
+ clk_busy_wait(&pll_clk);
+ }
+ pll_clk.ref++;
+
+ /* switch to XTAL CLK source temparily while
+ * we manipulate ref_cpu frequency */
+ clk_set_parent(clk, &ref_xtal_clk);
+
+ ret = clk_busy_wait(&h_clk);
+
+ if (ret) {
+ printk(KERN_ERR "-* HCLK busy wait timeout\n");
+ return ret;
+ }
+
+ ret = clk_busy_wait(clk);
+
+ if (ret) {
+ printk(KERN_ERR "-* couldn't set\
+ up CPU divisor\n");
+ return ret;
+ }
+
+ __raw_writel(val, CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC);
+
+ /* clear the gate */
+ __raw_writel(BM_CLKCTRL_FRAC_CLKGATECPU, CLKCTRL_BASE_ADDR +
+ HW_CLKCTRL_FRAC_CLR);
+
+ /* set the ref_cpu integer divider */
__raw_writel(reg_val, CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU);
- for (i = 10000; i; i--)
- if (!clk_is_busy(clk))
- break;
- if (!i) {
- printk(KERN_ERR "couldn't set up CPU divisor\n");
- return -ETIMEDOUT;
+ /* wait for the ref_cpu path to become stable before
+ * switching over to it
+ */
+
+ ret = clk_busy_wait(&ref_cpu_clk);
+
+ if (ret) {
+ printk(KERN_ERR "-** couldn't set\
+ up CPU divisor\n");
+ return ret;
}
+
+ /* change hclk divider to safe value for any ref_cpu
+ * value.
+ */
+ __raw_writel(hclk_reg, CLKCTRL_BASE_ADDR + HW_CLKCTRL_HBUS);
+
+ ret = clk_busy_wait(&h_clk);
+
+ if (ret) {
+ printk(KERN_ERR "-** HCLK busy wait timeout\n");
+ return ret;
+ }
+
+ clk_set_parent(clk, &ref_cpu_clk);
+
+ /* decrement the pll_clk ref count because
+ * we temporarily enabled/incremented the count
+ * above.
+ */
+ pll_clk.ref--;
+
+ ret = clk_busy_wait(&cpu_clk);
+
+ if (ret) {
+ printk(KERN_ERR "-*** Couldn't set\
+ up CPU divisor\n");
+ return ret;
+ }
+
+ ret = clk_busy_wait(&h_clk);
+
+ if (ret) {
+ printk(KERN_ERR "-*** HCLK busy wait timeout\n");
+ return ret;
+ }
+
}
- return 0;
+ return ret;
}
static struct clk cpu_clk = {
@@ -546,6 +720,7 @@ static struct clk cpu_clk = {
.bypass_bits = 7,
.busy_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_CPU,
.busy_bits = 28,
+ .xtal_busy_bits = 29,
};
static unsigned long uart_get_rate(struct clk *clk)
@@ -659,9 +834,6 @@ static int h_set_rate(struct clk *clk, unsigned long rate)
if (root_rate % round_rate)
return -EINVAL;
- if ((root_rate < rate) && (root_rate == 64000000))
- div = 3;
-
reg = __raw_readl(CLKCTRL_BASE_ADDR + HW_CLKCTRL_HBUS);
reg &= ~(BM_CLKCTRL_HBUS_DIV_FRAC_EN | BM_CLKCTRL_HBUS_DIV);
reg |= BF_CLKCTRL_HBUS_DIV(div);
@@ -815,6 +987,7 @@ static struct clk emi_clk = {
.scale_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_FRAC,
.busy_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_EMI,
.busy_bits = 28,
+ .xtal_busy_bits = 29,
.bypass_reg = CLKCTRL_BASE_ADDR + HW_CLKCTRL_CLKSEQ,
.bypass_bits = 7,
};
diff --git a/arch/arm/plat-mxs/cpufreq.c b/arch/arm/plat-mxs/cpufreq.c
index d36baa740dbc..37888e751cb2 100644
--- a/arch/arm/plat-mxs/cpufreq.c
+++ b/arch/arm/plat-mxs/cpufreq.c
@@ -209,14 +209,10 @@ static int set_op(struct cpufreq_policy *policy, unsigned int target_freq)
profiles[i].vdda);
}
timing_ctrl_rams(ss);
- if (freqs.old == 64000)
- clk_set_rate(ahb_clk, (profiles[i].ahb) * 1000);
clk_set_rate(cpu_clk, (profiles[i].cpu) * 1000);
- if (freqs.old != 64000)
- clk_set_rate(ahb_clk, (profiles[i].ahb) * 1000);
+ clk_set_rate(ahb_clk, (profiles[i].ahb) * 1000);
clk_set_rate(emi_clk, (profiles[i].emi) * 1000);
}
- udelay(100);
cpu_clk_set_pll_off(cpu_clk, freqs.new);
diff --git a/arch/arm/plat-mxs/include/mach/clock.h b/arch/arm/plat-mxs/include/mach/clock.h
index 744a031b42b6..9222788604f7 100644
--- a/arch/arm/plat-mxs/include/mach/clock.h
+++ b/arch/arm/plat-mxs/include/mach/clock.h
@@ -35,6 +35,7 @@ struct clk {
unsigned int enable_bits;
unsigned int bypass_bits;
unsigned int busy_bits;
+ unsigned int xtal_busy_bits;
unsigned int wait:1;
unsigned int invert:1;
@@ -77,11 +78,6 @@ int clk_get_usecount(struct clk *clk);
extern int clk_register(struct clk_lookup *lookup);
extern void clk_unregister(struct clk_lookup *lookup);
-static inline int clk_is_busy(struct clk *clk)
-{
- return __raw_readl(clk->busy_reg) & (1 << clk->busy_bits);
-}
-
struct mxs_emi_scaling_data {
u32 emi_div;
u32 frac_div;