2009-12-09 04:47:16 +03:00
/*
* OMAP3 / 4 - specific DPLL control functions
*
* Copyright ( C ) 2009 Texas Instruments , Inc .
* Copyright ( C ) 2009 Nokia Corporation
*
* Written by Paul Walmsley
* Testing and integration fixes by Jouni HÃ ¶ gander
*
* Parts of this code are based on code written by
* Richard Woodruff , Tony Lindgren , Tuukka Tikkanen , Karthik Dasu
*
* 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/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 <linux/io.h>
# include <linux/limits.h>
# include <linux/bitops.h>
2009-12-09 04:47:16 +03:00
# include <plat/cpu.h>
# include <plat/clock.h>
# include <plat/sram.h>
2009-12-09 04:47:16 +03:00
# include <asm/div64.h>
# include <asm/clkdev.h>
# include "clock.h"
# include "prm.h"
# include "prm-regbits-34xx.h"
# include "cm.h"
# include "cm-regbits-34xx.h"
/* CM_AUTOIDLE_PLL*.AUTO_* bit values */
# define DPLL_AUTOIDLE_DISABLE 0x0
# define DPLL_AUTOIDLE_LOW_POWER_STOP 0x1
# define MAX_DPLL_WAIT_TRIES 1000000
/**
* omap3_dpll_recalc - recalculate DPLL rate
* @ clk : DPLL struct clk
*
* Recalculate and propagate the DPLL rate .
*/
unsigned long omap3_dpll_recalc ( struct clk * clk )
{
return omap2_get_dpll_rate ( clk ) ;
}
/* _omap3_dpll_write_clken - write clken_bits arg to a DPLL's enable bits */
static void _omap3_dpll_write_clken ( struct clk * clk , u8 clken_bits )
{
const struct dpll_data * dd ;
u32 v ;
dd = clk - > dpll_data ;
v = __raw_readl ( dd - > control_reg ) ;
v & = ~ dd - > enable_mask ;
v | = clken_bits < < __ffs ( dd - > enable_mask ) ;
__raw_writel ( v , dd - > control_reg ) ;
}
/* _omap3_wait_dpll_status: wait for a DPLL to enter a specific state */
static int _omap3_wait_dpll_status ( struct clk * clk , u8 state )
{
const struct dpll_data * dd ;
int i = 0 ;
int ret = - EINVAL ;
dd = clk - > dpll_data ;
state < < = __ffs ( dd - > idlest_mask ) ;
while ( ( ( __raw_readl ( dd - > idlest_reg ) & dd - > idlest_mask ) ! = state ) & &
i < MAX_DPLL_WAIT_TRIES ) {
i + + ;
udelay ( 1 ) ;
}
if ( i = = MAX_DPLL_WAIT_TRIES ) {
printk ( KERN_ERR " clock: %s failed transition to '%s' \n " ,
clk - > name , ( state ) ? " locked " : " bypassed " ) ;
} else {
pr_debug ( " clock: %s transition to '%s' in %d loops \n " ,
clk - > name , ( state ) ? " locked " : " bypassed " , i ) ;
ret = 0 ;
}
return ret ;
}
/* From 3430 TRM ES2 4.7.6.2 */
static u16 _omap3_dpll_compute_freqsel ( struct clk * clk , u8 n )
{
unsigned long fint ;
u16 f = 0 ;
fint = clk - > dpll_data - > clk_ref - > rate / n ;
pr_debug ( " clock: fint is %lu \n " , fint ) ;
if ( fint > = 750000 & & fint < = 1000000 )
f = 0x3 ;
else if ( fint > 1000000 & & fint < = 1250000 )
f = 0x4 ;
else if ( fint > 1250000 & & fint < = 1500000 )
f = 0x5 ;
else if ( fint > 1500000 & & fint < = 1750000 )
f = 0x6 ;
else if ( fint > 1750000 & & fint < = 2100000 )
f = 0x7 ;
else if ( fint > 7500000 & & fint < = 10000000 )
f = 0xB ;
else if ( fint > 10000000 & & fint < = 12500000 )
f = 0xC ;
else if ( fint > 12500000 & & fint < = 15000000 )
f = 0xD ;
else if ( fint > 15000000 & & fint < = 17500000 )
f = 0xE ;
else if ( fint > 17500000 & & fint < = 21000000 )
f = 0xF ;
else
pr_debug ( " clock: unknown freqsel setting for %d \n " , n ) ;
return f ;
}
/* Non-CORE DPLL (e.g., DPLLs that do not control SDRC) clock functions */
/*
* _omap3_noncore_dpll_lock - instruct a DPLL to lock and wait for readiness
* @ clk : pointer to a DPLL struct clk
*
* Instructs a non - CORE DPLL to lock . Waits for the DPLL to report
* readiness before returning . Will save and restore the DPLL ' s
* autoidle state across the enable , per the CDP code . If the DPLL
* locked successfully , return 0 ; if the DPLL did not lock in the time
* allotted , or DPLL3 was passed in , return - EINVAL .
*/
static int _omap3_noncore_dpll_lock ( struct clk * clk )
{
u8 ai ;
int r ;
pr_debug ( " clock: locking DPLL %s \n " , clk - > name ) ;
ai = omap3_dpll_autoidle_read ( clk ) ;
omap3_dpll_deny_idle ( clk ) ;
_omap3_dpll_write_clken ( clk , DPLL_LOCKED ) ;
r = _omap3_wait_dpll_status ( clk , 1 ) ;
if ( ai )
omap3_dpll_allow_idle ( clk ) ;
return r ;
}
/*
* _omap3_noncore_dpll_bypass - instruct a DPLL to bypass and wait for readiness
* @ clk : pointer to a DPLL struct clk
*
* Instructs a non - CORE DPLL to enter low - power bypass mode . In
* bypass mode , the DPLL ' s rate is set equal to its parent clock ' s
* rate . Waits for the DPLL to report readiness before returning .
* Will save and restore the DPLL ' s autoidle state across the enable ,
* per the CDP code . If the DPLL entered bypass mode successfully ,
* return 0 ; if the DPLL did not enter bypass in the time allotted , or
* DPLL3 was passed in , or the DPLL does not support low - power bypass ,
* return - EINVAL .
*/
static int _omap3_noncore_dpll_bypass ( struct clk * clk )
{
int r ;
u8 ai ;
if ( ! ( clk - > dpll_data - > modes & ( 1 < < DPLL_LOW_POWER_BYPASS ) ) )
return - EINVAL ;
pr_debug ( " clock: configuring DPLL %s for low-power bypass \n " ,
clk - > name ) ;
ai = omap3_dpll_autoidle_read ( clk ) ;
_omap3_dpll_write_clken ( clk , DPLL_LOW_POWER_BYPASS ) ;
r = _omap3_wait_dpll_status ( clk , 0 ) ;
if ( ai )
omap3_dpll_allow_idle ( clk ) ;
else
omap3_dpll_deny_idle ( clk ) ;
return r ;
}
/*
* _omap3_noncore_dpll_stop - instruct a DPLL to stop
* @ clk : pointer to a DPLL struct clk
*
* Instructs a non - CORE DPLL to enter low - power stop . Will save and
* restore the DPLL ' s autoidle state across the stop , per the CDP
* code . If DPLL3 was passed in , or the DPLL does not support
* low - power stop , return - EINVAL ; otherwise , return 0.
*/
static int _omap3_noncore_dpll_stop ( struct clk * clk )
{
u8 ai ;
if ( ! ( clk - > dpll_data - > modes & ( 1 < < DPLL_LOW_POWER_STOP ) ) )
return - EINVAL ;
pr_debug ( " clock: stopping DPLL %s \n " , clk - > name ) ;
ai = omap3_dpll_autoidle_read ( clk ) ;
_omap3_dpll_write_clken ( clk , DPLL_LOW_POWER_STOP ) ;
if ( ai )
omap3_dpll_allow_idle ( clk ) ;
else
omap3_dpll_deny_idle ( clk ) ;
return 0 ;
}
/**
* omap3_noncore_dpll_enable - instruct a DPLL to enter bypass or lock mode
* @ clk : pointer to a DPLL struct clk
*
* Instructs a non - CORE DPLL to enable , e . g . , to enter bypass or lock .
* The choice of modes depends on the DPLL ' s programmed rate : if it is
* the same as the DPLL ' s parent clock , it will enter bypass ;
* otherwise , it will enter lock . This code will wait for the DPLL to
* indicate readiness before returning , unless the DPLL takes too long
* to enter the target state . Intended to be used as the struct clk ' s
* enable function . If DPLL3 was passed in , or the DPLL does not
* support low - power stop , or if the DPLL took too long to enter
* bypass or lock , return - EINVAL ; otherwise , return 0.
*/
int omap3_noncore_dpll_enable ( struct clk * clk )
{
int r ;
struct dpll_data * dd ;
dd = clk - > dpll_data ;
if ( ! dd )
return - EINVAL ;
if ( clk - > rate = = dd - > clk_bypass - > rate ) {
WARN_ON ( clk - > parent ! = dd - > clk_bypass ) ;
r = _omap3_noncore_dpll_bypass ( clk ) ;
} else {
WARN_ON ( clk - > parent ! = dd - > clk_ref ) ;
r = _omap3_noncore_dpll_lock ( clk ) ;
}
/*
* FIXME : this is dubious - if clk - > rate has changed , what about
* propagating ?
*/
if ( ! r )
clk - > rate = omap2_get_dpll_rate ( clk ) ;
return r ;
}
/**
* omap3_noncore_dpll_disable - instruct a DPLL to enter low - power stop
* @ clk : pointer to a DPLL struct clk
*
* Instructs a non - CORE DPLL to enter low - power stop . This function is
* intended for use in struct clkops . No return value .
*/
void omap3_noncore_dpll_disable ( struct clk * clk )
{
_omap3_noncore_dpll_stop ( clk ) ;
}
/* Non-CORE DPLL rate set code */
/*
* omap3_noncore_dpll_program - set non - core DPLL M , N values directly
* @ clk : struct clk * of DPLL to set
* @ m : DPLL multiplier to set
* @ n : DPLL divider to set
* @ freqsel : FREQSEL value to set
*
* Program the DPLL with the supplied M , N values , and wait for the DPLL to
* lock . . Returns - EINVAL upon error , or 0 upon success .
*/
int omap3_noncore_dpll_program ( struct clk * clk , u16 m , u8 n , u16 freqsel )
{
struct dpll_data * dd = clk - > dpll_data ;
u32 v ;
/* 3430 ES2 TRM: 4.7.6.9 DPLL Programming Sequence */
_omap3_noncore_dpll_bypass ( clk ) ;
/* Set jitter correction */
2009-12-09 04:47:16 +03:00
if ( ! cpu_is_omap44xx ( ) ) {
v = __raw_readl ( dd - > control_reg ) ;
v & = ~ dd - > freqsel_mask ;
v | = freqsel < < __ffs ( dd - > freqsel_mask ) ;
__raw_writel ( v , dd - > control_reg ) ;
}
2009-12-09 04:47:16 +03:00
/* Set DPLL multiplier, divider */
v = __raw_readl ( dd - > mult_div1_reg ) ;
v & = ~ ( dd - > mult_mask | dd - > div1_mask ) ;
v | = m < < __ffs ( dd - > mult_mask ) ;
v | = ( n - 1 ) < < __ffs ( dd - > div1_mask ) ;
__raw_writel ( v , dd - > mult_div1_reg ) ;
/* We let the clock framework set the other output dividers later */
/* REVISIT: Set ramp-up delay? */
_omap3_noncore_dpll_lock ( clk ) ;
return 0 ;
}
/**
* omap3_noncore_dpll_set_rate - set non - core DPLL rate
* @ clk : struct clk * of DPLL to set
* @ rate : rounded target rate
*
* Set the DPLL CLKOUT to the target rate . If the DPLL can enter
* low - power bypass , and the target rate is the bypass source clock
* rate , then configure the DPLL for bypass . Otherwise , round the
* target rate if it hasn ' t been done already , then program and lock
* the DPLL . Returns - EINVAL upon error , or 0 upon success .
*/
int omap3_noncore_dpll_set_rate ( struct clk * clk , unsigned long rate )
{
struct clk * new_parent = NULL ;
2009-12-09 04:47:16 +03:00
u16 freqsel = 0 ;
2009-12-09 04:47:16 +03:00
struct dpll_data * dd ;
int ret ;
if ( ! clk | | ! rate )
return - EINVAL ;
dd = clk - > dpll_data ;
if ( ! dd )
return - EINVAL ;
if ( rate = = omap2_get_dpll_rate ( clk ) )
return 0 ;
/*
* Ensure both the bypass and ref clocks are enabled prior to
* doing anything ; we need the bypass clock running to reprogram
* the DPLL .
*/
omap2_clk_enable ( dd - > clk_bypass ) ;
omap2_clk_enable ( dd - > clk_ref ) ;
if ( dd - > clk_bypass - > rate = = rate & &
( clk - > dpll_data - > modes & ( 1 < < DPLL_LOW_POWER_BYPASS ) ) ) {
pr_debug ( " clock: %s: set rate: entering bypass. \n " , clk - > name ) ;
ret = _omap3_noncore_dpll_bypass ( clk ) ;
if ( ! ret )
new_parent = dd - > clk_bypass ;
} else {
if ( dd - > last_rounded_rate ! = rate )
omap2_dpll_round_rate ( clk , rate ) ;
if ( dd - > last_rounded_rate = = 0 )
return - EINVAL ;
2009-12-09 04:47:16 +03:00
/* No freqsel on OMAP4 */
if ( ! cpu_is_omap44xx ( ) ) {
freqsel = _omap3_dpll_compute_freqsel ( clk ,
dd - > last_rounded_n ) ;
if ( ! freqsel )
WARN_ON ( 1 ) ;
}
2009-12-09 04:47:16 +03:00
pr_debug ( " clock: %s: set rate: locking rate to %lu. \n " ,
clk - > name , rate ) ;
ret = omap3_noncore_dpll_program ( clk , dd - > last_rounded_m ,
dd - > last_rounded_n , freqsel ) ;
if ( ! ret )
new_parent = dd - > clk_ref ;
}
if ( ! ret ) {
/*
* Switch the parent clock in the heirarchy , and make sure
* that the new parent ' s usecount is correct . Note : we
* enable the new parent before disabling the old to avoid
* any unnecessary hardware disable - > enable transitions .
*/
if ( clk - > usecount ) {
omap2_clk_enable ( new_parent ) ;
omap2_clk_disable ( clk - > parent ) ;
}
clk_reparent ( clk , new_parent ) ;
clk - > rate = rate ;
}
omap2_clk_disable ( dd - > clk_ref ) ;
omap2_clk_disable ( dd - > clk_bypass ) ;
return 0 ;
}
/* DPLL autoidle read/set code */
/**
* omap3_dpll_autoidle_read - read a DPLL ' s autoidle bits
* @ clk : struct clk * of the DPLL to read
*
* Return the DPLL ' s autoidle bits , shifted down to bit 0. Returns
* - EINVAL if passed a null pointer or if the struct clk does not
* appear to refer to a DPLL .
*/
u32 omap3_dpll_autoidle_read ( struct clk * clk )
{
const struct dpll_data * dd ;
u32 v ;
if ( ! clk | | ! clk - > dpll_data )
return - EINVAL ;
dd = clk - > dpll_data ;
v = __raw_readl ( dd - > autoidle_reg ) ;
v & = dd - > autoidle_mask ;
v > > = __ffs ( dd - > autoidle_mask ) ;
return v ;
}
/**
* omap3_dpll_allow_idle - enable DPLL autoidle bits
* @ clk : struct clk * of the DPLL to operate on
*
* Enable DPLL automatic idle control . This automatic idle mode
* switching takes effect only when the DPLL is locked , at least on
* OMAP3430 . The DPLL will enter low - power stop when its downstream
* clocks are gated . No return value .
*/
void omap3_dpll_allow_idle ( struct clk * clk )
{
const struct dpll_data * dd ;
u32 v ;
if ( ! clk | | ! clk - > dpll_data )
return ;
dd = clk - > dpll_data ;
/*
* REVISIT : CORE DPLL can optionally enter low - power bypass
* by writing 0x5 instead of 0x1 . Add some mechanism to
* optionally enter this mode .
*/
v = __raw_readl ( dd - > autoidle_reg ) ;
v & = ~ dd - > autoidle_mask ;
v | = DPLL_AUTOIDLE_LOW_POWER_STOP < < __ffs ( dd - > autoidle_mask ) ;
__raw_writel ( v , dd - > autoidle_reg ) ;
}
/**
* omap3_dpll_deny_idle - prevent DPLL from automatically idling
* @ clk : struct clk * of the DPLL to operate on
*
* Disable DPLL automatic idle control . No return value .
*/
void omap3_dpll_deny_idle ( struct clk * clk )
{
const struct dpll_data * dd ;
u32 v ;
if ( ! clk | | ! clk - > dpll_data )
return ;
dd = clk - > dpll_data ;
v = __raw_readl ( dd - > autoidle_reg ) ;
v & = ~ dd - > autoidle_mask ;
v | = DPLL_AUTOIDLE_DISABLE < < __ffs ( dd - > autoidle_mask ) ;
__raw_writel ( v , dd - > autoidle_reg ) ;
}
/* Clock control for DPLL outputs */
/**
* omap3_clkoutx2_recalc - recalculate DPLL X2 output virtual clock rate
* @ clk : DPLL output struct clk
*
* Using parent clock DPLL data , look up DPLL state . If locked , set our
* rate to the dpll_clk * 2 ; otherwise , just use dpll_clk .
*/
unsigned long omap3_clkoutx2_recalc ( struct clk * clk )
{
const struct dpll_data * dd ;
unsigned long rate ;
u32 v ;
struct clk * pclk ;
/* Walk up the parents of clk, looking for a DPLL */
pclk = clk - > parent ;
while ( pclk & & ! pclk - > dpll_data )
pclk = pclk - > parent ;
/* clk does not have a DPLL as a parent? */
WARN_ON ( ! pclk ) ;
dd = pclk - > dpll_data ;
WARN_ON ( ! dd - > enable_mask ) ;
v = __raw_readl ( dd - > control_reg ) & dd - > enable_mask ;
v > > = __ffs ( dd - > enable_mask ) ;
if ( v ! = OMAP3XXX_EN_DPLL_LOCKED )
rate = clk - > parent - > rate ;
else
rate = clk - > parent - > rate * 2 ;
return rate ;
}