You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1076 lines
24 KiB
1076 lines
24 KiB
/*
|
|
* linux/arch/arm/mach-omap2/clock.c
|
|
*
|
|
* Copyright (C) 2005 Texas Instruments Inc.
|
|
* Richard Woodruff <r-woodruff2@ti.com>
|
|
* Created for OMAP2.
|
|
*
|
|
* Cleaned up and modified to use omap shared clock framework by
|
|
* Tony Lindgren <tony@atomide.com>
|
|
*
|
|
* Based on omap1 clock.c, Copyright (C) 2004 - 2005 Nokia corporation
|
|
* Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com>
|
|
*
|
|
* 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/config.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/device.h>
|
|
#include <linux/list.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/clk.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/arch/clock.h>
|
|
#include <asm/arch/sram.h>
|
|
|
|
#include "prcm-regs.h"
|
|
#include "memory.h"
|
|
#include "clock.h"
|
|
|
|
//#define DOWN_VARIABLE_DPLL 1 /* Experimental */
|
|
|
|
static struct prcm_config *curr_prcm_set;
|
|
static u32 curr_perf_level = PRCM_FULL_SPEED;
|
|
|
|
/*-------------------------------------------------------------------------
|
|
* Omap2 specific clock functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/* Recalculate SYST_CLK */
|
|
static void omap2_sys_clk_recalc(struct clk * clk)
|
|
{
|
|
u32 div = PRCM_CLKSRC_CTRL;
|
|
div &= (1 << 7) | (1 << 6); /* Test if ext clk divided by 1 or 2 */
|
|
div >>= clk->rate_offset;
|
|
clk->rate = (clk->parent->rate / div);
|
|
propagate_rate(clk);
|
|
}
|
|
|
|
static u32 omap2_get_dpll_rate(struct clk * tclk)
|
|
{
|
|
long long dpll_clk;
|
|
int dpll_mult, dpll_div, amult;
|
|
|
|
dpll_mult = (CM_CLKSEL1_PLL >> 12) & 0x03ff; /* 10 bits */
|
|
dpll_div = (CM_CLKSEL1_PLL >> 8) & 0x0f; /* 4 bits */
|
|
dpll_clk = (long long)tclk->parent->rate * dpll_mult;
|
|
do_div(dpll_clk, dpll_div + 1);
|
|
amult = CM_CLKSEL2_PLL & 0x3;
|
|
dpll_clk *= amult;
|
|
|
|
return dpll_clk;
|
|
}
|
|
|
|
static void omap2_followparent_recalc(struct clk *clk)
|
|
{
|
|
followparent_recalc(clk);
|
|
}
|
|
|
|
static void omap2_propagate_rate(struct clk * clk)
|
|
{
|
|
if (!(clk->flags & RATE_FIXED))
|
|
clk->rate = clk->parent->rate;
|
|
|
|
propagate_rate(clk);
|
|
}
|
|
|
|
/* Enable an APLL if off */
|
|
static void omap2_clk_fixed_enable(struct clk *clk)
|
|
{
|
|
u32 cval, i=0;
|
|
|
|
if (clk->enable_bit == 0xff) /* Parent will do it */
|
|
return;
|
|
|
|
cval = CM_CLKEN_PLL;
|
|
|
|
if ((cval & (0x3 << clk->enable_bit)) == (0x3 << clk->enable_bit))
|
|
return;
|
|
|
|
cval &= ~(0x3 << clk->enable_bit);
|
|
cval |= (0x3 << clk->enable_bit);
|
|
CM_CLKEN_PLL = cval;
|
|
|
|
if (clk == &apll96_ck)
|
|
cval = (1 << 8);
|
|
else if (clk == &apll54_ck)
|
|
cval = (1 << 6);
|
|
|
|
while (!CM_IDLEST_CKGEN & cval) { /* Wait for lock */
|
|
++i;
|
|
udelay(1);
|
|
if (i == 100000)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Enables clock without considering parent dependencies or use count
|
|
* REVISIT: Maybe change this to use clk->enable like on omap1?
|
|
*/
|
|
static int _omap2_clk_enable(struct clk * clk)
|
|
{
|
|
u32 regval32;
|
|
|
|
if (clk->flags & ALWAYS_ENABLED)
|
|
return 0;
|
|
|
|
if (unlikely(clk->enable_reg == 0)) {
|
|
printk(KERN_ERR "clock.c: Enable for %s without enable code\n",
|
|
clk->name);
|
|
return 0;
|
|
}
|
|
|
|
if (clk->enable_reg == (void __iomem *)&CM_CLKEN_PLL) {
|
|
omap2_clk_fixed_enable(clk);
|
|
return 0;
|
|
}
|
|
|
|
regval32 = __raw_readl(clk->enable_reg);
|
|
regval32 |= (1 << clk->enable_bit);
|
|
__raw_writel(regval32, clk->enable_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Stop APLL */
|
|
static void omap2_clk_fixed_disable(struct clk *clk)
|
|
{
|
|
u32 cval;
|
|
|
|
if(clk->enable_bit == 0xff) /* let parent off do it */
|
|
return;
|
|
|
|
cval = CM_CLKEN_PLL;
|
|
cval &= ~(0x3 << clk->enable_bit);
|
|
CM_CLKEN_PLL = cval;
|
|
}
|
|
|
|
/* Disables clock without considering parent dependencies or use count */
|
|
static void _omap2_clk_disable(struct clk *clk)
|
|
{
|
|
u32 regval32;
|
|
|
|
if (clk->enable_reg == 0)
|
|
return;
|
|
|
|
if (clk->enable_reg == (void __iomem *)&CM_CLKEN_PLL) {
|
|
omap2_clk_fixed_disable(clk);
|
|
return;
|
|
}
|
|
|
|
regval32 = __raw_readl(clk->enable_reg);
|
|
regval32 &= ~(1 << clk->enable_bit);
|
|
__raw_writel(regval32, clk->enable_reg);
|
|
}
|
|
|
|
static int omap2_clk_enable(struct clk *clk)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (clk->usecount++ == 0) {
|
|
if (likely((u32)clk->parent))
|
|
ret = omap2_clk_enable(clk->parent);
|
|
|
|
if (unlikely(ret != 0)) {
|
|
clk->usecount--;
|
|
return ret;
|
|
}
|
|
|
|
ret = _omap2_clk_enable(clk);
|
|
|
|
if (unlikely(ret != 0) && clk->parent) {
|
|
omap2_clk_disable(clk->parent);
|
|
clk->usecount--;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void omap2_clk_disable(struct clk *clk)
|
|
{
|
|
if (clk->usecount > 0 && !(--clk->usecount)) {
|
|
_omap2_clk_disable(clk);
|
|
if (likely((u32)clk->parent))
|
|
omap2_clk_disable(clk->parent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Uses the current prcm set to tell if a rate is valid.
|
|
* You can go slower, but not faster within a given rate set.
|
|
*/
|
|
static u32 omap2_dpll_round_rate(unsigned long target_rate)
|
|
{
|
|
u32 high, low;
|
|
|
|
if ((CM_CLKSEL2_PLL & 0x3) == 1) { /* DPLL clockout */
|
|
high = curr_prcm_set->dpll_speed * 2;
|
|
low = curr_prcm_set->dpll_speed;
|
|
} else { /* DPLL clockout x 2 */
|
|
high = curr_prcm_set->dpll_speed;
|
|
low = curr_prcm_set->dpll_speed / 2;
|
|
}
|
|
|
|
#ifdef DOWN_VARIABLE_DPLL
|
|
if (target_rate > high)
|
|
return high;
|
|
else
|
|
return target_rate;
|
|
#else
|
|
if (target_rate > low)
|
|
return high;
|
|
else
|
|
return low;
|
|
#endif
|
|
|
|
}
|
|
|
|
/*
|
|
* Used for clocks that are part of CLKSEL_xyz governed clocks.
|
|
* REVISIT: Maybe change to use clk->enable() functions like on omap1?
|
|
*/
|
|
static void omap2_clksel_recalc(struct clk * clk)
|
|
{
|
|
u32 fixed = 0, div = 0;
|
|
|
|
if (clk == &dpll_ck) {
|
|
clk->rate = omap2_get_dpll_rate(clk);
|
|
fixed = 1;
|
|
div = 0;
|
|
}
|
|
|
|
if (clk == &iva1_mpu_int_ifck) {
|
|
div = 2;
|
|
fixed = 1;
|
|
}
|
|
|
|
if ((clk == &dss1_fck) && ((CM_CLKSEL1_CORE & (0x1f << 8)) == 0)) {
|
|
clk->rate = sys_ck.rate;
|
|
return;
|
|
}
|
|
|
|
if (!fixed) {
|
|
div = omap2_clksel_get_divisor(clk);
|
|
if (div == 0)
|
|
return;
|
|
}
|
|
|
|
if (div != 0) {
|
|
if (unlikely(clk->rate == clk->parent->rate / div))
|
|
return;
|
|
clk->rate = clk->parent->rate / div;
|
|
}
|
|
|
|
if (unlikely(clk->flags & RATE_PROPAGATES))
|
|
propagate_rate(clk);
|
|
}
|
|
|
|
/*
|
|
* Finds best divider value in an array based on the source and target
|
|
* rates. The divider array must be sorted with smallest divider first.
|
|
*/
|
|
static inline u32 omap2_divider_from_table(u32 size, u32 *div_array,
|
|
u32 src_rate, u32 tgt_rate)
|
|
{
|
|
int i, test_rate;
|
|
|
|
if (div_array == NULL)
|
|
return ~1;
|
|
|
|
for (i=0; i < size; i++) {
|
|
test_rate = src_rate / *div_array;
|
|
if (test_rate <= tgt_rate)
|
|
return *div_array;
|
|
++div_array;
|
|
}
|
|
|
|
return ~0; /* No acceptable divider */
|
|
}
|
|
|
|
/*
|
|
* Find divisor for the given clock and target rate.
|
|
*
|
|
* Note that this will not work for clocks which are part of CONFIG_PARTICIPANT,
|
|
* they are only settable as part of virtual_prcm set.
|
|
*/
|
|
static u32 omap2_clksel_round_rate(struct clk *tclk, u32 target_rate,
|
|
u32 *new_div)
|
|
{
|
|
u32 gfx_div[] = {2, 3, 4};
|
|
u32 sysclkout_div[] = {1, 2, 4, 8, 16};
|
|
u32 dss1_div[] = {1, 2, 3, 4, 5, 6, 8, 9, 12, 16};
|
|
u32 vylnq_div[] = {1, 2, 3, 4, 6, 8, 9, 12, 16, 18};
|
|
u32 best_div = ~0, asize = 0;
|
|
u32 *div_array = NULL;
|
|
|
|
switch (tclk->flags & SRC_RATE_SEL_MASK) {
|
|
case CM_GFX_SEL1:
|
|
asize = 3;
|
|
div_array = gfx_div;
|
|
break;
|
|
case CM_PLL_SEL1:
|
|
return omap2_dpll_round_rate(target_rate);
|
|
case CM_SYSCLKOUT_SEL1:
|
|
asize = 5;
|
|
div_array = sysclkout_div;
|
|
break;
|
|
case CM_CORE_SEL1:
|
|
if(tclk == &dss1_fck){
|
|
if(tclk->parent == &core_ck){
|
|
asize = 10;
|
|
div_array = dss1_div;
|
|
} else {
|
|
*new_div = 0; /* fixed clk */
|
|
return(tclk->parent->rate);
|
|
}
|
|
} else if((tclk == &vlynq_fck) && cpu_is_omap2420()){
|
|
if(tclk->parent == &core_ck){
|
|
asize = 10;
|
|
div_array = vylnq_div;
|
|
} else {
|
|
*new_div = 0; /* fixed clk */
|
|
return(tclk->parent->rate);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
best_div = omap2_divider_from_table(asize, div_array,
|
|
tclk->parent->rate, target_rate);
|
|
if (best_div == ~0){
|
|
*new_div = 1;
|
|
return best_div; /* signal error */
|
|
}
|
|
|
|
*new_div = best_div;
|
|
return (tclk->parent->rate / best_div);
|
|
}
|
|
|
|
/* Given a clock and a rate apply a clock specific rounding function */
|
|
static long omap2_clk_round_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
u32 new_div = 0;
|
|
int valid_rate;
|
|
|
|
if (clk->flags & RATE_FIXED)
|
|
return clk->rate;
|
|
|
|
if (clk->flags & RATE_CKCTL) {
|
|
valid_rate = omap2_clksel_round_rate(clk, rate, &new_div);
|
|
return valid_rate;
|
|
}
|
|
|
|
if (clk->round_rate != 0)
|
|
return clk->round_rate(clk, rate);
|
|
|
|
return clk->rate;
|
|
}
|
|
|
|
/*
|
|
* Check the DLL lock state, and return tue if running in unlock mode.
|
|
* This is needed to compenste for the shifted DLL value in unlock mode.
|
|
*/
|
|
static u32 omap2_dll_force_needed(void)
|
|
{
|
|
u32 dll_state = SDRC_DLLA_CTRL; /* dlla and dllb are a set */
|
|
|
|
if ((dll_state & (1 << 2)) == (1 << 2))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static u32 omap2_reprogram_sdrc(u32 level, u32 force)
|
|
{
|
|
u32 slow_dll_ctrl, fast_dll_ctrl, m_type;
|
|
u32 prev = curr_perf_level, flags;
|
|
|
|
if ((curr_perf_level == level) && !force)
|
|
return prev;
|
|
|
|
m_type = omap2_memory_get_type();
|
|
slow_dll_ctrl = omap2_memory_get_slow_dll_ctrl();
|
|
fast_dll_ctrl = omap2_memory_get_fast_dll_ctrl();
|
|
|
|
if (level == PRCM_HALF_SPEED) {
|
|
local_irq_save(flags);
|
|
PRCM_VOLTSETUP = 0xffff;
|
|
omap2_sram_reprogram_sdrc(PRCM_HALF_SPEED,
|
|
slow_dll_ctrl, m_type);
|
|
curr_perf_level = PRCM_HALF_SPEED;
|
|
local_irq_restore(flags);
|
|
}
|
|
if (level == PRCM_FULL_SPEED) {
|
|
local_irq_save(flags);
|
|
PRCM_VOLTSETUP = 0xffff;
|
|
omap2_sram_reprogram_sdrc(PRCM_FULL_SPEED,
|
|
fast_dll_ctrl, m_type);
|
|
curr_perf_level = PRCM_FULL_SPEED;
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
return prev;
|
|
}
|
|
|
|
static int omap2_reprogram_dpll(struct clk * clk, unsigned long rate)
|
|
{
|
|
u32 flags, cur_rate, low, mult, div, valid_rate, done_rate;
|
|
u32 bypass = 0;
|
|
struct prcm_config tmpset;
|
|
int ret = -EINVAL;
|
|
|
|
local_irq_save(flags);
|
|
cur_rate = omap2_get_dpll_rate(&dpll_ck);
|
|
mult = CM_CLKSEL2_PLL & 0x3;
|
|
|
|
if ((rate == (cur_rate / 2)) && (mult == 2)) {
|
|
omap2_reprogram_sdrc(PRCM_HALF_SPEED, 1);
|
|
} else if ((rate == (cur_rate * 2)) && (mult == 1)) {
|
|
omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1);
|
|
} else if (rate != cur_rate) {
|
|
valid_rate = omap2_dpll_round_rate(rate);
|
|
if (valid_rate != rate)
|
|
goto dpll_exit;
|
|
|
|
if ((CM_CLKSEL2_PLL & 0x3) == 1)
|
|
low = curr_prcm_set->dpll_speed;
|
|
else
|
|
low = curr_prcm_set->dpll_speed / 2;
|
|
|
|
tmpset.cm_clksel1_pll = CM_CLKSEL1_PLL;
|
|
tmpset.cm_clksel1_pll &= ~(0x3FFF << 8);
|
|
div = ((curr_prcm_set->xtal_speed / 1000000) - 1);
|
|
tmpset.cm_clksel2_pll = CM_CLKSEL2_PLL;
|
|
tmpset.cm_clksel2_pll &= ~0x3;
|
|
if (rate > low) {
|
|
tmpset.cm_clksel2_pll |= 0x2;
|
|
mult = ((rate / 2) / 1000000);
|
|
done_rate = PRCM_FULL_SPEED;
|
|
} else {
|
|
tmpset.cm_clksel2_pll |= 0x1;
|
|
mult = (rate / 1000000);
|
|
done_rate = PRCM_HALF_SPEED;
|
|
}
|
|
tmpset.cm_clksel1_pll |= ((div << 8) | (mult << 12));
|
|
|
|
/* Worst case */
|
|
tmpset.base_sdrc_rfr = V24XX_SDRC_RFR_CTRL_BYPASS;
|
|
|
|
if (rate == curr_prcm_set->xtal_speed) /* If asking for 1-1 */
|
|
bypass = 1;
|
|
|
|
omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1); /* For init_mem */
|
|
|
|
/* Force dll lock mode */
|
|
omap2_set_prcm(tmpset.cm_clksel1_pll, tmpset.base_sdrc_rfr,
|
|
bypass);
|
|
|
|
/* Errata: ret dll entry state */
|
|
omap2_init_memory_params(omap2_dll_force_needed());
|
|
omap2_reprogram_sdrc(done_rate, 0);
|
|
}
|
|
omap2_clksel_recalc(&dpll_ck);
|
|
ret = 0;
|
|
|
|
dpll_exit:
|
|
local_irq_restore(flags);
|
|
return(ret);
|
|
}
|
|
|
|
/* Just return the MPU speed */
|
|
static void omap2_mpu_recalc(struct clk * clk)
|
|
{
|
|
clk->rate = curr_prcm_set->mpu_speed;
|
|
}
|
|
|
|
/*
|
|
* Look for a rate equal or less than the target rate given a configuration set.
|
|
*
|
|
* What's not entirely clear is "which" field represents the key field.
|
|
* Some might argue L3-DDR, others ARM, others IVA. This code is simple and
|
|
* just uses the ARM rates.
|
|
*/
|
|
static long omap2_round_to_table_rate(struct clk * clk, unsigned long rate)
|
|
{
|
|
struct prcm_config * ptr;
|
|
long highest_rate;
|
|
|
|
if (clk != &virt_prcm_set)
|
|
return -EINVAL;
|
|
|
|
highest_rate = -EINVAL;
|
|
|
|
for (ptr = rate_table; ptr->mpu_speed; ptr++) {
|
|
if (ptr->xtal_speed != sys_ck.rate)
|
|
continue;
|
|
|
|
highest_rate = ptr->mpu_speed;
|
|
|
|
/* Can check only after xtal frequency check */
|
|
if (ptr->mpu_speed <= rate)
|
|
break;
|
|
}
|
|
return highest_rate;
|
|
}
|
|
|
|
/*
|
|
* omap2_convert_field_to_div() - turn field value into integer divider
|
|
*/
|
|
static u32 omap2_clksel_to_divisor(u32 div_sel, u32 field_val)
|
|
{
|
|
u32 i;
|
|
u32 clkout_array[] = {1, 2, 4, 8, 16};
|
|
|
|
if ((div_sel & SRC_RATE_SEL_MASK) == CM_SYSCLKOUT_SEL1) {
|
|
for (i = 0; i < 5; i++) {
|
|
if (field_val == i)
|
|
return clkout_array[i];
|
|
}
|
|
return ~0;
|
|
} else
|
|
return field_val;
|
|
}
|
|
|
|
/*
|
|
* Returns the CLKSEL divider register value
|
|
* REVISIT: This should be cleaned up to work nicely with void __iomem *
|
|
*/
|
|
static u32 omap2_get_clksel(u32 *div_sel, u32 *field_mask,
|
|
struct clk *clk)
|
|
{
|
|
int ret = ~0;
|
|
u32 reg_val, div_off;
|
|
u32 div_addr = 0;
|
|
u32 mask = ~0;
|
|
|
|
div_off = clk->rate_offset;
|
|
|
|
switch ((*div_sel & SRC_RATE_SEL_MASK)) {
|
|
case CM_MPU_SEL1:
|
|
div_addr = (u32)&CM_CLKSEL_MPU;
|
|
mask = 0x1f;
|
|
break;
|
|
case CM_DSP_SEL1:
|
|
div_addr = (u32)&CM_CLKSEL_DSP;
|
|
if (cpu_is_omap2420()) {
|
|
if ((div_off == 0) || (div_off == 8))
|
|
mask = 0x1f;
|
|
else if (div_off == 5)
|
|
mask = 0x3;
|
|
} else if (cpu_is_omap2430()) {
|
|
if (div_off == 0)
|
|
mask = 0x1f;
|
|
else if (div_off == 5)
|
|
mask = 0x3;
|
|
}
|
|
break;
|
|
case CM_GFX_SEL1:
|
|
div_addr = (u32)&CM_CLKSEL_GFX;
|
|
if (div_off == 0)
|
|
mask = 0x7;
|
|
break;
|
|
case CM_MODEM_SEL1:
|
|
div_addr = (u32)&CM_CLKSEL_MDM;
|
|
if (div_off == 0)
|
|
mask = 0xf;
|
|
break;
|
|
case CM_SYSCLKOUT_SEL1:
|
|
div_addr = (u32)&PRCM_CLKOUT_CTRL;
|
|
if ((div_off == 3) || (div_off = 11))
|
|
mask= 0x3;
|
|
break;
|
|
case CM_CORE_SEL1:
|
|
div_addr = (u32)&CM_CLKSEL1_CORE;
|
|
switch (div_off) {
|
|
case 0: /* l3 */
|
|
case 8: /* dss1 */
|
|
case 15: /* vylnc-2420 */
|
|
case 20: /* ssi */
|
|
mask = 0x1f; break;
|
|
case 5: /* l4 */
|
|
mask = 0x3; break;
|
|
case 13: /* dss2 */
|
|
mask = 0x1; break;
|
|
case 25: /* usb */
|
|
mask = 0x7; break;
|
|
}
|
|
}
|
|
|
|
*field_mask = mask;
|
|
|
|
if (unlikely(mask == ~0))
|
|
div_addr = 0;
|
|
|
|
*div_sel = div_addr;
|
|
|
|
if (unlikely(div_addr == 0))
|
|
return ret;
|
|
|
|
/* Isolate field */
|
|
reg_val = __raw_readl((void __iomem *)div_addr) & (mask << div_off);
|
|
|
|
/* Normalize back to divider value */
|
|
reg_val >>= div_off;
|
|
|
|
return reg_val;
|
|
}
|
|
|
|
/*
|
|
* Return divider to be applied to parent clock.
|
|
* Return 0 on error.
|
|
*/
|
|
static u32 omap2_clksel_get_divisor(struct clk *clk)
|
|
{
|
|
int ret = 0;
|
|
u32 div, div_sel, div_off, field_mask, field_val;
|
|
|
|
/* isolate control register */
|
|
div_sel = (SRC_RATE_SEL_MASK & clk->flags);
|
|
|
|
div_off = clk->rate_offset;
|
|
field_val = omap2_get_clksel(&div_sel, &field_mask, clk);
|
|
if (div_sel == 0)
|
|
return ret;
|
|
|
|
div_sel = (SRC_RATE_SEL_MASK & clk->flags);
|
|
div = omap2_clksel_to_divisor(div_sel, field_val);
|
|
|
|
return div;
|
|
}
|
|
|
|
/* Set the clock rate for a clock source */
|
|
static int omap2_clk_set_rate(struct clk *clk, unsigned long rate)
|
|
|
|
{
|
|
int ret = -EINVAL;
|
|
void __iomem * reg;
|
|
u32 div_sel, div_off, field_mask, field_val, reg_val, validrate;
|
|
u32 new_div = 0;
|
|
|
|
if (!(clk->flags & CONFIG_PARTICIPANT) && (clk->flags & RATE_CKCTL)) {
|
|
if (clk == &dpll_ck)
|
|
return omap2_reprogram_dpll(clk, rate);
|
|
|
|
/* Isolate control register */
|
|
div_sel = (SRC_RATE_SEL_MASK & clk->flags);
|
|
div_off = clk->src_offset;
|
|
|
|
validrate = omap2_clksel_round_rate(clk, rate, &new_div);
|
|
if(validrate != rate)
|
|
return(ret);
|
|
|
|
field_val = omap2_get_clksel(&div_sel, &field_mask, clk);
|
|
if (div_sel == 0)
|
|
return ret;
|
|
|
|
if(clk->flags & CM_SYSCLKOUT_SEL1){
|
|
switch(new_div){
|
|
case 16: field_val = 4; break;
|
|
case 8: field_val = 3; break;
|
|
case 4: field_val = 2; break;
|
|
case 2: field_val = 1; break;
|
|
case 1: field_val = 0; break;
|
|
}
|
|
}
|
|
else
|
|
field_val = new_div;
|
|
|
|
reg = (void __iomem *)div_sel;
|
|
|
|
reg_val = __raw_readl(reg);
|
|
reg_val &= ~(field_mask << div_off);
|
|
reg_val |= (field_val << div_off);
|
|
|
|
__raw_writel(reg_val, reg);
|
|
clk->rate = clk->parent->rate / field_val;
|
|
|
|
if (clk->flags & DELAYED_APP)
|
|
__raw_writel(0x1, (void __iomem *)&PRCM_CLKCFG_CTRL);
|
|
ret = 0;
|
|
} else if (clk->set_rate != 0)
|
|
ret = clk->set_rate(clk, rate);
|
|
|
|
if (unlikely(ret == 0 && (clk->flags & RATE_PROPAGATES)))
|
|
propagate_rate(clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Converts encoded control register address into a full address */
|
|
static u32 omap2_get_src_field(u32 *type_to_addr, u32 reg_offset,
|
|
struct clk *src_clk, u32 *field_mask)
|
|
{
|
|
u32 val = ~0, src_reg_addr = 0, mask = 0;
|
|
|
|
/* Find target control register.*/
|
|
switch ((*type_to_addr & SRC_RATE_SEL_MASK)) {
|
|
case CM_CORE_SEL1:
|
|
src_reg_addr = (u32)&CM_CLKSEL1_CORE;
|
|
if (reg_offset == 13) { /* DSS2_fclk */
|
|
mask = 0x1;
|
|
if (src_clk == &sys_ck)
|
|
val = 0;
|
|
if (src_clk == &func_48m_ck)
|
|
val = 1;
|
|
} else if (reg_offset == 8) { /* DSS1_fclk */
|
|
mask = 0x1f;
|
|
if (src_clk == &sys_ck)
|
|
val = 0;
|
|
else if (src_clk == &core_ck) /* divided clock */
|
|
val = 0x10; /* rate needs fixing */
|
|
} else if ((reg_offset == 15) && cpu_is_omap2420()){ /*vlnyq*/
|
|
mask = 0x1F;
|
|
if(src_clk == &func_96m_ck)
|
|
val = 0;
|
|
else if (src_clk == &core_ck)
|
|
val = 0x10;
|
|
}
|
|
break;
|
|
case CM_CORE_SEL2:
|
|
src_reg_addr = (u32)&CM_CLKSEL2_CORE;
|
|
mask = 0x3;
|
|
if (src_clk == &func_32k_ck)
|
|
val = 0x0;
|
|
if (src_clk == &sys_ck)
|
|
val = 0x1;
|
|
if (src_clk == &alt_ck)
|
|
val = 0x2;
|
|
break;
|
|
case CM_WKUP_SEL1:
|
|
src_reg_addr = (u32)&CM_CLKSEL2_CORE;
|
|
mask = 0x3;
|
|
if (src_clk == &func_32k_ck)
|
|
val = 0x0;
|
|
if (src_clk == &sys_ck)
|
|
val = 0x1;
|
|
if (src_clk == &alt_ck)
|
|
val = 0x2;
|
|
break;
|
|
case CM_PLL_SEL1:
|
|
src_reg_addr = (u32)&CM_CLKSEL1_PLL;
|
|
mask = 0x1;
|
|
if (reg_offset == 0x3) {
|
|
if (src_clk == &apll96_ck)
|
|
val = 0;
|
|
if (src_clk == &alt_ck)
|
|
val = 1;
|
|
}
|
|
else if (reg_offset == 0x5) {
|
|
if (src_clk == &apll54_ck)
|
|
val = 0;
|
|
if (src_clk == &alt_ck)
|
|
val = 1;
|
|
}
|
|
break;
|
|
case CM_PLL_SEL2:
|
|
src_reg_addr = (u32)&CM_CLKSEL2_PLL;
|
|
mask = 0x3;
|
|
if (src_clk == &func_32k_ck)
|
|
val = 0x0;
|
|
if (src_clk == &dpll_ck)
|
|
val = 0x2;
|
|
break;
|
|
case CM_SYSCLKOUT_SEL1:
|
|
src_reg_addr = (u32)&PRCM_CLKOUT_CTRL;
|
|
mask = 0x3;
|
|
if (src_clk == &dpll_ck)
|
|
val = 0;
|
|
if (src_clk == &sys_ck)
|
|
val = 1;
|
|
if (src_clk == &func_54m_ck)
|
|
val = 2;
|
|
if (src_clk == &func_96m_ck)
|
|
val = 3;
|
|
break;
|
|
}
|
|
|
|
if (val == ~0) /* Catch errors in offset */
|
|
*type_to_addr = 0;
|
|
else
|
|
*type_to_addr = src_reg_addr;
|
|
*field_mask = mask;
|
|
|
|
return val;
|
|
}
|
|
|
|
static int omap2_clk_set_parent(struct clk *clk, struct clk *new_parent)
|
|
{
|
|
void __iomem * reg;
|
|
u32 src_sel, src_off, field_val, field_mask, reg_val, rate;
|
|
int ret = -EINVAL;
|
|
|
|
if (unlikely(clk->flags & CONFIG_PARTICIPANT))
|
|
return ret;
|
|
|
|
if (clk->flags & SRC_SEL_MASK) { /* On-chip SEL collection */
|
|
src_sel = (SRC_RATE_SEL_MASK & clk->flags);
|
|
src_off = clk->src_offset;
|
|
|
|
if (src_sel == 0)
|
|
goto set_parent_error;
|
|
|
|
field_val = omap2_get_src_field(&src_sel, src_off, new_parent,
|
|
&field_mask);
|
|
|
|
reg = (void __iomem *)src_sel;
|
|
|
|
if (clk->usecount > 0)
|
|
_omap2_clk_disable(clk);
|
|
|
|
/* Set new source value (previous dividers if any in effect) */
|
|
reg_val = __raw_readl(reg) & ~(field_mask << src_off);
|
|
reg_val |= (field_val << src_off);
|
|
__raw_writel(reg_val, reg);
|
|
|
|
if (clk->flags & DELAYED_APP)
|
|
__raw_writel(0x1, (void __iomem *)&PRCM_CLKCFG_CTRL);
|
|
|
|
if (clk->usecount > 0)
|
|
_omap2_clk_enable(clk);
|
|
|
|
clk->parent = new_parent;
|
|
|
|
/* SRC_RATE_SEL_MASK clocks follow their parents rates.*/
|
|
if ((new_parent == &core_ck) && (clk == &dss1_fck))
|
|
clk->rate = new_parent->rate / 0x10;
|
|
else
|
|
clk->rate = new_parent->rate;
|
|
|
|
if (unlikely(clk->flags & RATE_PROPAGATES))
|
|
propagate_rate(clk);
|
|
|
|
return 0;
|
|
} else {
|
|
clk->parent = new_parent;
|
|
rate = new_parent->rate;
|
|
omap2_clk_set_rate(clk, rate);
|
|
ret = 0;
|
|
}
|
|
|
|
set_parent_error:
|
|
return ret;
|
|
}
|
|
|
|
/* Sets basic clocks based on the specified rate */
|
|
static int omap2_select_table_rate(struct clk * clk, unsigned long rate)
|
|
{
|
|
u32 flags, cur_rate, done_rate, bypass = 0;
|
|
u8 cpu_mask = 0;
|
|
struct prcm_config *prcm;
|
|
unsigned long found_speed = 0;
|
|
|
|
if (clk != &virt_prcm_set)
|
|
return -EINVAL;
|
|
|
|
/* FIXME: Change cpu_is_omap2420() to cpu_is_omap242x() */
|
|
if (cpu_is_omap2420())
|
|
cpu_mask = RATE_IN_242X;
|
|
else if (cpu_is_omap2430())
|
|
cpu_mask = RATE_IN_243X;
|
|
|
|
for (prcm = rate_table; prcm->mpu_speed; prcm++) {
|
|
if (!(prcm->flags & cpu_mask))
|
|
continue;
|
|
|
|
if (prcm->xtal_speed != sys_ck.rate)
|
|
continue;
|
|
|
|
if (prcm->mpu_speed <= rate) {
|
|
found_speed = prcm->mpu_speed;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found_speed) {
|
|
printk(KERN_INFO "Could not set MPU rate to %luMHz\n",
|
|
rate / 1000000);
|
|
return -EINVAL;
|
|
}
|
|
|
|
curr_prcm_set = prcm;
|
|
cur_rate = omap2_get_dpll_rate(&dpll_ck);
|
|
|
|
if (prcm->dpll_speed == cur_rate / 2) {
|
|
omap2_reprogram_sdrc(PRCM_HALF_SPEED, 1);
|
|
} else if (prcm->dpll_speed == cur_rate * 2) {
|
|
omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1);
|
|
} else if (prcm->dpll_speed != cur_rate) {
|
|
local_irq_save(flags);
|
|
|
|
if (prcm->dpll_speed == prcm->xtal_speed)
|
|
bypass = 1;
|
|
|
|
if ((prcm->cm_clksel2_pll & 0x3) == 2)
|
|
done_rate = PRCM_FULL_SPEED;
|
|
else
|
|
done_rate = PRCM_HALF_SPEED;
|
|
|
|
/* MPU divider */
|
|
CM_CLKSEL_MPU = prcm->cm_clksel_mpu;
|
|
|
|
/* dsp + iva1 div(2420), iva2.1(2430) */
|
|
CM_CLKSEL_DSP = prcm->cm_clksel_dsp;
|
|
|
|
CM_CLKSEL_GFX = prcm->cm_clksel_gfx;
|
|
|
|
/* Major subsystem dividers */
|
|
CM_CLKSEL1_CORE = prcm->cm_clksel1_core;
|
|
if (cpu_is_omap2430())
|
|
CM_CLKSEL_MDM = prcm->cm_clksel_mdm;
|
|
|
|
/* x2 to enter init_mem */
|
|
omap2_reprogram_sdrc(PRCM_FULL_SPEED, 1);
|
|
|
|
omap2_set_prcm(prcm->cm_clksel1_pll, prcm->base_sdrc_rfr,
|
|
bypass);
|
|
|
|
omap2_init_memory_params(omap2_dll_force_needed());
|
|
omap2_reprogram_sdrc(done_rate, 0);
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
omap2_clksel_recalc(&dpll_ck);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
* Omap2 clock reset and init functions
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
static struct clk_functions omap2_clk_functions = {
|
|
.clk_enable = omap2_clk_enable,
|
|
.clk_disable = omap2_clk_disable,
|
|
.clk_round_rate = omap2_clk_round_rate,
|
|
.clk_set_rate = omap2_clk_set_rate,
|
|
.clk_set_parent = omap2_clk_set_parent,
|
|
};
|
|
|
|
static void __init omap2_get_crystal_rate(struct clk *osc, struct clk *sys)
|
|
{
|
|
u32 div, aplls, sclk = 13000000;
|
|
|
|
aplls = CM_CLKSEL1_PLL;
|
|
aplls &= ((1 << 23) | (1 << 24) | (1 << 25));
|
|
aplls >>= 23; /* Isolate field, 0,2,3 */
|
|
|
|
if (aplls == 0)
|
|
sclk = 19200000;
|
|
else if (aplls == 2)
|
|
sclk = 13000000;
|
|
else if (aplls == 3)
|
|
sclk = 12000000;
|
|
|
|
div = PRCM_CLKSRC_CTRL;
|
|
div &= ((1 << 7) | (1 << 6));
|
|
div >>= sys->rate_offset;
|
|
|
|
osc->rate = sclk * div;
|
|
sys->rate = sclk;
|
|
}
|
|
|
|
#ifdef CONFIG_OMAP_RESET_CLOCKS
|
|
static void __init omap2_disable_unused_clocks(void)
|
|
{
|
|
struct clk *ck;
|
|
u32 regval32;
|
|
|
|
list_for_each_entry(ck, &clocks, node) {
|
|
if (ck->usecount > 0 || (ck->flags & ALWAYS_ENABLED) ||
|
|
ck->enable_reg == 0)
|
|
continue;
|
|
|
|
regval32 = __raw_readl(ck->enable_reg);
|
|
if ((regval32 & (1 << ck->enable_bit)) == 0)
|
|
continue;
|
|
|
|
printk(KERN_INFO "Disabling unused clock \"%s\"\n", ck->name);
|
|
_omap2_clk_disable(ck);
|
|
}
|
|
}
|
|
late_initcall(omap2_disable_unused_clocks);
|
|
#endif
|
|
|
|
/*
|
|
* Switch the MPU rate if specified on cmdline.
|
|
* We cannot do this early until cmdline is parsed.
|
|
*/
|
|
static int __init omap2_clk_arch_init(void)
|
|
{
|
|
if (!mpurate)
|
|
return -EINVAL;
|
|
|
|
if (omap2_select_table_rate(&virt_prcm_set, mpurate))
|
|
printk(KERN_ERR "Could not find matching MPU rate\n");
|
|
|
|
propagate_rate(&osc_ck); /* update main root fast */
|
|
propagate_rate(&func_32k_ck); /* update main root slow */
|
|
|
|
printk(KERN_INFO "Switched to new clocking rate (Crystal/DPLL/MPU): "
|
|
"%ld.%01ld/%ld/%ld MHz\n",
|
|
(sys_ck.rate / 1000000), (sys_ck.rate / 100000) % 10,
|
|
(dpll_ck.rate / 1000000), (mpu_ck.rate / 1000000)) ;
|
|
|
|
return 0;
|
|
}
|
|
arch_initcall(omap2_clk_arch_init);
|
|
|
|
int __init omap2_clk_init(void)
|
|
{
|
|
struct prcm_config *prcm;
|
|
struct clk ** clkp;
|
|
u32 clkrate;
|
|
|
|
clk_init(&omap2_clk_functions);
|
|
omap2_get_crystal_rate(&osc_ck, &sys_ck);
|
|
|
|
for (clkp = onchip_clks; clkp < onchip_clks + ARRAY_SIZE(onchip_clks);
|
|
clkp++) {
|
|
|
|
if ((*clkp)->flags & CLOCK_IN_OMAP242X && cpu_is_omap2420()) {
|
|
clk_register(*clkp);
|
|
continue;
|
|
}
|
|
|
|
if ((*clkp)->flags & CLOCK_IN_OMAP243X && cpu_is_omap2430()) {
|
|
clk_register(*clkp);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Check the MPU rate set by bootloader */
|
|
clkrate = omap2_get_dpll_rate(&dpll_ck);
|
|
for (prcm = rate_table; prcm->mpu_speed; prcm++) {
|
|
if (prcm->xtal_speed != sys_ck.rate)
|
|
continue;
|
|
if (prcm->dpll_speed <= clkrate)
|
|
break;
|
|
}
|
|
curr_prcm_set = prcm;
|
|
|
|
propagate_rate(&osc_ck); /* update main root fast */
|
|
propagate_rate(&func_32k_ck); /* update main root slow */
|
|
|
|
printk(KERN_INFO "Clocking rate (Crystal/DPLL/MPU): "
|
|
"%ld.%01ld/%ld/%ld MHz\n",
|
|
(sys_ck.rate / 1000000), (sys_ck.rate / 100000) % 10,
|
|
(dpll_ck.rate / 1000000), (mpu_ck.rate / 1000000)) ;
|
|
|
|
/*
|
|
* Only enable those clocks we will need, let the drivers
|
|
* enable other clocks as necessary
|
|
*/
|
|
clk_enable(&sync_32k_ick);
|
|
clk_enable(&omapctrl_ick);
|
|
if (cpu_is_omap2430())
|
|
clk_enable(&sdrc_ick);
|
|
|
|
return 0;
|
|
}
|
|
|