2019-05-28 09:57:21 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2014-07-15 17:20:22 +02:00
/*
* clk - flexgen . c
*
* Copyright ( C ) ST - Microelectronics SA 2013
* Author : Maxime Coquelin < maxime . coquelin @ st . com > for ST - Microelectronics .
2019-05-28 09:57:21 -07:00
*/
2014-07-15 17:20:22 +02:00
2015-06-19 15:00:46 -07:00
# include <linux/clk.h>
2014-07-15 17:20:22 +02:00
# include <linux/clk-provider.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/err.h>
# include <linux/string.h>
# include <linux/of.h>
# include <linux/of_address.h>
2016-08-29 14:26:57 +02:00
struct clkgen_data {
unsigned long flags ;
2016-08-29 14:26:58 +02:00
bool mode ;
2016-08-29 14:26:57 +02:00
} ;
2014-07-15 17:20:22 +02:00
struct flexgen {
struct clk_hw hw ;
/* Crossbar */
struct clk_mux mux ;
/* Pre-divisor's gate */
struct clk_gate pgate ;
/* Pre-divisor */
struct clk_divider pdiv ;
/* Final divisor's gate */
struct clk_gate fgate ;
/* Final divisor */
struct clk_divider fdiv ;
2016-08-29 14:26:58 +02:00
/* Asynchronous mode control */
struct clk_gate sync ;
/* hw control flags */
bool control_mode ;
2014-07-15 17:20:22 +02:00
} ;
# define to_flexgen(_hw) container_of(_hw, struct flexgen, hw)
2016-08-29 14:26:58 +02:00
# define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)
2014-07-15 17:20:22 +02:00
static int flexgen_enable ( struct clk_hw * hw )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * pgate_hw = & flexgen - > pgate . hw ;
struct clk_hw * fgate_hw = & flexgen - > fgate . hw ;
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( pgate_hw , hw ) ;
__clk_hw_set_clk ( fgate_hw , hw ) ;
2014-07-15 17:20:22 +02:00
clk_gate_ops . enable ( pgate_hw ) ;
clk_gate_ops . enable ( fgate_hw ) ;
2015-08-12 11:42:23 -07:00
pr_debug ( " %s: flexgen output enabled \n " , clk_hw_get_name ( hw ) ) ;
2014-07-15 17:20:22 +02:00
return 0 ;
}
static void flexgen_disable ( struct clk_hw * hw )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * fgate_hw = & flexgen - > fgate . hw ;
/* disable only the final gate */
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( fgate_hw , hw ) ;
2014-07-15 17:20:22 +02:00
clk_gate_ops . disable ( fgate_hw ) ;
2015-08-12 11:42:23 -07:00
pr_debug ( " %s: flexgen output disabled \n " , clk_hw_get_name ( hw ) ) ;
2014-07-15 17:20:22 +02:00
}
static int flexgen_is_enabled ( struct clk_hw * hw )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * fgate_hw = & flexgen - > fgate . hw ;
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( fgate_hw , hw ) ;
2014-07-15 17:20:22 +02:00
if ( ! clk_gate_ops . is_enabled ( fgate_hw ) )
return 0 ;
return 1 ;
}
static u8 flexgen_get_parent ( struct clk_hw * hw )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * mux_hw = & flexgen - > mux . hw ;
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( mux_hw , hw ) ;
2014-07-15 17:20:22 +02:00
return clk_mux_ops . get_parent ( mux_hw ) ;
}
static int flexgen_set_parent ( struct clk_hw * hw , u8 index )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * mux_hw = & flexgen - > mux . hw ;
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( mux_hw , hw ) ;
2014-07-15 17:20:22 +02:00
return clk_mux_ops . set_parent ( mux_hw , index ) ;
}
static inline unsigned long
clk_best_div ( unsigned long parent_rate , unsigned long rate )
{
return parent_rate / rate + ( ( rate > ( 2 * ( parent_rate % rate ) ) ) ? 0 : 1 ) ;
}
static long flexgen_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * prate )
{
unsigned long div ;
/* Round div according to exact prate and wished rate */
div = clk_best_div ( * prate , rate ) ;
2015-06-29 16:56:30 -07:00
if ( clk_hw_get_flags ( hw ) & CLK_SET_RATE_PARENT ) {
2014-07-15 17:20:22 +02:00
* prate = rate * div ;
return rate ;
}
return * prate / div ;
}
2015-05-01 12:45:53 -07:00
static unsigned long flexgen_recalc_rate ( struct clk_hw * hw ,
2014-07-15 17:20:22 +02:00
unsigned long parent_rate )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * pdiv_hw = & flexgen - > pdiv . hw ;
struct clk_hw * fdiv_hw = & flexgen - > fdiv . hw ;
unsigned long mid_rate ;
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( pdiv_hw , hw ) ;
__clk_hw_set_clk ( fdiv_hw , hw ) ;
2014-07-15 17:20:22 +02:00
mid_rate = clk_divider_ops . recalc_rate ( pdiv_hw , parent_rate ) ;
return clk_divider_ops . recalc_rate ( fdiv_hw , mid_rate ) ;
}
static int flexgen_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct flexgen * flexgen = to_flexgen ( hw ) ;
struct clk_hw * pdiv_hw = & flexgen - > pdiv . hw ;
struct clk_hw * fdiv_hw = & flexgen - > fdiv . hw ;
2016-08-29 14:26:58 +02:00
struct clk_hw * sync_hw = & flexgen - > sync . hw ;
struct clk_gate * config = to_clk_gate ( sync_hw ) ;
2015-01-20 15:32:41 +00:00
unsigned long div = 0 ;
2014-07-15 17:20:22 +02:00
int ret = 0 ;
2016-08-29 14:26:58 +02:00
u32 reg ;
2014-07-15 17:20:22 +02:00
2015-02-12 14:58:30 +01:00
__clk_hw_set_clk ( pdiv_hw , hw ) ;
__clk_hw_set_clk ( fdiv_hw , hw ) ;
2014-07-15 17:20:22 +02:00
2016-08-29 14:26:58 +02:00
if ( flexgen - > control_mode ) {
reg = readl ( config - > reg ) ;
reg & = ~ BIT ( config - > bit_idx ) ;
writel ( reg , config - > reg ) ;
}
2015-01-20 15:32:41 +00:00
div = clk_best_div ( parent_rate , rate ) ;
2014-07-15 17:20:22 +02:00
2015-01-20 15:32:41 +00:00
/*
* pdiv is mainly targeted for low freq results , while fdiv
* should be used for div < = 64. The other way round can
* lead to ' duty cycle ' issues .
*/
if ( div < = 64 ) {
clk_divider_ops . set_rate ( pdiv_hw , parent_rate , parent_rate ) ;
ret = clk_divider_ops . set_rate ( fdiv_hw , rate , rate * div ) ;
} else {
clk_divider_ops . set_rate ( fdiv_hw , parent_rate , parent_rate ) ;
ret = clk_divider_ops . set_rate ( pdiv_hw , rate , rate * div ) ;
}
2014-07-15 17:20:22 +02:00
return ret ;
}
static const struct clk_ops flexgen_ops = {
. enable = flexgen_enable ,
. disable = flexgen_disable ,
. is_enabled = flexgen_is_enabled ,
. get_parent = flexgen_get_parent ,
. set_parent = flexgen_set_parent ,
. round_rate = flexgen_round_rate ,
. recalc_rate = flexgen_recalc_rate ,
. set_rate = flexgen_set_rate ,
} ;
2015-05-01 12:45:53 -07:00
static struct clk * clk_register_flexgen ( const char * name ,
2014-07-15 17:20:22 +02:00
const char * * parent_names , u8 num_parents ,
void __iomem * reg , spinlock_t * lock , u32 idx ,
2016-08-29 14:26:58 +02:00
unsigned long flexgen_flags , bool mode ) {
2014-07-15 17:20:22 +02:00
struct flexgen * fgxbar ;
struct clk * clk ;
struct clk_init_data init ;
u32 xbar_shift ;
void __iomem * xbar_reg , * fdiv_reg ;
fgxbar = kzalloc ( sizeof ( struct flexgen ) , GFP_KERNEL ) ;
if ( ! fgxbar )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & flexgen_ops ;
2018-12-06 10:38:31 -08:00
init . flags = CLK_GET_RATE_NOCACHE | flexgen_flags ;
2014-07-15 17:20:22 +02:00
init . parent_names = parent_names ;
init . num_parents = num_parents ;
xbar_reg = reg + 0x18 + ( idx & ~ 0x3 ) ;
xbar_shift = ( idx % 4 ) * 0x8 ;
fdiv_reg = reg + 0x164 + idx * 4 ;
/* Crossbar element config */
fgxbar - > mux . lock = lock ;
fgxbar - > mux . mask = BIT ( 6 ) - 1 ;
fgxbar - > mux . reg = xbar_reg ;
fgxbar - > mux . shift = xbar_shift ;
fgxbar - > mux . table = NULL ;
/* Pre-divider's gate config (in xbar register)*/
fgxbar - > pgate . lock = lock ;
fgxbar - > pgate . reg = xbar_reg ;
fgxbar - > pgate . bit_idx = xbar_shift + 6 ;
/* Pre-divider config */
fgxbar - > pdiv . lock = lock ;
fgxbar - > pdiv . reg = reg + 0x58 + idx * 4 ;
fgxbar - > pdiv . width = 10 ;
/* Final divider's gate config */
fgxbar - > fgate . lock = lock ;
fgxbar - > fgate . reg = fdiv_reg ;
fgxbar - > fgate . bit_idx = 6 ;
/* Final divider config */
fgxbar - > fdiv . lock = lock ;
fgxbar - > fdiv . reg = fdiv_reg ;
fgxbar - > fdiv . width = 6 ;
2016-08-29 14:26:58 +02:00
/* Final divider sync config */
fgxbar - > sync . lock = lock ;
fgxbar - > sync . reg = fdiv_reg ;
fgxbar - > sync . bit_idx = 7 ;
fgxbar - > control_mode = mode ;
2014-07-15 17:20:22 +02:00
fgxbar - > hw . init = & init ;
clk = clk_register ( NULL , & fgxbar - > hw ) ;
if ( IS_ERR ( clk ) )
kfree ( fgxbar ) ;
else
pr_debug ( " %s: parent %s rate %u \n " ,
__clk_get_name ( clk ) ,
__clk_get_name ( clk_get_parent ( clk ) ) ,
( unsigned int ) clk_get_rate ( clk ) ) ;
return clk ;
}
static const char * * __init flexgen_get_parents ( struct device_node * np ,
int * num_parents )
{
const char * * parents ;
2016-02-19 17:43:30 -08:00
unsigned int nparents ;
2014-07-15 17:20:22 +02:00
2015-05-29 11:25:46 +02:00
nparents = of_clk_get_parent_count ( np ) ;
2016-02-19 17:43:30 -08:00
if ( WARN_ON ( ! nparents ) )
2014-07-15 17:20:22 +02:00
return NULL ;
parents = kcalloc ( nparents , sizeof ( const char * ) , GFP_KERNEL ) ;
if ( ! parents )
return NULL ;
2015-07-06 22:59:04 -05:00
* num_parents = of_clk_parent_fill ( np , parents , nparents ) ;
2014-07-15 17:20:22 +02:00
return parents ;
}
2016-08-29 14:26:57 +02:00
static const struct clkgen_data clkgen_audio = {
. flags = CLK_SET_RATE_PARENT ,
} ;
2016-08-29 14:26:58 +02:00
static const struct clkgen_data clkgen_video = {
. flags = CLK_SET_RATE_PARENT ,
. mode = 1 ,
} ;
2016-08-29 14:26:57 +02:00
static const struct of_device_id flexgen_of_match [ ] = {
{
. compatible = " st,flexgen-audio " ,
. data = & clkgen_audio ,
} ,
2016-08-29 14:26:58 +02:00
{
. compatible = " st,flexgen-video " ,
. data = & clkgen_video ,
} ,
2016-08-29 14:26:57 +02:00
{ }
} ;
2015-05-01 12:45:53 -07:00
static void __init st_of_flexgen_setup ( struct device_node * np )
2014-07-15 17:20:22 +02:00
{
struct device_node * pnode ;
void __iomem * reg ;
struct clk_onecell_data * clk_data ;
const char * * parents ;
int num_parents , i ;
spinlock_t * rlock = NULL ;
2016-08-29 14:26:57 +02:00
const struct of_device_id * match ;
struct clkgen_data * data = NULL ;
unsigned long flex_flags = 0 ;
2015-09-24 16:00:16 +02:00
int ret ;
2016-08-29 14:26:58 +02:00
bool clk_mode = 0 ;
2014-07-15 17:20:22 +02:00
pnode = of_get_parent ( np ) ;
if ( ! pnode )
return ;
reg = of_iomap ( pnode , 0 ) ;
if ( ! reg )
return ;
parents = flexgen_get_parents ( np , & num_parents ) ;
2016-09-19 13:51:24 +05:30
if ( ! parents ) {
iounmap ( reg ) ;
2014-07-15 17:20:22 +02:00
return ;
2016-09-19 13:51:24 +05:30
}
2014-07-15 17:20:22 +02:00
2016-08-29 14:26:57 +02:00
match = of_match_node ( flexgen_of_match , np ) ;
if ( match ) {
data = ( struct clkgen_data * ) match - > data ;
flex_flags = data - > flags ;
2016-08-29 14:26:58 +02:00
clk_mode = data - > mode ;
2016-08-29 14:26:57 +02:00
}
2014-07-15 17:20:22 +02:00
clk_data = kzalloc ( sizeof ( * clk_data ) , GFP_KERNEL ) ;
if ( ! clk_data )
goto err ;
2015-09-24 16:00:16 +02:00
ret = of_property_count_strings ( np , " clock-output-names " ) ;
if ( ret < = 0 ) {
2014-07-15 17:20:22 +02:00
pr_err ( " %s: Failed to get number of output clocks (%d) " ,
__func__ , clk_data - > clk_num ) ;
goto err ;
}
2015-09-24 16:00:16 +02:00
clk_data - > clk_num = ret ;
2014-07-15 17:20:22 +02:00
clk_data - > clks = kcalloc ( clk_data - > clk_num , sizeof ( struct clk * ) ,
GFP_KERNEL ) ;
if ( ! clk_data - > clks )
goto err ;
rlock = kzalloc ( sizeof ( spinlock_t ) , GFP_KERNEL ) ;
if ( ! rlock )
goto err ;
2015-06-23 16:09:23 +02:00
spin_lock_init ( rlock ) ;
2014-07-15 17:20:22 +02:00
for ( i = 0 ; i < clk_data - > clk_num ; i + + ) {
struct clk * clk ;
const char * clk_name ;
if ( of_property_read_string_index ( np , " clock-output-names " ,
i , & clk_name ) ) {
break ;
}
2016-06-07 12:19:25 +01:00
of_clk_detect_critical ( np , i , & flex_flags ) ;
2014-07-15 17:20:22 +02:00
/*
* If we read an empty clock name then the output is unused
*/
if ( * clk_name = = ' \0 ' )
continue ;
clk = clk_register_flexgen ( clk_name , parents , num_parents ,
2016-08-29 14:26:58 +02:00
reg , rlock , i , flex_flags , clk_mode ) ;
2014-07-15 17:20:22 +02:00
if ( IS_ERR ( clk ) )
goto err ;
clk_data - > clks [ i ] = clk ;
}
kfree ( parents ) ;
of_clk_add_provider ( np , of_clk_src_onecell_get , clk_data ) ;
return ;
err :
2016-09-19 13:51:24 +05:30
iounmap ( reg ) ;
2014-07-15 17:20:22 +02:00
if ( clk_data )
kfree ( clk_data - > clks ) ;
kfree ( clk_data ) ;
kfree ( parents ) ;
kfree ( rlock ) ;
}
CLK_OF_DECLARE ( flexgen , " st,flexgen " , st_of_flexgen_setup ) ;