linux/arch/sh/kernel/cpu/sh4/clock-sh4-202.c
Paul Mundt 253b0887b3 sh: clkfwk: Rework legacy CPG clock handling.
This moves out the old legacy CPG clocks to their own file, and converts
over the existing users. With these clocks going away and each CPU
dealing with them on their own, CPUs can gradually move over to the new
interface.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2009-05-13 17:38:11 +09:00

171 lines
3.7 KiB
C

/*
* arch/sh/kernel/cpu/sh4/clock-sh4-202.c
*
* Additional SH4-202 support for the clock framework
*
* Copyright (C) 2005 Paul Mundt
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <asm/clock.h>
#include <asm/freq.h>
#include <asm/io.h>
#define CPG2_FRQCR3 0xfe0a0018
static int frqcr3_divisors[] = { 1, 2, 3, 4, 6, 8, 16 };
static int frqcr3_values[] = { 0, 1, 2, 3, 4, 5, 6 };
static unsigned long emi_clk_recalc(struct clk *clk)
{
int idx = ctrl_inl(CPG2_FRQCR3) & 0x0007;
return clk->parent->rate / frqcr3_divisors[idx];
}
static inline int frqcr3_lookup(struct clk *clk, unsigned long rate)
{
int divisor = clk->parent->rate / rate;
int i;
for (i = 0; i < ARRAY_SIZE(frqcr3_divisors); i++)
if (frqcr3_divisors[i] == divisor)
return frqcr3_values[i];
/* Safe fallback */
return 5;
}
static struct clk_ops sh4202_emi_clk_ops = {
.recalc = emi_clk_recalc,
};
static struct clk sh4202_emi_clk = {
.name = "emi_clk",
.flags = CLK_ENABLE_ON_INIT,
.ops = &sh4202_emi_clk_ops,
};
static unsigned long femi_clk_recalc(struct clk *clk)
{
int idx = (ctrl_inl(CPG2_FRQCR3) >> 3) & 0x0007;
return clk->parent->rate / frqcr3_divisors[idx];
}
static struct clk_ops sh4202_femi_clk_ops = {
.recalc = femi_clk_recalc,
};
static struct clk sh4202_femi_clk = {
.name = "femi_clk",
.flags = CLK_ENABLE_ON_INIT,
.ops = &sh4202_femi_clk_ops,
};
static void shoc_clk_init(struct clk *clk)
{
int i;
/*
* For some reason, the shoc_clk seems to be set to some really
* insane value at boot (values outside of the allowable frequency
* range for instance). We deal with this by scaling it back down
* to something sensible just in case.
*
* Start scaling from the high end down until we find something
* that passes rate verification..
*/
for (i = 0; i < ARRAY_SIZE(frqcr3_divisors); i++) {
int divisor = frqcr3_divisors[i];
if (clk->ops->set_rate(clk, clk->parent->rate /
divisor, 0) == 0)
break;
}
WARN_ON(i == ARRAY_SIZE(frqcr3_divisors)); /* Undefined clock */
}
static unsigned long shoc_clk_recalc(struct clk *clk)
{
int idx = (ctrl_inl(CPG2_FRQCR3) >> 6) & 0x0007;
return clk->parent->rate / frqcr3_divisors[idx];
}
static int shoc_clk_verify_rate(struct clk *clk, unsigned long rate)
{
struct clk *bclk = clk_get(NULL, "bus_clk");
unsigned long bclk_rate = clk_get_rate(bclk);
clk_put(bclk);
if (rate > bclk_rate)
return 1;
if (rate > 66000000)
return 1;
return 0;
}
static int shoc_clk_set_rate(struct clk *clk, unsigned long rate, int algo_id)
{
unsigned long frqcr3;
unsigned int tmp;
/* Make sure we have something sensible to switch to */
if (shoc_clk_verify_rate(clk, rate) != 0)
return -EINVAL;
tmp = frqcr3_lookup(clk, rate);
frqcr3 = ctrl_inl(CPG2_FRQCR3);
frqcr3 &= ~(0x0007 << 6);
frqcr3 |= tmp << 6;
ctrl_outl(frqcr3, CPG2_FRQCR3);
clk->rate = clk->parent->rate / frqcr3_divisors[tmp];
return 0;
}
static struct clk_ops sh4202_shoc_clk_ops = {
.init = shoc_clk_init,
.recalc = shoc_clk_recalc,
.set_rate = shoc_clk_set_rate,
};
static struct clk sh4202_shoc_clk = {
.name = "shoc_clk",
.flags = CLK_ENABLE_ON_INIT,
.ops = &sh4202_shoc_clk_ops,
};
static struct clk *sh4202_onchip_clocks[] = {
&sh4202_emi_clk,
&sh4202_femi_clk,
&sh4202_shoc_clk,
};
int __init arch_clk_init(void)
{
struct clk *clk;
int i, ret = 0;
cpg_clk_init();
clk = clk_get(NULL, "master_clk");
for (i = 0; i < ARRAY_SIZE(sh4202_onchip_clocks); i++) {
struct clk *clkp = sh4202_onchip_clocks[i];
clkp->parent = clk;
ret |= clk_register(clkp);
}
clk_put(clk);
return ret;
}