2008-03-18 16:09:51 +03:00
/*
* OMAP3 - specific clock framework functions
*
2008-07-03 13:24:45 +04:00
* Copyright ( C ) 2007 - 2008 Texas Instruments , Inc .
* Copyright ( C ) 2007 - 2008 Nokia Corporation
2008-03-18 16:09:51 +03:00
*
* Written by Paul Walmsley
2008-07-03 13:24:45 +04:00
* Testing and integration fixes by Jouni Högander
2008-03-18 16:09:51 +03:00
*
* 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 .
*/
# undef DEBUG
# 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>
2008-07-03 13:24:45 +04:00
# include <linux/limits.h>
2008-03-18 16:09:51 +03:00
2008-08-05 19:14:15 +04:00
# include <mach/clock.h>
# include <mach/sram.h>
2008-03-18 16:09:51 +03:00
# include <asm/div64.h>
# include <asm/bitops.h>
# include "memory.h"
# include "clock.h"
# include "clock34xx.h"
# include "prm.h"
# include "prm-regbits-34xx.h"
# include "cm.h"
# include "cm-regbits-34xx.h"
2008-07-03 13:24:45 +04:00
/* CM_AUTOIDLE_PLL*.AUTO_* bit values */
# define DPLL_AUTOIDLE_DISABLE 0x0
# define DPLL_AUTOIDLE_LOW_POWER_STOP 0x1
# define MAX_DPLL_WAIT_TRIES 1000000
2008-03-18 16:09:51 +03:00
/**
* omap3_dpll_recalc - recalculate DPLL rate
* @ clk : DPLL struct clk
*
* Recalculate and propagate the DPLL rate .
*/
static void omap3_dpll_recalc ( struct clk * clk )
{
clk - > rate = omap2_get_dpll_rate ( clk ) ;
propagate_rate ( clk ) ;
}
2008-07-03 13:24:45 +04:00
/* _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 ;
dd = clk - > dpll_data ;
cm_rmw_reg_bits ( dd - > enable_mask , clken_bits < < __ffs ( dd - > enable_mask ) ,
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 ;
u32 idlest_mask ;
dd = clk - > dpll_data ;
state < < = dd - > idlest_bit ;
idlest_mask = 1 < < dd - > idlest_bit ;
while ( ( ( cm_read_reg ( dd - > idlest_reg ) & 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 ;
}
/* 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 ;
if ( clk = = & dpll3_ck )
return - EINVAL ;
pr_debug ( " clock: locking DPLL %s \n " , clk - > name ) ;
ai = omap3_dpll_autoidle_read ( clk ) ;
_omap3_dpll_write_clken ( clk , DPLL_LOCKED ) ;
if ( ai ) {
/*
* If no downstream clocks are enabled , CM_IDLEST bit
* may never become active , so don ' t wait for DPLL to lock .
*/
r = 0 ;
omap3_dpll_allow_idle ( clk ) ;
} else {
r = _omap3_wait_dpll_status ( clk , 1 ) ;
omap3_dpll_deny_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 = = & dpll3_ck )
return - EINVAL ;
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 = = & dpll3_ck )
return - EINVAL ;
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.
*/
static int omap3_noncore_dpll_enable ( struct clk * clk )
{
int r ;
if ( clk = = & dpll3_ck )
return - EINVAL ;
if ( clk - > parent - > rate = = clk_get_rate ( clk ) )
r = _omap3_noncore_dpll_bypass ( clk ) ;
else
r = _omap3_noncore_dpll_lock ( clk ) ;
return r ;
}
/**
* 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.
*/
static void omap3_noncore_dpll_disable ( struct clk * clk )
{
if ( clk = = & dpll3_ck )
return ;
_omap3_noncore_dpll_stop ( clk ) ;
}
/**
* 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 .
*/
static 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 = cm_read_reg ( 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 .
*/
static void omap3_dpll_allow_idle ( struct clk * clk )
{
const struct dpll_data * dd ;
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 .
*/
cm_rmw_reg_bits ( dd - > autoidle_mask ,
DPLL_AUTOIDLE_LOW_POWER_STOP < < __ffs ( dd - > autoidle_mask ) ,
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 .
*/
static void omap3_dpll_deny_idle ( struct clk * clk )
{
const struct dpll_data * dd ;
if ( ! clk | | ! clk - > dpll_data )
return ;
dd = clk - > dpll_data ;
cm_rmw_reg_bits ( dd - > autoidle_mask ,
DPLL_AUTOIDLE_DISABLE < < __ffs ( dd - > autoidle_mask ) ,
dd - > autoidle_reg ) ;
}
/* Clock control for DPLL outputs */
2008-03-18 16:09:51 +03:00
/**
* 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 .
*/
static void omap3_clkoutx2_recalc ( struct clk * clk )
{
const struct dpll_data * dd ;
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 - > control_reg | | ! dd - > enable_mask ) ;
v = __raw_readl ( dd - > control_reg ) & dd - > enable_mask ;
v > > = __ffs ( dd - > enable_mask ) ;
if ( v ! = DPLL_LOCKED )
clk - > rate = clk - > parent - > rate ;
else
clk - > rate = clk - > parent - > rate * 2 ;
if ( clk - > flags & RATE_PROPAGATES )
propagate_rate ( clk ) ;
}
2008-07-03 13:24:45 +04:00
/* Common clock code */
2008-03-18 16:09:51 +03:00
/*
* As it is structured now , this will prevent an OMAP2 / 3 multiboot
* kernel from compiling . This will need further attention .
*/
# if defined(CONFIG_ARCH_OMAP3)
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 ,
. clk_disable_unused = omap2_clk_disable_unused ,
} ;
/*
* Set clocks for bypass mode for reboot to work .
*/
void omap2_clk_prepare_for_reboot ( void )
{
/* REVISIT: Not ready for 343x */
#if 0
u32 rate ;
if ( vclk = = NULL | | sclk = = NULL )
return ;
rate = clk_get_rate ( sclk ) ;
clk_set_rate ( vclk , rate ) ;
# endif
}
/* REVISIT: Move this init stuff out into clock.c */
/*
* 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 ;
/* REVISIT: not yet ready for 343x */
#if 0
if ( omap2_select_table_rate ( & virt_prcm_set , mpurate ) )
printk ( KERN_ERR " Could not find matching MPU rate \n " ) ;
# endif
recalculate_root_clocks ( ) ;
printk ( KERN_INFO " Switched to new clocking rate (Crystal/DPLL3/MPU): "
" %ld.%01ld/%ld/%ld MHz \n " ,
( osc_sys_ck . rate / 1000000 ) , ( osc_sys_ck . rate / 100000 ) % 10 ,
( core_ck . rate / 1000000 ) , ( dpll1_fck . 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; */
u32 cpu_clkflg ;
/* REVISIT: Ultimately this will be used for multiboot */
#if 0
if ( cpu_is_omap242x ( ) ) {
cpu_mask = RATE_IN_242X ;
cpu_clkflg = CLOCK_IN_OMAP242X ;
clkp = onchip_24xx_clks ;
} else if ( cpu_is_omap2430 ( ) ) {
cpu_mask = RATE_IN_243X ;
cpu_clkflg = CLOCK_IN_OMAP243X ;
clkp = onchip_24xx_clks ;
}
# endif
if ( cpu_is_omap34xx ( ) ) {
cpu_mask = RATE_IN_343X ;
cpu_clkflg = CLOCK_IN_OMAP343X ;
clkp = onchip_34xx_clks ;
/*
* Update this if there are further clock changes between ES2
* and production parts
*/
if ( is_sil_rev_equal_to ( OMAP3430_REV_ES1_0 ) ) {
/* No 3430ES1-only rates exist, so no RATE_IN_3430ES1 */
cpu_clkflg | = CLOCK_IN_OMAP3430ES1 ;
} else {
cpu_mask | = RATE_IN_3430ES2 ;
cpu_clkflg | = CLOCK_IN_OMAP3430ES2 ;
}
}
clk_init ( & omap2_clk_functions ) ;
for ( clkp = onchip_34xx_clks ;
clkp < onchip_34xx_clks + ARRAY_SIZE ( onchip_34xx_clks ) ;
clkp + + ) {
if ( ( * clkp ) - > flags & cpu_clkflg )
clk_register ( * clkp ) ;
}
/* REVISIT: Not yet ready for OMAP3 */
#if 0
/* Check the MPU rate set by bootloader */
clkrate = omap2_get_dpll_rate_24xx ( & dpll_ck ) ;
for ( prcm = rate_table ; prcm - > mpu_speed ; prcm + + ) {
if ( ! ( prcm - > flags & cpu_mask ) )
continue ;
if ( prcm - > xtal_speed ! = sys_ck . rate )
continue ;
if ( prcm - > dpll_speed < = clkrate )
break ;
}
curr_prcm_set = prcm ;
# endif
recalculate_root_clocks ( ) ;
2008-03-13 22:35:09 +03:00
printk ( KERN_INFO " Clocking rate (Crystal/DPLL/ARM core): "
2008-03-18 16:09:51 +03:00
" %ld.%01ld/%ld/%ld MHz \n " ,
( osc_sys_ck . rate / 1000000 ) , ( osc_sys_ck . rate / 100000 ) % 10 ,
2008-03-13 22:35:09 +03:00
( core_ck . rate / 1000000 ) , ( arm_fck . rate / 1000000 ) ) ;
2008-03-18 16:09:51 +03:00
/*
* Only enable those clocks we will need , let the drivers
* enable other clocks as necessary
*/
clk_enable_init_clocks ( ) ;
/* Avoid sleeping during omap2_clk_prepare_for_reboot() */
/* REVISIT: not yet ready for 343x */
#if 0
vclk = clk_get ( NULL , " virt_prcm_set " ) ;
sclk = clk_get ( NULL , " sys_ck " ) ;
# endif
return 0 ;
}
# endif