2015-07-31 11:43:12 +02:00
/*
* Copyright ( C ) 2015 Atmel Corporation ,
* Nicolas Ferre < nicolas . ferre @ atmel . com >
*
* Based on clk - programmable & clk - peripheral drivers by Boris BREZILLON .
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
*/
# include <linux/clk-provider.h>
# include <linux/clkdev.h>
# include <linux/clk/at91_pmc.h>
# include <linux/of.h>
2014-09-07 08:14:29 +02:00
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
2015-07-31 11:43:12 +02:00
# include "pmc.h"
# define PERIPHERAL_MAX 64
# define PERIPHERAL_ID_MIN 2
# define GENERATED_SOURCE_MAX 6
# define GENERATED_MAX_DIV 255
struct clk_generated {
struct clk_hw hw ;
2014-09-07 08:14:29 +02:00
struct regmap * regmap ;
2015-07-31 11:43:12 +02:00
struct clk_range range ;
2014-09-07 08:14:29 +02:00
spinlock_t * lock ;
2015-07-31 11:43:12 +02:00
u32 id ;
u32 gckdiv ;
u8 parent_id ;
} ;
# define to_clk_generated(hw) \
container_of ( hw , struct clk_generated , hw )
static int clk_generated_enable ( struct clk_hw * hw )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
2014-09-07 08:14:29 +02:00
unsigned long flags ;
2015-07-31 11:43:12 +02:00
pr_debug ( " GCLK: %s, gckdiv = %d, parent id = %d \n " ,
__func__ , gck - > gckdiv , gck - > parent_id ) ;
2014-09-07 08:14:29 +02:00
spin_lock_irqsave ( gck - > lock , flags ) ;
regmap_write ( gck - > regmap , AT91_PMC_PCR ,
( gck - > id & AT91_PMC_PCR_PID_MASK ) ) ;
regmap_update_bits ( gck - > regmap , AT91_PMC_PCR ,
AT91_PMC_PCR_GCKDIV_MASK | AT91_PMC_PCR_GCKCSS_MASK |
AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN ,
AT91_PMC_PCR_GCKCSS ( gck - > parent_id ) |
AT91_PMC_PCR_CMD |
AT91_PMC_PCR_GCKDIV ( gck - > gckdiv ) |
AT91_PMC_PCR_GCKEN ) ;
spin_unlock_irqrestore ( gck - > lock , flags ) ;
2015-07-31 11:43:12 +02:00
return 0 ;
}
static void clk_generated_disable ( struct clk_hw * hw )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
2014-09-07 08:14:29 +02:00
unsigned long flags ;
spin_lock_irqsave ( gck - > lock , flags ) ;
regmap_write ( gck - > regmap , AT91_PMC_PCR ,
( gck - > id & AT91_PMC_PCR_PID_MASK ) ) ;
regmap_update_bits ( gck - > regmap , AT91_PMC_PCR ,
AT91_PMC_PCR_CMD | AT91_PMC_PCR_GCKEN ,
AT91_PMC_PCR_CMD ) ;
spin_unlock_irqrestore ( gck - > lock , flags ) ;
2015-07-31 11:43:12 +02:00
}
static int clk_generated_is_enabled ( struct clk_hw * hw )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
2014-09-07 08:14:29 +02:00
unsigned long flags ;
unsigned int status ;
2015-07-31 11:43:12 +02:00
2014-09-07 08:14:29 +02:00
spin_lock_irqsave ( gck - > lock , flags ) ;
regmap_write ( gck - > regmap , AT91_PMC_PCR ,
( gck - > id & AT91_PMC_PCR_PID_MASK ) ) ;
regmap_read ( gck - > regmap , AT91_PMC_PCR , & status ) ;
spin_unlock_irqrestore ( gck - > lock , flags ) ;
2015-07-31 11:43:12 +02:00
2014-09-07 08:14:29 +02:00
return status & AT91_PMC_PCR_GCKEN ? 1 : 0 ;
2015-07-31 11:43:12 +02:00
}
static unsigned long
clk_generated_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
return DIV_ROUND_CLOSEST ( parent_rate , gck - > gckdiv + 1 ) ;
}
static int clk_generated_determine_rate ( struct clk_hw * hw ,
struct clk_rate_request * req )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
struct clk_hw * parent = NULL ;
long best_rate = - EINVAL ;
unsigned long tmp_rate , min_rate ;
int best_diff = - 1 ;
int tmp_diff ;
int i ;
for ( i = 0 ; i < clk_hw_get_num_parents ( hw ) ; i + + ) {
u32 div ;
unsigned long parent_rate ;
parent = clk_hw_get_parent_by_index ( hw , i ) ;
if ( ! parent )
continue ;
parent_rate = clk_hw_get_rate ( parent ) ;
min_rate = DIV_ROUND_CLOSEST ( parent_rate , GENERATED_MAX_DIV + 1 ) ;
if ( ! parent_rate | |
( gck - > range . max & & min_rate > gck - > range . max ) )
continue ;
for ( div = 1 ; div < GENERATED_MAX_DIV + 2 ; div + + ) {
tmp_rate = DIV_ROUND_CLOSEST ( parent_rate , div ) ;
tmp_diff = abs ( req - > rate - tmp_rate ) ;
if ( best_diff < 0 | | best_diff > tmp_diff ) {
best_rate = tmp_rate ;
best_diff = tmp_diff ;
req - > best_parent_rate = parent_rate ;
req - > best_parent_hw = parent ;
}
if ( ! best_diff | | tmp_rate < req - > rate )
break ;
}
if ( ! best_diff )
break ;
}
pr_debug ( " GCLK: %s, best_rate = %ld, parent clk: %s @ %ld \n " ,
__func__ , best_rate ,
__clk_get_name ( ( req - > best_parent_hw ) - > clk ) ,
req - > best_parent_rate ) ;
if ( best_rate < 0 )
return best_rate ;
req - > rate = best_rate ;
return 0 ;
}
/* No modification of hardware as we have the flag CLK_SET_PARENT_GATE set */
static int clk_generated_set_parent ( struct clk_hw * hw , u8 index )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
if ( index > = clk_hw_get_num_parents ( hw ) )
return - EINVAL ;
gck - > parent_id = index ;
return 0 ;
}
static u8 clk_generated_get_parent ( struct clk_hw * hw )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
return gck - > parent_id ;
}
/* No modification of hardware as we have the flag CLK_SET_RATE_GATE set */
static int clk_generated_set_rate ( struct clk_hw * hw ,
unsigned long rate ,
unsigned long parent_rate )
{
struct clk_generated * gck = to_clk_generated ( hw ) ;
u32 div ;
if ( ! rate )
return - EINVAL ;
if ( gck - > range . max & & rate > gck - > range . max )
return - EINVAL ;
div = DIV_ROUND_CLOSEST ( parent_rate , rate ) ;
if ( div > GENERATED_MAX_DIV + 1 | | ! div )
return - EINVAL ;
gck - > gckdiv = div - 1 ;
return 0 ;
}
static const struct clk_ops generated_ops = {
. enable = clk_generated_enable ,
. disable = clk_generated_disable ,
. is_enabled = clk_generated_is_enabled ,
. recalc_rate = clk_generated_recalc_rate ,
. determine_rate = clk_generated_determine_rate ,
. get_parent = clk_generated_get_parent ,
. set_parent = clk_generated_set_parent ,
. set_rate = clk_generated_set_rate ,
} ;
/**
* clk_generated_startup - Initialize a given clock to its default parent and
* divisor parameter .
*
* @ gck : Generated clock to set the startup parameters for .
*
* Take parameters from the hardware and update local clock configuration
* accordingly .
*/
static void clk_generated_startup ( struct clk_generated * gck )
{
u32 tmp ;
2014-09-07 08:14:29 +02:00
unsigned long flags ;
2015-07-31 11:43:12 +02:00
2014-09-07 08:14:29 +02:00
spin_lock_irqsave ( gck - > lock , flags ) ;
regmap_write ( gck - > regmap , AT91_PMC_PCR ,
( gck - > id & AT91_PMC_PCR_PID_MASK ) ) ;
regmap_read ( gck - > regmap , AT91_PMC_PCR , & tmp ) ;
spin_unlock_irqrestore ( gck - > lock , flags ) ;
2015-07-31 11:43:12 +02:00
gck - > parent_id = ( tmp & AT91_PMC_PCR_GCKCSS_MASK )
> > AT91_PMC_PCR_GCKCSS_OFFSET ;
gck - > gckdiv = ( tmp & AT91_PMC_PCR_GCKDIV_MASK )
> > AT91_PMC_PCR_GCKDIV_OFFSET ;
}
2016-06-01 14:31:22 -07:00
static struct clk_hw * __init
at91_clk_register_generated ( struct regmap * regmap , spinlock_t * lock ,
const char * name , const char * * parent_names ,
u8 num_parents , u8 id ,
const struct clk_range * range )
2015-07-31 11:43:12 +02:00
{
struct clk_generated * gck ;
struct clk_init_data init ;
2016-06-01 14:31:22 -07:00
struct clk_hw * hw ;
int ret ;
2015-07-31 11:43:12 +02:00
gck = kzalloc ( sizeof ( * gck ) , GFP_KERNEL ) ;
if ( ! gck )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & generated_ops ;
init . parent_names = parent_names ;
init . num_parents = num_parents ;
init . flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE ;
gck - > id = id ;
gck - > hw . init = & init ;
2014-09-07 08:14:29 +02:00
gck - > regmap = regmap ;
gck - > lock = lock ;
2015-07-31 11:43:12 +02:00
gck - > range = * range ;
2017-05-12 16:25:30 +02:00
clk_generated_startup ( gck ) ;
2016-06-01 14:31:22 -07:00
hw = & gck - > hw ;
ret = clk_hw_register ( NULL , & gck - > hw ) ;
if ( ret ) {
2015-07-31 11:43:12 +02:00
kfree ( gck ) ;
2016-06-01 14:31:22 -07:00
hw = ERR_PTR ( ret ) ;
2017-06-05 00:02:57 +02:00
}
2015-07-31 11:43:12 +02:00
2016-06-01 14:31:22 -07:00
return hw ;
2015-07-31 11:43:12 +02:00
}
2016-06-07 17:38:09 +01:00
static void __init of_sama5d2_clk_generated_setup ( struct device_node * np )
2015-07-31 11:43:12 +02:00
{
int num ;
u32 id ;
const char * name ;
2016-06-01 14:31:22 -07:00
struct clk_hw * hw ;
2016-02-19 17:29:17 -08:00
unsigned int num_parents ;
2015-07-31 11:43:12 +02:00
const char * parent_names [ GENERATED_SOURCE_MAX ] ;
struct device_node * gcknp ;
struct clk_range range = CLK_RANGE ( 0 , 0 ) ;
2014-09-07 08:14:29 +02:00
struct regmap * regmap ;
2015-07-31 11:43:12 +02:00
num_parents = of_clk_get_parent_count ( np ) ;
2016-02-19 17:29:17 -08:00
if ( num_parents = = 0 | | num_parents > GENERATED_SOURCE_MAX )
2015-07-31 11:43:12 +02:00
return ;
of_clk_parent_fill ( np , parent_names , num_parents ) ;
num = of_get_child_count ( np ) ;
if ( ! num | | num > PERIPHERAL_MAX )
return ;
2014-09-07 08:14:29 +02:00
regmap = syscon_node_to_regmap ( of_get_parent ( np ) ) ;
if ( IS_ERR ( regmap ) )
return ;
2015-07-31 11:43:12 +02:00
for_each_child_of_node ( np , gcknp ) {
if ( of_property_read_u32 ( gcknp , " reg " , & id ) )
continue ;
if ( id < PERIPHERAL_ID_MIN | | id > = PERIPHERAL_MAX )
continue ;
if ( of_property_read_string ( np , " clock-output-names " , & name ) )
name = gcknp - > name ;
of_at91_get_clk_range ( gcknp , " atmel,clk-output-range " ,
& range ) ;
2016-06-01 14:31:22 -07:00
hw = at91_clk_register_generated ( regmap , & pmc_pcr_lock , name ,
2014-09-07 08:14:29 +02:00
parent_names , num_parents ,
id , & range ) ;
2016-06-01 14:31:22 -07:00
if ( IS_ERR ( hw ) )
2015-07-31 11:43:12 +02:00
continue ;
2016-06-01 14:31:22 -07:00
of_clk_add_hw_provider ( gcknp , of_clk_hw_simple_get , hw ) ;
2015-07-31 11:43:12 +02:00
}
}
2014-09-07 08:14:29 +02:00
CLK_OF_DECLARE ( of_sama5d2_clk_generated_setup , " atmel,sama5d2-clk-generated " ,
of_sama5d2_clk_generated_setup ) ;