2015-04-03 19:43:45 +03:00
/*
* Copyright ( c ) 2014 Samsung Electronics Co . , Ltd .
* Author : Thomas Abraham < thomas . ab @ samsung . com >
*
* Copyright ( c ) 2015 Samsung Electronics Co . , Ltd .
* Bartlomiej Zolnierkiewicz < b . zolnierkie @ samsung . 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 .
*
* This file contains the utility function to register CPU clock for Samsung
* Exynos platforms . A CPU clock is defined as a clock supplied to a CPU or a
* group of CPUs . The CPU clock is typically derived from a hierarchy of clock
* blocks which includes mux and divider blocks . There are a number of other
* auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
* clock for CPU domain . The rates of these auxiliary clocks are related to the
* CPU clock rate and this relation is usually specified in the hardware manual
* of the SoC or supplied after the SoC characterization .
*
* The below implementation of the CPU clock allows the rate changes of the CPU
* clock and the corresponding rate changes of the auxillary clocks of the CPU
* domain . The platform clock driver provides a clock register configuration
* for each configurable rate which is then used to program the clock hardware
* registers to acheive a fast co - oridinated rate change for all the CPU domain
* clocks .
*
* On a rate change request for the CPU clock , the rate change is propagated
* upto the PLL supplying the clock to the CPU domain clock blocks . While the
* CPU domain PLL is reconfigured , the CPU domain clocks are driven using an
* alternate clock source . If required , the alternate clock source is divided
* down in order to keep the output clock rate within the previous OPP limits .
*/
# include <linux/errno.h>
2015-06-20 01:00:46 +03:00
# include <linux/slab.h>
# include <linux/clk.h>
# include <linux/clk-provider.h>
2015-04-03 19:43:45 +03:00
# include "clk-cpu.h"
# define E4210_SRC_CPU 0x0
# define E4210_STAT_CPU 0x200
# define E4210_DIV_CPU0 0x300
# define E4210_DIV_CPU1 0x304
# define E4210_DIV_STAT_CPU0 0x400
# define E4210_DIV_STAT_CPU1 0x404
2016-05-24 16:19:16 +03:00
# define E5433_MUX_SEL2 0x008
# define E5433_MUX_STAT2 0x208
# define E5433_DIV_CPU0 0x400
# define E5433_DIV_CPU1 0x404
# define E5433_DIV_STAT_CPU0 0x500
# define E5433_DIV_STAT_CPU1 0x504
2015-04-03 19:43:45 +03:00
# define E4210_DIV0_RATIO0_MASK 0x7
# define E4210_DIV1_HPM_MASK (0x7 << 4)
# define E4210_DIV1_COPY_MASK (0x7 << 0)
# define E4210_MUX_HPM_MASK (1 << 20)
# define E4210_DIV0_ATB_SHIFT 16
# define E4210_DIV0_ATB_MASK (DIV_MASK << E4210_DIV0_ATB_SHIFT)
# define MAX_DIV 8
# define DIV_MASK 7
# define DIV_MASK_ALL 0xffffffff
# define MUX_MASK 7
/*
* Helper function to wait until divider ( s ) have stabilized after the divider
* value has changed .
*/
static void wait_until_divider_stable ( void __iomem * div_reg , unsigned long mask )
{
unsigned long timeout = jiffies + msecs_to_jiffies ( 10 ) ;
do {
if ( ! ( readl ( div_reg ) & mask ) )
return ;
} while ( time_before ( jiffies , timeout ) ) ;
if ( ! ( readl ( div_reg ) & mask ) )
return ;
pr_err ( " %s: timeout in divider stablization \n " , __func__ ) ;
}
/*
* Helper function to wait until mux has stabilized after the mux selection
* value was changed .
*/
static void wait_until_mux_stable ( void __iomem * mux_reg , u32 mux_pos ,
unsigned long mux_value )
{
unsigned long timeout = jiffies + msecs_to_jiffies ( 10 ) ;
do {
if ( ( ( readl ( mux_reg ) > > mux_pos ) & MUX_MASK ) = = mux_value )
return ;
} while ( time_before ( jiffies , timeout ) ) ;
if ( ( ( readl ( mux_reg ) > > mux_pos ) & MUX_MASK ) = = mux_value )
return ;
pr_err ( " %s: re-parenting mux timed-out \n " , __func__ ) ;
}
/* common round rate callback useable for all types of CPU clocks */
static long exynos_cpuclk_round_rate ( struct clk_hw * hw ,
unsigned long drate , unsigned long * prate )
{
2015-07-31 03:20:57 +03:00
struct clk_hw * parent = clk_hw_get_parent ( hw ) ;
* prate = clk_hw_round_rate ( parent , drate ) ;
2015-04-03 19:43:45 +03:00
return * prate ;
}
/* common recalc rate callback useable for all types of CPU clocks */
static unsigned long exynos_cpuclk_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
/*
* The CPU clock output ( armclk ) rate is the same as its parent
* rate . Although there exist certain dividers inside the CPU
* clock block that could be used to divide the parent clock ,
* the driver does not make use of them currently , except during
* frequency transitions .
*/
return parent_rate ;
}
static const struct clk_ops exynos_cpuclk_clk_ops = {
. recalc_rate = exynos_cpuclk_recalc_rate ,
. round_rate = exynos_cpuclk_round_rate ,
} ;
/*
* Helper function to set the ' safe ' dividers for the CPU clock . The parameters
* div and mask contain the divider value and the register bit mask of the
* dividers to be programmed .
*/
static void exynos_set_safe_div ( void __iomem * base , unsigned long div ,
unsigned long mask )
{
unsigned long div0 ;
div0 = readl ( base + E4210_DIV_CPU0 ) ;
div0 = ( div0 & ~ mask ) | ( div & mask ) ;
writel ( div0 , base + E4210_DIV_CPU0 ) ;
wait_until_divider_stable ( base + E4210_DIV_STAT_CPU0 , mask ) ;
}
/* handler for pre-rate change notification from parent clock */
static int exynos_cpuclk_pre_rate_change ( struct clk_notifier_data * ndata ,
struct exynos_cpuclk * cpuclk , void __iomem * base )
{
const struct exynos_cpuclk_cfg_data * cfg_data = cpuclk - > cfg ;
unsigned long alt_prate = clk_get_rate ( cpuclk - > alt_parent ) ;
unsigned long alt_div = 0 , alt_div_mask = DIV_MASK ;
unsigned long div0 , div1 = 0 , mux_reg ;
2015-12-11 17:38:40 +03:00
unsigned long flags ;
2015-04-03 19:43:45 +03:00
/* find out the divider values to use for clock data */
while ( ( cfg_data - > prate * 1000 ) ! = ndata - > new_rate ) {
if ( cfg_data - > prate = = 0 )
return - EINVAL ;
cfg_data + + ;
}
2015-12-11 17:38:40 +03:00
spin_lock_irqsave ( cpuclk - > lock , flags ) ;
2015-04-03 19:43:45 +03:00
/*
* For the selected PLL clock frequency , get the pre - defined divider
* values . If the clock for sclk_hpm is not sourced from apll , then
* the values for DIV_COPY and DIV_HPM dividers need not be set .
*/
div0 = cfg_data - > div0 ;
2015-08-28 14:49:35 +03:00
if ( cpuclk - > flags & CLK_CPU_HAS_DIV1 ) {
2015-04-03 19:43:45 +03:00
div1 = cfg_data - > div1 ;
if ( readl ( base + E4210_SRC_CPU ) & E4210_MUX_HPM_MASK )
div1 = readl ( base + E4210_DIV_CPU1 ) &
( E4210_DIV1_HPM_MASK | E4210_DIV1_COPY_MASK ) ;
}
/*
* If the old parent clock speed is less than the clock speed of
* the alternate parent , then it should be ensured that at no point
* the armclk speed is more than the old_prate until the dividers are
* set . Also workaround the issue of the dividers being set to lower
* values before the parent clock speed is set to new lower speed
* ( this can result in too high speed of armclk output clocks ) .
*/
if ( alt_prate > ndata - > old_rate | | ndata - > old_rate > ndata - > new_rate ) {
unsigned long tmp_rate = min ( ndata - > old_rate , ndata - > new_rate ) ;
alt_div = DIV_ROUND_UP ( alt_prate , tmp_rate ) - 1 ;
WARN_ON ( alt_div > = MAX_DIV ) ;
2015-08-28 14:49:35 +03:00
if ( cpuclk - > flags & CLK_CPU_NEEDS_DEBUG_ALT_DIV ) {
2015-04-03 19:43:45 +03:00
/*
* In Exynos4210 , ATB clock parent is also mout_core . So
* ATB clock also needs to be mantained at safe speed .
*/
alt_div | = E4210_DIV0_ATB_MASK ;
alt_div_mask | = E4210_DIV0_ATB_MASK ;
}
exynos_set_safe_div ( base , alt_div , alt_div_mask ) ;
div0 | = alt_div ;
}
/* select sclk_mpll as the alternate parent */
mux_reg = readl ( base + E4210_SRC_CPU ) ;
writel ( mux_reg | ( 1 < < 16 ) , base + E4210_SRC_CPU ) ;
wait_until_mux_stable ( base + E4210_STAT_CPU , 16 , 2 ) ;
/* alternate parent is active now. set the dividers */
writel ( div0 , base + E4210_DIV_CPU0 ) ;
wait_until_divider_stable ( base + E4210_DIV_STAT_CPU0 , DIV_MASK_ALL ) ;
2015-08-28 14:49:35 +03:00
if ( cpuclk - > flags & CLK_CPU_HAS_DIV1 ) {
2015-04-03 19:43:45 +03:00
writel ( div1 , base + E4210_DIV_CPU1 ) ;
wait_until_divider_stable ( base + E4210_DIV_STAT_CPU1 ,
DIV_MASK_ALL ) ;
}
2015-12-11 17:38:40 +03:00
spin_unlock_irqrestore ( cpuclk - > lock , flags ) ;
2015-04-03 19:43:45 +03:00
return 0 ;
}
/* handler for post-rate change notification from parent clock */
static int exynos_cpuclk_post_rate_change ( struct clk_notifier_data * ndata ,
struct exynos_cpuclk * cpuclk , void __iomem * base )
{
const struct exynos_cpuclk_cfg_data * cfg_data = cpuclk - > cfg ;
unsigned long div = 0 , div_mask = DIV_MASK ;
unsigned long mux_reg ;
2015-12-11 17:38:40 +03:00
unsigned long flags ;
2015-04-03 19:43:45 +03:00
/* find out the divider values to use for clock data */
2015-08-28 14:49:35 +03:00
if ( cpuclk - > flags & CLK_CPU_NEEDS_DEBUG_ALT_DIV ) {
2015-04-03 19:43:45 +03:00
while ( ( cfg_data - > prate * 1000 ) ! = ndata - > new_rate ) {
if ( cfg_data - > prate = = 0 )
return - EINVAL ;
cfg_data + + ;
}
}
2015-12-11 17:38:40 +03:00
spin_lock_irqsave ( cpuclk - > lock , flags ) ;
2015-04-03 19:43:45 +03:00
/* select mout_apll as the alternate parent */
mux_reg = readl ( base + E4210_SRC_CPU ) ;
writel ( mux_reg & ~ ( 1 < < 16 ) , base + E4210_SRC_CPU ) ;
wait_until_mux_stable ( base + E4210_STAT_CPU , 16 , 1 ) ;
2015-08-28 14:49:35 +03:00
if ( cpuclk - > flags & CLK_CPU_NEEDS_DEBUG_ALT_DIV ) {
2015-04-03 19:43:45 +03:00
div | = ( cfg_data - > div0 & E4210_DIV0_ATB_MASK ) ;
div_mask | = E4210_DIV0_ATB_MASK ;
}
exynos_set_safe_div ( base , div , div_mask ) ;
2015-12-11 17:38:40 +03:00
spin_unlock_irqrestore ( cpuclk - > lock , flags ) ;
2015-04-03 19:43:45 +03:00
return 0 ;
}
2016-05-24 16:19:16 +03:00
/*
* Helper function to set the ' safe ' dividers for the CPU clock . The parameters
* div and mask contain the divider value and the register bit mask of the
* dividers to be programmed .
*/
static void exynos5433_set_safe_div ( void __iomem * base , unsigned long div ,
unsigned long mask )
{
unsigned long div0 ;
div0 = readl ( base + E5433_DIV_CPU0 ) ;
div0 = ( div0 & ~ mask ) | ( div & mask ) ;
writel ( div0 , base + E5433_DIV_CPU0 ) ;
wait_until_divider_stable ( base + E5433_DIV_STAT_CPU0 , mask ) ;
}
/* handler for pre-rate change notification from parent clock */
static int exynos5433_cpuclk_pre_rate_change ( struct clk_notifier_data * ndata ,
struct exynos_cpuclk * cpuclk , void __iomem * base )
{
const struct exynos_cpuclk_cfg_data * cfg_data = cpuclk - > cfg ;
unsigned long alt_prate = clk_get_rate ( cpuclk - > alt_parent ) ;
unsigned long alt_div = 0 , alt_div_mask = DIV_MASK ;
unsigned long div0 , div1 = 0 , mux_reg ;
unsigned long flags ;
/* find out the divider values to use for clock data */
while ( ( cfg_data - > prate * 1000 ) ! = ndata - > new_rate ) {
if ( cfg_data - > prate = = 0 )
return - EINVAL ;
cfg_data + + ;
}
spin_lock_irqsave ( cpuclk - > lock , flags ) ;
/*
* For the selected PLL clock frequency , get the pre - defined divider
* values .
*/
div0 = cfg_data - > div0 ;
div1 = cfg_data - > div1 ;
/*
* If the old parent clock speed is less than the clock speed of
* the alternate parent , then it should be ensured that at no point
* the armclk speed is more than the old_prate until the dividers are
* set . Also workaround the issue of the dividers being set to lower
* values before the parent clock speed is set to new lower speed
* ( this can result in too high speed of armclk output clocks ) .
*/
if ( alt_prate > ndata - > old_rate | | ndata - > old_rate > ndata - > new_rate ) {
unsigned long tmp_rate = min ( ndata - > old_rate , ndata - > new_rate ) ;
alt_div = DIV_ROUND_UP ( alt_prate , tmp_rate ) - 1 ;
WARN_ON ( alt_div > = MAX_DIV ) ;
exynos5433_set_safe_div ( base , alt_div , alt_div_mask ) ;
div0 | = alt_div ;
}
/* select the alternate parent */
mux_reg = readl ( base + E5433_MUX_SEL2 ) ;
writel ( mux_reg | 1 , base + E5433_MUX_SEL2 ) ;
wait_until_mux_stable ( base + E5433_MUX_STAT2 , 0 , 2 ) ;
/* alternate parent is active now. set the dividers */
writel ( div0 , base + E5433_DIV_CPU0 ) ;
wait_until_divider_stable ( base + E5433_DIV_STAT_CPU0 , DIV_MASK_ALL ) ;
writel ( div1 , base + E5433_DIV_CPU1 ) ;
wait_until_divider_stable ( base + E5433_DIV_STAT_CPU1 , DIV_MASK_ALL ) ;
spin_unlock_irqrestore ( cpuclk - > lock , flags ) ;
return 0 ;
}
/* handler for post-rate change notification from parent clock */
static int exynos5433_cpuclk_post_rate_change ( struct clk_notifier_data * ndata ,
struct exynos_cpuclk * cpuclk , void __iomem * base )
{
unsigned long div = 0 , div_mask = DIV_MASK ;
unsigned long mux_reg ;
unsigned long flags ;
spin_lock_irqsave ( cpuclk - > lock , flags ) ;
/* select apll as the alternate parent */
mux_reg = readl ( base + E5433_MUX_SEL2 ) ;
writel ( mux_reg & ~ 1 , base + E5433_MUX_SEL2 ) ;
wait_until_mux_stable ( base + E5433_MUX_STAT2 , 0 , 1 ) ;
exynos5433_set_safe_div ( base , div , div_mask ) ;
spin_unlock_irqrestore ( cpuclk - > lock , flags ) ;
return 0 ;
}
2015-04-03 19:43:45 +03:00
/*
* This notifier function is called for the pre - rate and post - rate change
* notifications of the parent clock of cpuclk .
*/
static int exynos_cpuclk_notifier_cb ( struct notifier_block * nb ,
unsigned long event , void * data )
{
struct clk_notifier_data * ndata = data ;
struct exynos_cpuclk * cpuclk ;
void __iomem * base ;
int err = 0 ;
cpuclk = container_of ( nb , struct exynos_cpuclk , clk_nb ) ;
base = cpuclk - > ctrl_base ;
if ( event = = PRE_RATE_CHANGE )
err = exynos_cpuclk_pre_rate_change ( ndata , cpuclk , base ) ;
else if ( event = = POST_RATE_CHANGE )
err = exynos_cpuclk_post_rate_change ( ndata , cpuclk , base ) ;
return notifier_from_errno ( err ) ;
}
2016-05-24 16:19:16 +03:00
/*
* This notifier function is called for the pre - rate and post - rate change
* notifications of the parent clock of cpuclk .
*/
static int exynos5433_cpuclk_notifier_cb ( struct notifier_block * nb ,
unsigned long event , void * data )
{
struct clk_notifier_data * ndata = data ;
struct exynos_cpuclk * cpuclk ;
void __iomem * base ;
int err = 0 ;
cpuclk = container_of ( nb , struct exynos_cpuclk , clk_nb ) ;
base = cpuclk - > ctrl_base ;
if ( event = = PRE_RATE_CHANGE )
err = exynos5433_cpuclk_pre_rate_change ( ndata , cpuclk , base ) ;
else if ( event = = POST_RATE_CHANGE )
err = exynos5433_cpuclk_post_rate_change ( ndata , cpuclk , base ) ;
return notifier_from_errno ( err ) ;
}
2015-04-03 19:43:45 +03:00
/* helper function to register a CPU clock */
int __init exynos_register_cpu_clock ( struct samsung_clk_provider * ctx ,
unsigned int lookup_id , const char * name , const char * parent ,
const char * alt_parent , unsigned long offset ,
const struct exynos_cpuclk_cfg_data * cfg ,
unsigned long num_cfgs , unsigned long flags )
{
struct exynos_cpuclk * cpuclk ;
struct clk_init_data init ;
struct clk * clk ;
int ret = 0 ;
cpuclk = kzalloc ( sizeof ( * cpuclk ) , GFP_KERNEL ) ;
if ( ! cpuclk )
return - ENOMEM ;
init . name = name ;
init . flags = CLK_SET_RATE_PARENT ;
init . parent_names = & parent ;
init . num_parents = 1 ;
init . ops = & exynos_cpuclk_clk_ops ;
cpuclk - > hw . init = & init ;
cpuclk - > ctrl_base = ctx - > reg_base + offset ;
cpuclk - > lock = & ctx - > lock ;
cpuclk - > flags = flags ;
2016-05-24 16:19:16 +03:00
if ( flags & CLK_CPU_HAS_E5433_REGS_LAYOUT )
cpuclk - > clk_nb . notifier_call = exynos5433_cpuclk_notifier_cb ;
else
cpuclk - > clk_nb . notifier_call = exynos_cpuclk_notifier_cb ;
2015-04-03 19:43:45 +03:00
cpuclk - > alt_parent = __clk_lookup ( alt_parent ) ;
if ( ! cpuclk - > alt_parent ) {
pr_err ( " %s: could not lookup alternate parent %s \n " ,
__func__ , alt_parent ) ;
ret = - EINVAL ;
goto free_cpuclk ;
}
clk = __clk_lookup ( parent ) ;
if ( ! clk ) {
pr_err ( " %s: could not lookup parent clock %s \n " ,
__func__ , parent ) ;
ret = - EINVAL ;
goto free_cpuclk ;
}
ret = clk_notifier_register ( clk , & cpuclk - > clk_nb ) ;
if ( ret ) {
pr_err ( " %s: failed to register clock notifier for %s \n " ,
__func__ , name ) ;
goto free_cpuclk ;
}
cpuclk - > cfg = kmemdup ( cfg , sizeof ( * cfg ) * num_cfgs , GFP_KERNEL ) ;
if ( ! cpuclk - > cfg ) {
pr_err ( " %s: could not allocate memory for cpuclk data \n " ,
__func__ ) ;
ret = - ENOMEM ;
goto unregister_clk_nb ;
}
clk = clk_register ( NULL , & cpuclk - > hw ) ;
if ( IS_ERR ( clk ) ) {
pr_err ( " %s: could not register cpuclk %s \n " , __func__ , name ) ;
ret = PTR_ERR ( clk ) ;
goto free_cpuclk_data ;
}
samsung_clk_add_lookup ( ctx , clk , lookup_id ) ;
return 0 ;
free_cpuclk_data :
kfree ( cpuclk - > cfg ) ;
unregister_clk_nb :
clk_notifier_unregister ( __clk_lookup ( parent ) , & cpuclk - > clk_nb ) ;
free_cpuclk :
kfree ( cpuclk ) ;
return ret ;
}