forked from Minki/linux
6d803ba736
factorise some generic infrastructure to assist looking up struct clks for the ARM & SH architecture. as the code is identical at 99% put the arch specific code for allocation as example in asm/clkdev.h Signed-off-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Acked-by: Paul Mundt <lethal@linux-sh.org> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
1135 lines
24 KiB
C
1135 lines
24 KiB
C
/*
|
|
* Clock manipulation routines for Freescale STMP37XX/STMP378X
|
|
*
|
|
* Author: Vitaly Wool <vital@embeddedalley.com>
|
|
*
|
|
* Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
* Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* The code contained herein is licensed under the GNU General Public
|
|
* License. You may obtain a copy of the GNU General Public License
|
|
* Version 2 or later at the following locations:
|
|
*
|
|
* http://www.opensource.org/licenses/gpl-license.html
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
#define DEBUG
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/clkdev.h>
|
|
|
|
#include <asm/mach-types.h>
|
|
#include <mach/platform.h>
|
|
#include <mach/regs-clkctrl.h>
|
|
|
|
#include "clock.h"
|
|
|
|
static DEFINE_SPINLOCK(clocks_lock);
|
|
|
|
static struct clk osc_24M;
|
|
static struct clk pll_clk;
|
|
static struct clk cpu_clk;
|
|
static struct clk hclk;
|
|
|
|
static int propagate_rate(struct clk *);
|
|
|
|
static inline int clk_is_busy(struct clk *clk)
|
|
{
|
|
return __raw_readl(clk->busy_reg) & (1 << clk->busy_bit);
|
|
}
|
|
|
|
static inline int clk_good(struct clk *clk)
|
|
{
|
|
return clk && !IS_ERR(clk) && clk->ops;
|
|
}
|
|
|
|
static int std_clk_enable(struct clk *clk)
|
|
{
|
|
if (clk->enable_reg) {
|
|
u32 clk_reg = __raw_readl(clk->enable_reg);
|
|
if (clk->enable_negate)
|
|
clk_reg &= ~(1 << clk->enable_shift);
|
|
else
|
|
clk_reg |= (1 << clk->enable_shift);
|
|
__raw_writel(clk_reg, clk->enable_reg);
|
|
if (clk->enable_wait)
|
|
udelay(clk->enable_wait);
|
|
return 0;
|
|
} else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int std_clk_disable(struct clk *clk)
|
|
{
|
|
if (clk->enable_reg) {
|
|
u32 clk_reg = __raw_readl(clk->enable_reg);
|
|
if (clk->enable_negate)
|
|
clk_reg |= (1 << clk->enable_shift);
|
|
else
|
|
clk_reg &= ~(1 << clk->enable_shift);
|
|
__raw_writel(clk_reg, clk->enable_reg);
|
|
return 0;
|
|
} else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int io_set_rate(struct clk *clk, u32 rate)
|
|
{
|
|
u32 reg_frac, clkctrl_frac;
|
|
int i, ret = 0, mask = 0x1f;
|
|
|
|
clkctrl_frac = (clk->parent->rate * 18 + rate - 1) / rate;
|
|
|
|
if (clkctrl_frac < 18 || clkctrl_frac > 35) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
reg_frac = __raw_readl(clk->scale_reg);
|
|
reg_frac &= ~(mask << clk->scale_shift);
|
|
__raw_writel(reg_frac | (clkctrl_frac << clk->scale_shift),
|
|
clk->scale_reg);
|
|
if (clk->busy_reg) {
|
|
for (i = 10000; i; i--)
|
|
if (!clk_is_busy(clk))
|
|
break;
|
|
if (!i)
|
|
ret = -ETIMEDOUT;
|
|
else
|
|
ret = 0;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static long io_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate * 18;
|
|
int mask = 0x1f;
|
|
|
|
rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask;
|
|
clk->rate = rate;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static long per_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate;
|
|
long div;
|
|
const int mask = 0xff;
|
|
|
|
if (clk->enable_reg &&
|
|
!(__raw_readl(clk->enable_reg) & clk->enable_shift))
|
|
clk->rate = 0;
|
|
else {
|
|
div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask;
|
|
if (div)
|
|
rate /= div;
|
|
clk->rate = rate;
|
|
}
|
|
|
|
return clk->rate;
|
|
}
|
|
|
|
static int per_set_rate(struct clk *clk, u32 rate)
|
|
{
|
|
int ret = -EINVAL;
|
|
int div = (clk->parent->rate + rate - 1) / rate;
|
|
u32 reg_frac;
|
|
const int mask = 0xff;
|
|
int try = 10;
|
|
int i = -1;
|
|
|
|
if (div == 0 || div > mask)
|
|
goto out;
|
|
|
|
reg_frac = __raw_readl(clk->scale_reg);
|
|
reg_frac &= ~(mask << clk->scale_shift);
|
|
|
|
while (try--) {
|
|
__raw_writel(reg_frac | (div << clk->scale_shift),
|
|
clk->scale_reg);
|
|
|
|
if (clk->busy_reg) {
|
|
for (i = 10000; i; i--)
|
|
if (!clk_is_busy(clk))
|
|
break;
|
|
}
|
|
if (i)
|
|
break;
|
|
}
|
|
|
|
if (!i)
|
|
ret = -ETIMEDOUT;
|
|
else
|
|
ret = 0;
|
|
|
|
out:
|
|
if (ret != 0)
|
|
printk(KERN_ERR "%s: error %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static long lcdif_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate;
|
|
long div;
|
|
const int mask = 0xff;
|
|
|
|
div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask;
|
|
if (div) {
|
|
rate /= div;
|
|
div = (__raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC) &
|
|
BM_CLKCTRL_FRAC_PIXFRAC) >> BP_CLKCTRL_FRAC_PIXFRAC;
|
|
rate /= div;
|
|
}
|
|
clk->rate = rate;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static int lcdif_set_rate(struct clk *clk, u32 rate)
|
|
{
|
|
int ret = 0;
|
|
/*
|
|
* On 3700, we can get most timings exact by modifying ref_pix
|
|
* and the divider, but keeping the phase timings at 1 (2
|
|
* phases per cycle).
|
|
*
|
|
* ref_pix can be between 480e6*18/35=246.9MHz and 480e6*18/18=480MHz,
|
|
* which is between 18/(18*480e6)=2.084ns and 35/(18*480e6)=4.050ns.
|
|
*
|
|
* ns_cycle >= 2*18e3/(18*480) = 25/6
|
|
* ns_cycle <= 2*35e3/(18*480) = 875/108
|
|
*
|
|
* Multiply the ns_cycle by 'div' to lengthen it until it fits the
|
|
* bounds. This is the divider we'll use after ref_pix.
|
|
*
|
|
* 6 * ns_cycle >= 25 * div
|
|
* 108 * ns_cycle <= 875 * div
|
|
*/
|
|
u32 ns_cycle = 1000000 / rate;
|
|
u32 div, reg_val;
|
|
u32 lowest_result = (u32) -1;
|
|
u32 lowest_div = 0, lowest_fracdiv = 0;
|
|
|
|
for (div = 1; div < 256; ++div) {
|
|
u32 fracdiv;
|
|
u32 ps_result;
|
|
int lower_bound = 6 * ns_cycle >= 25 * div;
|
|
int upper_bound = 108 * ns_cycle <= 875 * div;
|
|
if (!lower_bound)
|
|
break;
|
|
if (!upper_bound)
|
|
continue;
|
|
/*
|
|
* Found a matching div. Calculate fractional divider needed,
|
|
* rounded up.
|
|
*/
|
|
fracdiv = ((clk->parent->rate / 1000 * 18 / 2) *
|
|
ns_cycle + 1000 * div - 1) /
|
|
(1000 * div);
|
|
if (fracdiv < 18 || fracdiv > 35) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
/* Calculate the actual cycle time this results in */
|
|
ps_result = 6250 * div * fracdiv / 27;
|
|
|
|
/* Use the fastest result that doesn't break ns_cycle */
|
|
if (ps_result <= lowest_result) {
|
|
lowest_result = ps_result;
|
|
lowest_div = div;
|
|
lowest_fracdiv = fracdiv;
|
|
}
|
|
}
|
|
|
|
if (div >= 256 || lowest_result == (u32) -1) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
pr_debug("Programming PFD=%u,DIV=%u ref_pix=%uMHz "
|
|
"PIXCLK=%uMHz cycle=%u.%03uns\n",
|
|
lowest_fracdiv, lowest_div,
|
|
480*18/lowest_fracdiv, 480*18/lowest_fracdiv/lowest_div,
|
|
lowest_result / 1000, lowest_result % 1000);
|
|
|
|
/* Program ref_pix phase fractional divider */
|
|
reg_val = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC);
|
|
reg_val &= ~BM_CLKCTRL_FRAC_PIXFRAC;
|
|
reg_val |= BF(lowest_fracdiv, CLKCTRL_FRAC_PIXFRAC);
|
|
__raw_writel(reg_val, REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC);
|
|
|
|
/* Ungate PFD */
|
|
stmp3xxx_clearl(BM_CLKCTRL_FRAC_CLKGATEPIX,
|
|
REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC);
|
|
|
|
/* Program pix divider */
|
|
reg_val = __raw_readl(clk->scale_reg);
|
|
reg_val &= ~(BM_CLKCTRL_PIX_DIV | BM_CLKCTRL_PIX_CLKGATE);
|
|
reg_val |= BF(lowest_div, CLKCTRL_PIX_DIV);
|
|
__raw_writel(reg_val, clk->scale_reg);
|
|
|
|
/* Wait for divider update */
|
|
if (clk->busy_reg) {
|
|
int i;
|
|
for (i = 10000; i; i--)
|
|
if (!clk_is_busy(clk))
|
|
break;
|
|
if (!i) {
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Switch to ref_pix source */
|
|
reg_val = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ);
|
|
reg_val &= ~BM_CLKCTRL_CLKSEQ_BYPASS_PIX;
|
|
__raw_writel(reg_val, REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int cpu_set_rate(struct clk *clk, u32 rate)
|
|
{
|
|
u32 reg_val;
|
|
|
|
if (rate < 24000)
|
|
return -EINVAL;
|
|
else if (rate == 24000) {
|
|
/* switch to the 24M source */
|
|
clk_set_parent(clk, &osc_24M);
|
|
} else {
|
|
int i;
|
|
u32 clkctrl_cpu = 1;
|
|
u32 c = clkctrl_cpu;
|
|
u32 clkctrl_frac = 1;
|
|
u32 val;
|
|
for ( ; c < 0x40; c++) {
|
|
u32 f = (pll_clk.rate*18/c + rate/2) / rate;
|
|
int s1, s2;
|
|
|
|
if (f < 18 || f > 35)
|
|
continue;
|
|
s1 = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu - rate;
|
|
s2 = pll_clk.rate*18/c/f - rate;
|
|
pr_debug("%s: s1 %d, s2 %d\n", __func__, s1, s2);
|
|
if (abs(s1) > abs(s2)) {
|
|
clkctrl_cpu = c;
|
|
clkctrl_frac = f;
|
|
}
|
|
if (s2 == 0)
|
|
break;
|
|
};
|
|
pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__,
|
|
clkctrl_cpu, clkctrl_frac);
|
|
if (c == 0x40) {
|
|
int d = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu -
|
|
rate;
|
|
if (abs(d) > 100 ||
|
|
clkctrl_frac < 18 || clkctrl_frac > 35)
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 4.6.2 */
|
|
val = __raw_readl(clk->scale_reg);
|
|
val &= ~(0x3f << clk->scale_shift);
|
|
val |= clkctrl_frac;
|
|
clk_set_parent(clk, &osc_24M);
|
|
udelay(10);
|
|
__raw_writel(val, clk->scale_reg);
|
|
/* ungate */
|
|
__raw_writel(1<<7, clk->scale_reg + 8);
|
|
/* write clkctrl_cpu */
|
|
clk->saved_div = clkctrl_cpu;
|
|
|
|
reg_val = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);
|
|
reg_val &= ~0x3F;
|
|
reg_val |= clkctrl_cpu;
|
|
__raw_writel(reg_val, REGS_CLKCTRL_BASE + 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;
|
|
}
|
|
clk_set_parent(clk, &pll_clk);
|
|
clk->saved_div = 0;
|
|
udelay(10);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static long cpu_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate * 18;
|
|
|
|
rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f;
|
|
rate /= __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU) & 0x3f;
|
|
rate = ((rate + 9) / 10) * 10;
|
|
clk->rate = rate;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static long cpu_round_rate(struct clk *clk, u32 rate)
|
|
{
|
|
unsigned long r = 0;
|
|
|
|
if (rate <= 24000)
|
|
r = 24000;
|
|
else {
|
|
u32 clkctrl_cpu = 1;
|
|
u32 clkctrl_frac;
|
|
do {
|
|
clkctrl_frac =
|
|
(pll_clk.rate*18 / clkctrl_cpu + rate/2) / rate;
|
|
if (clkctrl_frac > 35)
|
|
continue;
|
|
if (pll_clk.rate*18 / clkctrl_frac / clkctrl_cpu/10 ==
|
|
rate / 10)
|
|
break;
|
|
} while (pll_clk.rate / 2 >= clkctrl_cpu++ * rate);
|
|
if (pll_clk.rate / 2 < (clkctrl_cpu - 1) * rate)
|
|
clkctrl_cpu--;
|
|
pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__,
|
|
clkctrl_cpu, clkctrl_frac);
|
|
if (clkctrl_frac < 18)
|
|
clkctrl_frac = 18;
|
|
if (clkctrl_frac > 35)
|
|
clkctrl_frac = 35;
|
|
|
|
r = pll_clk.rate * 18;
|
|
r /= clkctrl_frac;
|
|
r /= clkctrl_cpu;
|
|
r = 10 * ((r + 9) / 10);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static long emi_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate * 18;
|
|
|
|
rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f;
|
|
rate /= __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_EMI) & 0x3f;
|
|
clk->rate = rate;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static int clkseq_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
int ret = -EINVAL;
|
|
int shift = 8;
|
|
|
|
/* bypass? */
|
|
if (parent == &osc_24M)
|
|
shift = 4;
|
|
|
|
if (clk->bypass_reg) {
|
|
#ifdef CONFIG_ARCH_STMP378X
|
|
u32 hbus_val, cpu_val;
|
|
|
|
if (clk == &cpu_clk && shift == 4) {
|
|
hbus_val = __raw_readl(REGS_CLKCTRL_BASE +
|
|
HW_CLKCTRL_HBUS);
|
|
cpu_val = __raw_readl(REGS_CLKCTRL_BASE +
|
|
HW_CLKCTRL_CPU);
|
|
|
|
hbus_val &= ~(BM_CLKCTRL_HBUS_DIV_FRAC_EN |
|
|
BM_CLKCTRL_HBUS_DIV);
|
|
clk->saved_div = cpu_val & BM_CLKCTRL_CPU_DIV_CPU;
|
|
cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU;
|
|
cpu_val |= 1;
|
|
|
|
if (machine_is_stmp378x()) {
|
|
__raw_writel(hbus_val,
|
|
REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS);
|
|
__raw_writel(cpu_val,
|
|
REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);
|
|
hclk.rate = 0;
|
|
}
|
|
} else if (clk == &cpu_clk && shift == 8) {
|
|
hbus_val = __raw_readl(REGS_CLKCTRL_BASE +
|
|
HW_CLKCTRL_HBUS);
|
|
cpu_val = __raw_readl(REGS_CLKCTRL_BASE +
|
|
HW_CLKCTRL_CPU);
|
|
hbus_val &= ~(BM_CLKCTRL_HBUS_DIV_FRAC_EN |
|
|
BM_CLKCTRL_HBUS_DIV);
|
|
hbus_val |= 2;
|
|
cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU;
|
|
if (clk->saved_div)
|
|
cpu_val |= clk->saved_div;
|
|
else
|
|
cpu_val |= 2;
|
|
|
|
if (machine_is_stmp378x()) {
|
|
__raw_writel(hbus_val,
|
|
REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS);
|
|
__raw_writel(cpu_val,
|
|
REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU);
|
|
hclk.rate = 0;
|
|
}
|
|
}
|
|
#endif
|
|
__raw_writel(1 << clk->bypass_shift, clk->bypass_reg + shift);
|
|
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hbus_set_rate(struct clk *clk, u32 rate)
|
|
{
|
|
u8 div = 0;
|
|
int is_frac = 0;
|
|
u32 clkctrl_hbus;
|
|
struct clk *parent = clk->parent;
|
|
|
|
pr_debug("%s: rate %d, parent rate %d\n", __func__, rate,
|
|
parent->rate);
|
|
|
|
if (rate > parent->rate)
|
|
return -EINVAL;
|
|
|
|
if (((parent->rate + rate/2) / rate) * rate != parent->rate &&
|
|
parent->rate / rate < 32) {
|
|
pr_debug("%s: switching to fractional mode\n", __func__);
|
|
is_frac = 1;
|
|
}
|
|
|
|
if (is_frac)
|
|
div = (32 * rate + parent->rate / 2) / parent->rate;
|
|
else
|
|
div = (parent->rate + rate - 1) / rate;
|
|
pr_debug("%s: div calculated is %d\n", __func__, div);
|
|
if (!div || div > 0x1f)
|
|
return -EINVAL;
|
|
|
|
clk_set_parent(&cpu_clk, &osc_24M);
|
|
udelay(10);
|
|
clkctrl_hbus = __raw_readl(clk->scale_reg);
|
|
clkctrl_hbus &= ~0x3f;
|
|
clkctrl_hbus |= div;
|
|
clkctrl_hbus |= (is_frac << 5);
|
|
|
|
__raw_writel(clkctrl_hbus, clk->scale_reg);
|
|
if (clk->busy_reg) {
|
|
int i;
|
|
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;
|
|
}
|
|
}
|
|
clk_set_parent(&cpu_clk, &pll_clk);
|
|
__raw_writel(clkctrl_hbus, clk->scale_reg);
|
|
udelay(10);
|
|
return 0;
|
|
}
|
|
|
|
static long hbus_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate;
|
|
|
|
if (__raw_readl(clk->scale_reg) & 0x20) {
|
|
rate *= __raw_readl(clk->scale_reg) & 0x1f;
|
|
rate /= 32;
|
|
} else
|
|
rate /= __raw_readl(clk->scale_reg) & 0x1f;
|
|
clk->rate = rate;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static int xbus_set_rate(struct clk *clk, u32 rate)
|
|
{
|
|
u16 div = 0;
|
|
u32 clkctrl_xbus;
|
|
|
|
pr_debug("%s: rate %d, parent rate %d\n", __func__, rate,
|
|
clk->parent->rate);
|
|
|
|
div = (clk->parent->rate + rate - 1) / rate;
|
|
pr_debug("%s: div calculated is %d\n", __func__, div);
|
|
if (!div || div > 0x3ff)
|
|
return -EINVAL;
|
|
|
|
clkctrl_xbus = __raw_readl(clk->scale_reg);
|
|
clkctrl_xbus &= ~0x3ff;
|
|
clkctrl_xbus |= div;
|
|
__raw_writel(clkctrl_xbus, clk->scale_reg);
|
|
if (clk->busy_reg) {
|
|
int i;
|
|
for (i = 10000; i; i--)
|
|
if (!clk_is_busy(clk))
|
|
break;
|
|
if (!i) {
|
|
printk(KERN_ERR "couldn't set up xbus divisor\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static long xbus_get_rate(struct clk *clk)
|
|
{
|
|
long rate = clk->parent->rate;
|
|
|
|
rate /= __raw_readl(clk->scale_reg) & 0x3ff;
|
|
clk->rate = rate;
|
|
|
|
return rate;
|
|
}
|
|
|
|
|
|
/* Clock ops */
|
|
|
|
static struct clk_ops std_ops = {
|
|
.enable = std_clk_enable,
|
|
.disable = std_clk_disable,
|
|
.get_rate = per_get_rate,
|
|
.set_rate = per_set_rate,
|
|
.set_parent = clkseq_set_parent,
|
|
};
|
|
|
|
static struct clk_ops min_ops = {
|
|
.enable = std_clk_enable,
|
|
.disable = std_clk_disable,
|
|
};
|
|
|
|
static struct clk_ops cpu_ops = {
|
|
.enable = std_clk_enable,
|
|
.disable = std_clk_disable,
|
|
.get_rate = cpu_get_rate,
|
|
.set_rate = cpu_set_rate,
|
|
.round_rate = cpu_round_rate,
|
|
.set_parent = clkseq_set_parent,
|
|
};
|
|
|
|
static struct clk_ops io_ops = {
|
|
.enable = std_clk_enable,
|
|
.disable = std_clk_disable,
|
|
.get_rate = io_get_rate,
|
|
.set_rate = io_set_rate,
|
|
};
|
|
|
|
static struct clk_ops hbus_ops = {
|
|
.get_rate = hbus_get_rate,
|
|
.set_rate = hbus_set_rate,
|
|
};
|
|
|
|
static struct clk_ops xbus_ops = {
|
|
.get_rate = xbus_get_rate,
|
|
.set_rate = xbus_set_rate,
|
|
};
|
|
|
|
static struct clk_ops lcdif_ops = {
|
|
.enable = std_clk_enable,
|
|
.disable = std_clk_disable,
|
|
.get_rate = lcdif_get_rate,
|
|
.set_rate = lcdif_set_rate,
|
|
.set_parent = clkseq_set_parent,
|
|
};
|
|
|
|
static struct clk_ops emi_ops = {
|
|
.get_rate = emi_get_rate,
|
|
};
|
|
|
|
/* List of on-chip clocks */
|
|
|
|
static struct clk osc_24M = {
|
|
.flags = FIXED_RATE | ENABLED,
|
|
.rate = 24000,
|
|
};
|
|
|
|
static struct clk pll_clk = {
|
|
.parent = &osc_24M,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0,
|
|
.enable_shift = 16,
|
|
.enable_wait = 10,
|
|
.flags = FIXED_RATE | ENABLED,
|
|
.rate = 480000,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk cpu_clk = {
|
|
.parent = &pll_clk,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
|
|
.scale_shift = 0,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 7,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU,
|
|
.busy_bit = 28,
|
|
.flags = RATE_PROPAGATES | ENABLED,
|
|
.ops = &cpu_ops,
|
|
};
|
|
|
|
static struct clk io_clk = {
|
|
.parent = &pll_clk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
|
|
.scale_shift = 24,
|
|
.flags = RATE_PROPAGATES | ENABLED,
|
|
.ops = &io_ops,
|
|
};
|
|
|
|
static struct clk hclk = {
|
|
.parent = &cpu_clk,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 7,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS,
|
|
.busy_bit = 29,
|
|
.flags = RATE_PROPAGATES | ENABLED,
|
|
.ops = &hbus_ops,
|
|
};
|
|
|
|
static struct clk xclk = {
|
|
.parent = &osc_24M,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XBUS,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XBUS,
|
|
.busy_bit = 31,
|
|
.flags = RATE_PROPAGATES | ENABLED,
|
|
.ops = &xbus_ops,
|
|
};
|
|
|
|
static struct clk uart_clk = {
|
|
.parent = &xclk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.flags = ENABLED,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk audio_clk = {
|
|
.parent = &xclk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
|
|
.enable_shift = 30,
|
|
.enable_negate = 1,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk pwm_clk = {
|
|
.parent = &xclk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
|
|
.enable_shift = 29,
|
|
.enable_negate = 1,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk dri_clk = {
|
|
.parent = &xclk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
|
|
.enable_shift = 28,
|
|
.enable_negate = 1,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk digctl_clk = {
|
|
.parent = &xclk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
|
|
.enable_shift = 27,
|
|
.enable_negate = 1,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk timer_clk = {
|
|
.parent = &xclk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_XTAL,
|
|
.enable_shift = 26,
|
|
.enable_negate = 1,
|
|
.flags = ENABLED,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk lcdif_clk = {
|
|
.parent = &pll_clk,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PIX,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PIX,
|
|
.busy_bit = 29,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PIX,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 1,
|
|
.flags = NEEDS_SET_PARENT,
|
|
.ops = &lcdif_ops,
|
|
};
|
|
|
|
static struct clk ssp_clk = {
|
|
.parent = &io_clk,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SSP,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SSP,
|
|
.busy_bit = 29,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SSP,
|
|
.enable_shift = 31,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 5,
|
|
.enable_negate = 1,
|
|
.flags = NEEDS_SET_PARENT,
|
|
.ops = &std_ops,
|
|
};
|
|
|
|
static struct clk gpmi_clk = {
|
|
.parent = &io_clk,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_GPMI,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_GPMI,
|
|
.busy_bit = 29,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_GPMI,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 4,
|
|
.flags = NEEDS_SET_PARENT,
|
|
.ops = &std_ops,
|
|
};
|
|
|
|
static struct clk spdif_clk = {
|
|
.parent = &pll_clk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SPDIF,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk emi_clk = {
|
|
.parent = &pll_clk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_EMI,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC,
|
|
.scale_shift = 8,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_EMI,
|
|
.busy_bit = 28,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 6,
|
|
.flags = ENABLED,
|
|
.ops = &emi_ops,
|
|
};
|
|
|
|
static struct clk ir_clk = {
|
|
.parent = &io_clk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_IR,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 3,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
static struct clk saif_clk = {
|
|
.parent = &pll_clk,
|
|
.scale_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SAIF,
|
|
.busy_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SAIF,
|
|
.busy_bit = 29,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_SAIF,
|
|
.enable_shift = 31,
|
|
.enable_negate = 1,
|
|
.bypass_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ,
|
|
.bypass_shift = 0,
|
|
.ops = &std_ops,
|
|
};
|
|
|
|
static struct clk usb_clk = {
|
|
.parent = &pll_clk,
|
|
.enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0,
|
|
.enable_shift = 18,
|
|
.enable_negate = 1,
|
|
.ops = &min_ops,
|
|
};
|
|
|
|
/* list of all the clocks */
|
|
static struct clk_lookup onchip_clks[] = {
|
|
{
|
|
.con_id = "osc_24M",
|
|
.clk = &osc_24M,
|
|
}, {
|
|
.con_id = "pll",
|
|
.clk = &pll_clk,
|
|
}, {
|
|
.con_id = "cpu",
|
|
.clk = &cpu_clk,
|
|
}, {
|
|
.con_id = "hclk",
|
|
.clk = &hclk,
|
|
}, {
|
|
.con_id = "xclk",
|
|
.clk = &xclk,
|
|
}, {
|
|
.con_id = "io",
|
|
.clk = &io_clk,
|
|
}, {
|
|
.con_id = "uart",
|
|
.clk = &uart_clk,
|
|
}, {
|
|
.con_id = "audio",
|
|
.clk = &audio_clk,
|
|
}, {
|
|
.con_id = "pwm",
|
|
.clk = &pwm_clk,
|
|
}, {
|
|
.con_id = "dri",
|
|
.clk = &dri_clk,
|
|
}, {
|
|
.con_id = "digctl",
|
|
.clk = &digctl_clk,
|
|
}, {
|
|
.con_id = "timer",
|
|
.clk = &timer_clk,
|
|
}, {
|
|
.con_id = "lcdif",
|
|
.clk = &lcdif_clk,
|
|
}, {
|
|
.con_id = "ssp",
|
|
.clk = &ssp_clk,
|
|
}, {
|
|
.con_id = "gpmi",
|
|
.clk = &gpmi_clk,
|
|
}, {
|
|
.con_id = "spdif",
|
|
.clk = &spdif_clk,
|
|
}, {
|
|
.con_id = "emi",
|
|
.clk = &emi_clk,
|
|
}, {
|
|
.con_id = "ir",
|
|
.clk = &ir_clk,
|
|
}, {
|
|
.con_id = "saif",
|
|
.clk = &saif_clk,
|
|
}, {
|
|
.con_id = "usb",
|
|
.clk = &usb_clk,
|
|
},
|
|
};
|
|
|
|
static int __init propagate_rate(struct clk *clk)
|
|
{
|
|
struct clk_lookup *cl;
|
|
|
|
for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks);
|
|
cl++) {
|
|
if (unlikely(!clk_good(cl->clk)))
|
|
continue;
|
|
if (cl->clk->parent == clk && cl->clk->ops->get_rate) {
|
|
cl->clk->ops->get_rate(cl->clk);
|
|
if (cl->clk->flags & RATE_PROPAGATES)
|
|
propagate_rate(cl->clk);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Exported API */
|
|
unsigned long clk_get_rate(struct clk *clk)
|
|
{
|
|
if (unlikely(!clk_good(clk)))
|
|
return 0;
|
|
|
|
if (clk->rate != 0)
|
|
return clk->rate;
|
|
|
|
if (clk->ops->get_rate != NULL)
|
|
return clk->ops->get_rate(clk);
|
|
|
|
return clk_get_rate(clk->parent);
|
|
}
|
|
EXPORT_SYMBOL(clk_get_rate);
|
|
|
|
long clk_round_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
if (unlikely(!clk_good(clk)))
|
|
return 0;
|
|
|
|
if (clk->ops->round_rate)
|
|
return clk->ops->round_rate(clk, rate);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_round_rate);
|
|
|
|
static inline int close_enough(long rate1, long rate2)
|
|
{
|
|
return rate1 && !((rate2 - rate1) * 1000 / rate1);
|
|
}
|
|
|
|
int clk_set_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
int ret = -EINVAL;
|
|
|
|
if (unlikely(!clk_good(clk)))
|
|
goto out;
|
|
|
|
if (clk->flags & FIXED_RATE || !clk->ops->set_rate)
|
|
goto out;
|
|
|
|
else if (!close_enough(clk->rate, rate)) {
|
|
ret = clk->ops->set_rate(clk, rate);
|
|
if (ret < 0)
|
|
goto out;
|
|
clk->rate = rate;
|
|
if (clk->flags & RATE_PROPAGATES)
|
|
propagate_rate(clk);
|
|
} else
|
|
ret = 0;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_rate);
|
|
|
|
int clk_enable(struct clk *clk)
|
|
{
|
|
unsigned long clocks_flags;
|
|
|
|
if (unlikely(!clk_good(clk)))
|
|
return -EINVAL;
|
|
|
|
if (clk->parent)
|
|
clk_enable(clk->parent);
|
|
|
|
spin_lock_irqsave(&clocks_lock, clocks_flags);
|
|
|
|
clk->usage++;
|
|
if (clk->ops && clk->ops->enable)
|
|
clk->ops->enable(clk);
|
|
|
|
spin_unlock_irqrestore(&clocks_lock, clocks_flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_enable);
|
|
|
|
static void local_clk_disable(struct clk *clk)
|
|
{
|
|
if (unlikely(!clk_good(clk)))
|
|
return;
|
|
|
|
if (clk->usage == 0 && clk->ops->disable)
|
|
clk->ops->disable(clk);
|
|
|
|
if (clk->parent)
|
|
local_clk_disable(clk->parent);
|
|
}
|
|
|
|
void clk_disable(struct clk *clk)
|
|
{
|
|
unsigned long clocks_flags;
|
|
|
|
if (unlikely(!clk_good(clk)))
|
|
return;
|
|
|
|
spin_lock_irqsave(&clocks_lock, clocks_flags);
|
|
|
|
if ((--clk->usage) == 0 && clk->ops->disable)
|
|
clk->ops->disable(clk);
|
|
|
|
spin_unlock_irqrestore(&clocks_lock, clocks_flags);
|
|
if (clk->parent)
|
|
clk_disable(clk->parent);
|
|
}
|
|
EXPORT_SYMBOL(clk_disable);
|
|
|
|
/* Some additional API */
|
|
int clk_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
int ret = -ENODEV;
|
|
unsigned long clocks_flags;
|
|
|
|
if (unlikely(!clk_good(clk)))
|
|
goto out;
|
|
|
|
if (!clk->ops->set_parent)
|
|
goto out;
|
|
|
|
spin_lock_irqsave(&clocks_lock, clocks_flags);
|
|
|
|
ret = clk->ops->set_parent(clk, parent);
|
|
if (!ret) {
|
|
/* disable if usage count is 0 */
|
|
local_clk_disable(parent);
|
|
|
|
parent->usage += clk->usage;
|
|
clk->parent->usage -= clk->usage;
|
|
|
|
/* disable if new usage count is 0 */
|
|
local_clk_disable(clk->parent);
|
|
|
|
clk->parent = parent;
|
|
}
|
|
spin_unlock_irqrestore(&clocks_lock, clocks_flags);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_parent);
|
|
|
|
struct clk *clk_get_parent(struct clk *clk)
|
|
{
|
|
if (unlikely(!clk_good(clk)))
|
|
return NULL;
|
|
return clk->parent;
|
|
}
|
|
EXPORT_SYMBOL(clk_get_parent);
|
|
|
|
static int __init clk_init(void)
|
|
{
|
|
struct clk_lookup *cl;
|
|
struct clk_ops *ops;
|
|
|
|
spin_lock_init(&clocks_lock);
|
|
|
|
for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks);
|
|
cl++) {
|
|
if (cl->clk->flags & ENABLED)
|
|
clk_enable(cl->clk);
|
|
else
|
|
local_clk_disable(cl->clk);
|
|
|
|
ops = cl->clk->ops;
|
|
|
|
if ((cl->clk->flags & NEEDS_INITIALIZATION) &&
|
|
ops && ops->set_rate)
|
|
ops->set_rate(cl->clk, cl->clk->rate);
|
|
|
|
if (cl->clk->flags & FIXED_RATE) {
|
|
if (cl->clk->flags & RATE_PROPAGATES)
|
|
propagate_rate(cl->clk);
|
|
} else {
|
|
if (ops && ops->get_rate)
|
|
ops->get_rate(cl->clk);
|
|
}
|
|
|
|
if (cl->clk->flags & NEEDS_SET_PARENT) {
|
|
if (ops && ops->set_parent)
|
|
ops->set_parent(cl->clk, cl->clk->parent);
|
|
}
|
|
}
|
|
clkdev_add_table(onchip_clks, ARRAY_SIZE(onchip_clks));
|
|
return 0;
|
|
}
|
|
|
|
arch_initcall(clk_init);
|