blob: 07bf13e65998c939acd04ca50466ce2e28e9b1d9 [file] [log] [blame]
/*
* Marvell Berlin SoC clk driver
* Author: Jisheng Zhang <jszhang@marvell.com>
* Copyright: Marvell International Ltd.
*
* 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/export.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <mach/galois_platform.h>
#include "clock.h"
static inline u32 rdlc(int offset)
{
return __raw_readl(IOMEM(MEMMAP_CHIP_CTRL_REG_BASE + offset));
}
static u32 vcodiv[] = {10, 15, 20, 25, 30, 40, 50, 60, 80};
static u32 clkdiv[] = {1, 2, 4, 6, 8, 12, 1, 1};
static inline u32 cpu0_get_divider(void)
{
u32 val;
val = rdlc(RA_GBL_CLKSWITCH);
if ((val >> 8) & 0x1)
return 3;
if ((val >> 7) & 0x1) {
u32 clksel = (rdlc(RA_GBL_CLKSELECT) >> 9) & 0x7;
return clkdiv[clksel];
} else
return 1;
}
static inline u32 cfg_get_divider(void)
{
u32 val;
val = rdlc(RA_GBL_CLKSWITCH);
if ((val >> 17) & 0x1)
return 3;
if ((val >> 16) & 0x1) {
u32 clksel = (rdlc(RA_GBL_CLKSELECT) >> 26) & 0x7;
return clkdiv[clksel];
} else
return 1;
}
static inline u32 perif_get_divider(void)
{
u32 val;
val = rdlc(RA_GBL_CLKSWITCH);
if ((val >> 26) & 0x1)
return 3;
if ((val >> 25) & 0x1) {
u32 clksel = (rdlc(RA_GBL_CLKSELECT1) >> 12) & 0x7;
return clkdiv[clksel];
} else
return 1;
}
static inline u32 sdioxin_get_divider(void)
{
u32 val;
val = rdlc(RA_GBL_SDIOXINCLKCTRL);
if ((val >> 6) & 0x1)
return 3;
if ((val >> 5) & 0x1) {
u32 clksel = (val >> 7) & 0x7;
return clkdiv[clksel];
} else
return 1;
}
static inline u32 sdio1xin_get_divider(void)
{
u32 val;
val = rdlc(RA_GBL_SDIO1XINCLKCTRL);
if ((val >> 6) & 0x1)
return 3;
if ((val >> 5) & 0x1) {
u32 clksel = (val >> 7) & 0x7;
return clkdiv[clksel];
} else
return 1;
}
static inline u32 nfcecc_get_divider(void)
{
u32 val;
val = rdlc(RA_GBL_CLKSWITCH1);
if ((val >> 3) & 0x1)
return 3;
if ((val >> 2) & 0x1) {
u32 clksel = rdlc(RA_GBL_CLKSELECT2) & 0x7;
return clkdiv[clksel];
} else
return 1;
}
/* input clock is 25MHZ */
#define CLKIN 25
unsigned long get_pll(struct clk *clk)
{
u32 val, fbdiv, rfdiv, vcodivsel;
unsigned long pll;
val = rdlc(clk->ctl);
fbdiv = (val >> 6) & 0x1ff;
rfdiv = (val >> 1) & 0x1f;
if (rfdiv == 0)
rfdiv = 1;
pll = CLKIN * fbdiv / rfdiv;
val = rdlc(clk->ctl1);
vcodivsel = (val >> 7) & 0xf;
pll = pll * 10 / vcodiv[vcodivsel];
return pll;
}
static unsigned long twd_get_rate(struct clk *clk)
{
unsigned long pll = get_pll(clk);
u32 divider = cpu0_get_divider();
return 1000000 * pll / divider / 3;
}
static struct clkops twd_clk_ops = {
.getrate = twd_get_rate,
};
static struct clk twd_clk = {
.ctl = RA_GBL_CPUPLLCTL,
.ctl1 = RA_GBL_CPUPLLCTL1,
.ops = &twd_clk_ops,
};
static unsigned long cfg_get_rate(struct clk *clk)
{
unsigned long pll = get_pll(clk);
u32 divider = cfg_get_divider();
return 1000000*pll/divider;
}
static struct clkops cfg_clk_ops = {
.getrate = cfg_get_rate,
};
static struct clk cfg_clk = {
.ctl = RA_GBL_SYSPLLCTL,
.ctl1 = RA_GBL_SYSPLLCTL1,
.ops = &cfg_clk_ops,
};
CLK(cpu0, RA_GBL_CPUPLLCTL, RA_GBL_CPUPLLCTL1);
CLK(perif, RA_GBL_SYSPLLCTL, RA_GBL_SYSPLLCTL1);
CLK(sdioxin, RA_GBL_SYSPLLCTL, RA_GBL_SYSPLLCTL1);
CLK(sdio1xin, RA_GBL_SYSPLLCTL, RA_GBL_SYSPLLCTL1);
CLK(nfcecc, RA_GBL_SYSPLLCTL, RA_GBL_SYSPLLCTL1);
static struct clk_lookup clks[] = {
CLK_LOOKUP(NULL, "cpu0", cpu0_clk),
CLK_LOOKUP(NULL, "cfg", cfg_clk),
CLK_LOOKUP(NULL, "perif", perif_clk),
CLK_LOOKUP("smp_twd", NULL, twd_clk),
CLK_LOOKUP("f7ab0000.sdhci", NULL, sdioxin_clk),
CLK_LOOKUP("f7ab0800.sdhci", NULL, sdio1xin_clk),
CLK_LOOKUP("f7ab1000.sdhci", NULL, nfcecc_clk),
CLK_LOOKUP("f7e81400.i2c", NULL, cfg_clk),
CLK_LOOKUP("f7e81800.i2c", NULL, cfg_clk),
CLK_LOOKUP("f7fc7000.i2c", NULL, cfg_clk),
CLK_LOOKUP("f7fc8000.i2c", NULL, cfg_clk),
};
void __init berlin_clk_init(void)
{
clkdev_add_table(clks, ARRAY_SIZE(clks));
}
#if 0
int clk_enable(struct clk *clk)
{
return 0;
}
EXPORT_SYMBOL(clk_enable);
void clk_disable(struct clk *clk)
{
}
EXPORT_SYMBOL(clk_disable);
#endif
unsigned long clk_get_rate(struct clk *clk)
{
unsigned long rate;
if (clk->ops->getrate)
rate = clk->ops->getrate(clk);
else
rate = 0;
return rate;
}
EXPORT_SYMBOL(clk_get_rate);