2015-05-05 11:13:19 -07:00
/*
* Copyright ( C ) 2014 Broadcom Corporation
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/kernel.h>
# include <linux/err.h>
# include <linux/clk-provider.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/clkdev.h>
# include <linux/of_address.h>
# include <linux/delay.h>
# include "clk-iproc.h"
struct iproc_asiu ;
struct iproc_asiu_clk {
struct clk_hw hw ;
const char * name ;
struct iproc_asiu * asiu ;
unsigned long rate ;
struct iproc_asiu_div div ;
struct iproc_asiu_gate gate ;
} ;
struct iproc_asiu {
void __iomem * div_base ;
void __iomem * gate_base ;
struct clk_onecell_data clk_data ;
struct iproc_asiu_clk * clks ;
} ;
# define to_asiu_clk(hw) container_of(hw, struct iproc_asiu_clk, hw)
static int iproc_asiu_clk_enable ( struct clk_hw * hw )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
u32 val ;
/* some clocks at the ASIU level are always enabled */
if ( clk - > gate . offset = = IPROC_CLK_INVALID_OFFSET )
return 0 ;
val = readl ( asiu - > gate_base + clk - > gate . offset ) ;
val | = ( 1 < < clk - > gate . en_shift ) ;
writel ( val , asiu - > gate_base + clk - > gate . offset ) ;
return 0 ;
}
static void iproc_asiu_clk_disable ( struct clk_hw * hw )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
u32 val ;
/* some clocks at the ASIU level are always enabled */
if ( clk - > gate . offset = = IPROC_CLK_INVALID_OFFSET )
return ;
val = readl ( asiu - > gate_base + clk - > gate . offset ) ;
val & = ~ ( 1 < < clk - > gate . en_shift ) ;
writel ( val , asiu - > gate_base + clk - > gate . offset ) ;
}
static unsigned long iproc_asiu_clk_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
u32 val ;
unsigned int div_h , div_l ;
if ( parent_rate = = 0 ) {
clk - > rate = 0 ;
return 0 ;
}
/* if clock divisor is not enabled, simply return parent rate */
val = readl ( asiu - > div_base + clk - > div . offset ) ;
if ( ( val & ( 1 < < clk - > div . en_shift ) ) = = 0 ) {
clk - > rate = parent_rate ;
return parent_rate ;
}
/* clock rate = parent rate / (high_div + 1) + (low_div + 1) */
div_h = ( val > > clk - > div . high_shift ) & bit_mask ( clk - > div . high_width ) ;
div_h + + ;
div_l = ( val > > clk - > div . low_shift ) & bit_mask ( clk - > div . low_width ) ;
div_l + + ;
clk - > rate = parent_rate / ( div_h + div_l ) ;
pr_debug ( " %s: rate: %lu. parent rate: %lu div_h: %u div_l: %u \n " ,
__func__ , clk - > rate , parent_rate , div_h , div_l ) ;
return clk - > rate ;
}
static long iproc_asiu_clk_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned int div ;
if ( rate = = 0 | | * parent_rate = = 0 )
return - EINVAL ;
if ( rate = = * parent_rate )
return * parent_rate ;
div = DIV_ROUND_UP ( * parent_rate , rate ) ;
if ( div < 2 )
return * parent_rate ;
return * parent_rate / div ;
}
static int iproc_asiu_clk_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct iproc_asiu_clk * clk = to_asiu_clk ( hw ) ;
struct iproc_asiu * asiu = clk - > asiu ;
unsigned int div , div_h , div_l ;
u32 val ;
if ( rate = = 0 | | parent_rate = = 0 )
return - EINVAL ;
/* simply disable the divisor if one wants the same rate as parent */
if ( rate = = parent_rate ) {
val = readl ( asiu - > div_base + clk - > div . offset ) ;
val & = ~ ( 1 < < clk - > div . en_shift ) ;
writel ( val , asiu - > div_base + clk - > div . offset ) ;
return 0 ;
}
div = DIV_ROUND_UP ( parent_rate , rate ) ;
if ( div < 2 )
return - EINVAL ;
div_h = div_l = div > > 1 ;
div_h - - ;
div_l - - ;
val = readl ( asiu - > div_base + clk - > div . offset ) ;
val | = 1 < < clk - > div . en_shift ;
if ( div_h ) {
val & = ~ ( bit_mask ( clk - > div . high_width )
< < clk - > div . high_shift ) ;
val | = div_h < < clk - > div . high_shift ;
} else {
val & = ~ ( bit_mask ( clk - > div . high_width )
< < clk - > div . high_shift ) ;
}
if ( div_l ) {
val & = ~ ( bit_mask ( clk - > div . low_width ) < < clk - > div . low_shift ) ;
val | = div_l < < clk - > div . low_shift ;
} else {
val & = ~ ( bit_mask ( clk - > div . low_width ) < < clk - > div . low_shift ) ;
}
writel ( val , asiu - > div_base + clk - > div . offset ) ;
return 0 ;
}
static const struct clk_ops iproc_asiu_ops = {
. enable = iproc_asiu_clk_enable ,
. disable = iproc_asiu_clk_disable ,
. recalc_rate = iproc_asiu_clk_recalc_rate ,
. round_rate = iproc_asiu_clk_round_rate ,
. set_rate = iproc_asiu_clk_set_rate ,
} ;
void __init iproc_asiu_setup ( struct device_node * node ,
const struct iproc_asiu_div * div ,
const struct iproc_asiu_gate * gate ,
unsigned int num_clks )
{
int i , ret ;
struct iproc_asiu * asiu ;
if ( WARN_ON ( ! gate | | ! div ) )
return ;
asiu = kzalloc ( sizeof ( * asiu ) , GFP_KERNEL ) ;
if ( WARN_ON ( ! asiu ) )
return ;
asiu - > clk_data . clk_num = num_clks ;
asiu - > clk_data . clks = kcalloc ( num_clks , sizeof ( * asiu - > clk_data . clks ) ,
GFP_KERNEL ) ;
if ( WARN_ON ( ! asiu - > clk_data . clks ) )
goto err_clks ;
asiu - > clks = kcalloc ( num_clks , sizeof ( * asiu - > clks ) , GFP_KERNEL ) ;
if ( WARN_ON ( ! asiu - > clks ) )
goto err_asiu_clks ;
asiu - > div_base = of_iomap ( node , 0 ) ;
if ( WARN_ON ( ! asiu - > div_base ) )
goto err_iomap_div ;
asiu - > gate_base = of_iomap ( node , 1 ) ;
if ( WARN_ON ( ! asiu - > gate_base ) )
goto err_iomap_gate ;
for ( i = 0 ; i < num_clks ; i + + ) {
struct clk_init_data init ;
struct clk * clk ;
const char * parent_name ;
struct iproc_asiu_clk * asiu_clk ;
const char * clk_name ;
ret = of_property_read_string_index ( node , " clock-output-names " ,
i , & clk_name ) ;
if ( WARN_ON ( ret ) )
goto err_clk_register ;
asiu_clk = & asiu - > clks [ i ] ;
asiu_clk - > name = clk_name ;
asiu_clk - > asiu = asiu ;
asiu_clk - > div = div [ i ] ;
asiu_clk - > gate = gate [ i ] ;
init . name = clk_name ;
init . ops = & iproc_asiu_ops ;
init . flags = 0 ;
parent_name = of_clk_get_parent_name ( node , 0 ) ;
init . parent_names = ( parent_name ? & parent_name : NULL ) ;
init . num_parents = ( parent_name ? 1 : 0 ) ;
asiu_clk - > hw . init = & init ;
clk = clk_register ( NULL , & asiu_clk - > hw ) ;
if ( WARN_ON ( IS_ERR ( clk ) ) )
goto err_clk_register ;
asiu - > clk_data . clks [ i ] = clk ;
}
ret = of_clk_add_provider ( node , of_clk_src_onecell_get ,
& asiu - > clk_data ) ;
if ( WARN_ON ( ret ) )
goto err_clk_register ;
return ;
err_clk_register :
for ( i = 0 ; i < num_clks ; i + + )
2015-06-29 14:30:09 -07:00
clk_unregister ( asiu - > clk_data . clks [ i ] ) ;
2015-05-05 11:13:19 -07:00
iounmap ( asiu - > gate_base ) ;
err_iomap_gate :
iounmap ( asiu - > div_base ) ;
err_iomap_div :
kfree ( asiu - > clks ) ;
err_asiu_clks :
kfree ( asiu - > clk_data . clks ) ;
err_clks :
kfree ( asiu ) ;
}