bf7d57b67f
Signed-off-by: Florian Fainelli <florian@openwrt.org> SVN-Revision: 45273
272 lines
5.4 KiB
C
272 lines
5.4 KiB
C
/*
|
|
* Moschip MCS814x clock routines
|
|
*
|
|
* Copyright (C) 2012, Florian Fainelli <florian@openwrt.org>
|
|
*
|
|
* Licensed under GPLv2
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/export.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/clkdev.h>
|
|
#include <linux/clk.h>
|
|
|
|
#include <mach/mcs814x.h>
|
|
|
|
#include "common.h"
|
|
|
|
#define KHZ 1000
|
|
#define MHZ (KHZ * KHZ)
|
|
|
|
struct clk_ops {
|
|
unsigned long (*get_rate)(struct clk *clk);
|
|
int (*set_rate)(struct clk *clk, unsigned long rate);
|
|
struct clk *(*get_parent)(struct clk *clk);
|
|
int (*enable)(struct clk *clk, int enable);
|
|
};
|
|
|
|
struct clk {
|
|
struct clk *parent; /* parent clk */
|
|
unsigned long rate; /* clock rate in Hz */
|
|
unsigned long divider; /* clock divider */
|
|
u32 usecount; /* reference count */
|
|
struct clk_ops *ops; /* clock operation */
|
|
u32 enable_reg; /* clock enable register */
|
|
u32 enable_mask; /* clock enable mask */
|
|
};
|
|
|
|
static unsigned long clk_divide_parent(struct clk *clk)
|
|
{
|
|
if (clk->parent && clk->divider)
|
|
return clk_get_rate(clk->parent) / clk->divider;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int clk_local_onoff_enable(struct clk *clk, int enable)
|
|
{
|
|
u32 tmp;
|
|
|
|
/* no enable_reg means the clock is always enabled */
|
|
if (!clk->enable_reg)
|
|
return 0;
|
|
|
|
tmp = readl_relaxed(mcs814x_sysdbg_base + clk->enable_reg);
|
|
if (!enable)
|
|
tmp &= ~clk->enable_mask;
|
|
else
|
|
tmp |= clk->enable_mask;
|
|
|
|
writel_relaxed(tmp, mcs814x_sysdbg_base + clk->enable_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct clk_ops default_clk_ops = {
|
|
.get_rate = clk_divide_parent,
|
|
.enable = clk_local_onoff_enable,
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(clocks_lock);
|
|
|
|
static const unsigned long cpu_freq_table[] = {
|
|
175000,
|
|
300000,
|
|
125000,
|
|
137500,
|
|
212500,
|
|
250000,
|
|
162500,
|
|
187500,
|
|
162500,
|
|
150000,
|
|
225000,
|
|
237500,
|
|
200000,
|
|
262500,
|
|
275000,
|
|
287500
|
|
};
|
|
|
|
static struct clk clk_cpu;
|
|
|
|
/* System clock is fixed at 50Mhz */
|
|
static struct clk clk_sys = {
|
|
.rate = 50 * MHZ,
|
|
};
|
|
|
|
static struct clk clk_sdram;
|
|
|
|
static struct clk clk_timer0 = {
|
|
.parent = &clk_sdram,
|
|
.divider = 2,
|
|
.ops = &default_clk_ops,
|
|
};
|
|
|
|
static struct clk clk_timer1_2 = {
|
|
.parent = &clk_sys,
|
|
};
|
|
|
|
/* Watchdog clock is system clock / 128 */
|
|
static struct clk clk_wdt = {
|
|
.parent = &clk_sys,
|
|
.divider = 128,
|
|
.ops = &default_clk_ops,
|
|
};
|
|
|
|
static struct clk clk_emac = {
|
|
.ops = &default_clk_ops,
|
|
.enable_reg = SYSDBG_SYSCTL,
|
|
.enable_mask = SYSCTL_EMAC,
|
|
};
|
|
|
|
static struct clk clk_ephy = {
|
|
.ops = &default_clk_ops,
|
|
.enable_reg = SYSDBG_PLL_CTL,
|
|
.enable_mask = ~SYSCTL_EPHY, /* active low */
|
|
};
|
|
|
|
static struct clk clk_cipher = {
|
|
.ops = &default_clk_ops,
|
|
.enable_reg = SYSDBG_SYSCTL,
|
|
.enable_mask = SYSCTL_CIPHER,
|
|
};
|
|
|
|
#define CLK(_dev, _con, _clk) \
|
|
{ .dev_id = (_dev), .con_id = (_con), .clk = (_clk) },
|
|
|
|
static struct clk_lookup mcs814x_chip_clks[] = {
|
|
CLK("cpu", NULL, &clk_cpu)
|
|
CLK("sys", NULL, &clk_sys)
|
|
CLK("sdram", NULL, &clk_sdram)
|
|
/* 32-bits timer0 */
|
|
CLK("timer0", NULL, &clk_timer0)
|
|
/* 16-bits timer1 */
|
|
CLK("timer1", NULL, &clk_timer1_2)
|
|
/* 64-bits timer2, same as timer 1 */
|
|
CLK("timer2", NULL, &clk_timer1_2)
|
|
CLK(NULL, "wdt", &clk_wdt)
|
|
CLK(NULL, "emac", &clk_emac)
|
|
CLK(NULL, "ephy", &clk_ephy)
|
|
CLK(NULL, "cipher", &clk_cipher)
|
|
};
|
|
|
|
static void local_clk_disable(struct clk *clk)
|
|
{
|
|
WARN_ON(!clk->usecount);
|
|
|
|
if (clk->usecount > 0) {
|
|
clk->usecount--;
|
|
|
|
if ((clk->usecount == 0) && (clk->ops->enable))
|
|
clk->ops->enable(clk, 0);
|
|
|
|
if (clk->parent)
|
|
local_clk_disable(clk->parent);
|
|
}
|
|
}
|
|
|
|
static int local_clk_enable(struct clk *clk)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (clk->parent)
|
|
ret = local_clk_enable(clk->parent);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((clk->usecount == 0) && (clk->ops->enable))
|
|
ret = clk->ops->enable(clk, 1);
|
|
|
|
if (!ret)
|
|
clk->usecount++;
|
|
else if (clk->parent && clk->parent->ops->enable)
|
|
local_clk_disable(clk->parent);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int clk_enable(struct clk *clk)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
ret = local_clk_enable(clk);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(clk_enable);
|
|
|
|
void clk_disable(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
local_clk_disable(clk);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(clk_disable);
|
|
|
|
unsigned long clk_get_rate(struct clk *clk)
|
|
{
|
|
if (unlikely(IS_ERR_OR_NULL(clk)))
|
|
return 0;
|
|
|
|
if (clk->rate)
|
|
return clk->rate;
|
|
|
|
if (clk->ops && clk->ops->get_rate)
|
|
return clk->ops->get_rate(clk);
|
|
|
|
return clk_get_rate(clk->parent);
|
|
}
|
|
EXPORT_SYMBOL(clk_get_rate);
|
|
|
|
struct clk *clk_get_parent(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (unlikely(IS_ERR_OR_NULL(clk)))
|
|
return NULL;
|
|
|
|
if (!clk->ops || !clk->ops->get_parent)
|
|
return clk->parent;
|
|
|
|
spin_lock_irqsave(&clocks_lock, flags);
|
|
clk->parent = clk->ops->get_parent(clk);
|
|
spin_unlock_irqrestore(&clocks_lock, flags);
|
|
|
|
return clk->parent;
|
|
}
|
|
EXPORT_SYMBOL(clk_get_parent);
|
|
|
|
void __init mcs814x_clk_init(void)
|
|
{
|
|
u32 bs1;
|
|
u8 cpu_freq;
|
|
|
|
clkdev_add_table(mcs814x_chip_clks, ARRAY_SIZE(mcs814x_chip_clks));
|
|
|
|
/* read the bootstrap registers to know the exact clocking scheme */
|
|
bs1 = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS1);
|
|
cpu_freq = (bs1 >> CPU_FREQ_SHIFT) & CPU_FREQ_MASK;
|
|
|
|
pr_info("CPU frequency: %lu (kHz)\n", cpu_freq_table[cpu_freq]);
|
|
clk_cpu.rate = cpu_freq * KHZ;
|
|
|
|
/* read SDRAM frequency */
|
|
if (bs1 & SDRAM_FREQ_BIT)
|
|
clk_sdram.rate = 100 * MHZ;
|
|
else
|
|
clk_sdram.rate = 133 * MHZ;
|
|
|
|
pr_info("SDRAM frequency: %lu (MHz)\n", clk_sdram.rate / MHZ);
|
|
}
|
|
|