2012-04-11 16:34:23 +04:00
/*
* Copyright ( C ) 2012 ST Microelectronics
2015-07-18 02:23:50 +03:00
* Viresh Kumar < vireshk @ kernel . org >
2012-04-11 16:34:23 +04:00
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*
* Auxiliary Synthesizer clock implementation
*/
# define pr_fmt(fmt) "clk-aux-synth: " fmt
# include <linux/clk-provider.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/err.h>
# include "clk.h"
/*
* DOC : Auxiliary Synthesizer clock
*
* Aux synth gives rate for different values of eq , x and y
*
* Fout from synthesizer can be given from two equations :
* Fout1 = ( Fin * X / Y ) / 2 EQ1
* Fout2 = Fin * X / Y EQ2
*/
# define to_clk_aux(_hw) container_of(_hw, struct clk_aux, hw)
static struct aux_clk_masks default_aux_masks = {
. eq_sel_mask = AUX_EQ_SEL_MASK ,
. eq_sel_shift = AUX_EQ_SEL_SHIFT ,
. eq1_mask = AUX_EQ1_SEL ,
. eq2_mask = AUX_EQ2_SEL ,
. xscale_sel_mask = AUX_XSCALE_MASK ,
. xscale_sel_shift = AUX_XSCALE_SHIFT ,
. yscale_sel_mask = AUX_YSCALE_MASK ,
. yscale_sel_shift = AUX_YSCALE_SHIFT ,
. enable_bit = AUX_SYNT_ENB ,
} ;
static unsigned long aux_calc_rate ( struct clk_hw * hw , unsigned long prate ,
int index )
{
struct clk_aux * aux = to_clk_aux ( hw ) ;
struct aux_rate_tbl * rtbl = aux - > rtbl ;
u8 eq = rtbl [ index ] . eq ? 1 : 2 ;
return ( ( ( prate / 10000 ) * rtbl [ index ] . xscale ) /
( rtbl [ index ] . yscale * eq ) ) * 10000 ;
}
static long clk_aux_round_rate ( struct clk_hw * hw , unsigned long drate ,
unsigned long * prate )
{
struct clk_aux * aux = to_clk_aux ( hw ) ;
int unused ;
return clk_round_rate_index ( hw , drate , * prate , aux_calc_rate ,
aux - > rtbl_cnt , & unused ) ;
}
static unsigned long clk_aux_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_aux * aux = to_clk_aux ( hw ) ;
unsigned int num = 1 , den = 1 , val , eqn ;
unsigned long flags = 0 ;
if ( aux - > lock )
spin_lock_irqsave ( aux - > lock , flags ) ;
val = readl_relaxed ( aux - > reg ) ;
if ( aux - > lock )
spin_unlock_irqrestore ( aux - > lock , flags ) ;
eqn = ( val > > aux - > masks - > eq_sel_shift ) & aux - > masks - > eq_sel_mask ;
if ( eqn = = aux - > masks - > eq1_mask )
den = 2 ;
/* calculate numerator */
num = ( val > > aux - > masks - > xscale_sel_shift ) &
aux - > masks - > xscale_sel_mask ;
/* calculate denominator */
den * = ( val > > aux - > masks - > yscale_sel_shift ) &
aux - > masks - > yscale_sel_mask ;
if ( ! den )
return 0 ;
return ( ( ( parent_rate / 10000 ) * num ) / den ) * 10000 ;
}
/* Configures new clock rate of aux */
static int clk_aux_set_rate ( struct clk_hw * hw , unsigned long drate ,
unsigned long prate )
{
struct clk_aux * aux = to_clk_aux ( hw ) ;
struct aux_rate_tbl * rtbl = aux - > rtbl ;
unsigned long val , flags = 0 ;
int i ;
clk_round_rate_index ( hw , drate , prate , aux_calc_rate , aux - > rtbl_cnt ,
& i ) ;
if ( aux - > lock )
spin_lock_irqsave ( aux - > lock , flags ) ;
val = readl_relaxed ( aux - > reg ) &
~ ( aux - > masks - > eq_sel_mask < < aux - > masks - > eq_sel_shift ) ;
val | = ( rtbl [ i ] . eq & aux - > masks - > eq_sel_mask ) < <
aux - > masks - > eq_sel_shift ;
val & = ~ ( aux - > masks - > xscale_sel_mask < < aux - > masks - > xscale_sel_shift ) ;
val | = ( rtbl [ i ] . xscale & aux - > masks - > xscale_sel_mask ) < <
aux - > masks - > xscale_sel_shift ;
val & = ~ ( aux - > masks - > yscale_sel_mask < < aux - > masks - > yscale_sel_shift ) ;
val | = ( rtbl [ i ] . yscale & aux - > masks - > yscale_sel_mask ) < <
aux - > masks - > yscale_sel_shift ;
writel_relaxed ( val , aux - > reg ) ;
if ( aux - > lock )
spin_unlock_irqrestore ( aux - > lock , flags ) ;
return 0 ;
}
static struct clk_ops clk_aux_ops = {
. recalc_rate = clk_aux_recalc_rate ,
. round_rate = clk_aux_round_rate ,
. set_rate = clk_aux_set_rate ,
} ;
struct clk * clk_register_aux ( const char * aux_name , const char * gate_name ,
const char * parent_name , unsigned long flags , void __iomem * reg ,
struct aux_clk_masks * masks , struct aux_rate_tbl * rtbl ,
u8 rtbl_cnt , spinlock_t * lock , struct clk * * gate_clk )
{
struct clk_aux * aux ;
struct clk_init_data init ;
struct clk * clk ;
if ( ! aux_name | | ! parent_name | | ! reg | | ! rtbl | | ! rtbl_cnt ) {
pr_err ( " Invalid arguments passed " ) ;
return ERR_PTR ( - EINVAL ) ;
}
aux = kzalloc ( sizeof ( * aux ) , GFP_KERNEL ) ;
if ( ! aux ) {
pr_err ( " could not allocate aux clk \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
/* struct clk_aux assignments */
if ( ! masks )
aux - > masks = & default_aux_masks ;
else
aux - > masks = masks ;
aux - > reg = reg ;
aux - > rtbl = rtbl ;
aux - > rtbl_cnt = rtbl_cnt ;
aux - > lock = lock ;
aux - > hw . init = & init ;
init . name = aux_name ;
init . ops = & clk_aux_ops ;
init . flags = flags ;
init . parent_names = & parent_name ;
init . num_parents = 1 ;
clk = clk_register ( NULL , & aux - > hw ) ;
if ( IS_ERR_OR_NULL ( clk ) )
goto free_aux ;
if ( gate_name ) {
struct clk * tgate_clk ;
2012-11-10 10:43:43 +04:00
tgate_clk = clk_register_gate ( NULL , gate_name , aux_name ,
CLK_SET_RATE_PARENT , reg ,
2012-04-11 16:34:23 +04:00
aux - > masks - > enable_bit , 0 , lock ) ;
if ( IS_ERR_OR_NULL ( tgate_clk ) )
goto free_aux ;
if ( gate_clk )
* gate_clk = tgate_clk ;
}
return clk ;
free_aux :
kfree ( aux ) ;
pr_err ( " clk register failed \n " ) ;
return NULL ;
}