2013-10-11 10:48:26 +02:00
/*
* Copyright ( C ) 2013 Boris BREZILLON < b . brezillon @ overkiz . com >
*
* 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>
2013-10-11 10:48:26 +02:00
# include "pmc.h"
# define to_clk_plldiv(hw) container_of(hw, struct clk_plldiv, hw)
struct clk_plldiv {
struct clk_hw hw ;
2014-09-07 08:14:29 +02:00
struct regmap * regmap ;
2013-10-11 10:48:26 +02:00
} ;
static unsigned long clk_plldiv_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_plldiv * plldiv = to_clk_plldiv ( hw ) ;
2014-09-07 08:14:29 +02:00
unsigned int mckr ;
2013-10-11 10:48:26 +02:00
2014-09-07 08:14:29 +02:00
regmap_read ( plldiv - > regmap , AT91_PMC_MCKR , & mckr ) ;
if ( mckr & AT91_PMC_PLLADIV2 )
2013-10-11 10:48:26 +02:00
return parent_rate / 2 ;
return parent_rate ;
}
static long clk_plldiv_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
unsigned long div ;
if ( rate > * parent_rate )
return * parent_rate ;
div = * parent_rate / 2 ;
if ( rate < div )
return div ;
if ( rate - div < * parent_rate - rate )
return div ;
return * parent_rate ;
}
static int clk_plldiv_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct clk_plldiv * plldiv = to_clk_plldiv ( hw ) ;
2014-09-07 08:14:29 +02:00
if ( ( parent_rate ! = rate ) & & ( parent_rate / 2 ! = rate ) )
2013-10-11 10:48:26 +02:00
return - EINVAL ;
2014-09-07 08:14:29 +02:00
regmap_update_bits ( plldiv - > regmap , AT91_PMC_MCKR , AT91_PMC_PLLADIV2 ,
parent_rate ! = rate ? AT91_PMC_PLLADIV2 : 0 ) ;
2013-10-11 10:48:26 +02:00
return 0 ;
}
static const struct clk_ops plldiv_ops = {
. recalc_rate = clk_plldiv_recalc_rate ,
. round_rate = clk_plldiv_round_rate ,
. set_rate = clk_plldiv_set_rate ,
} ;
2016-06-01 14:31:22 -07:00
static struct clk_hw * __init
2014-09-07 08:14:29 +02:00
at91_clk_register_plldiv ( struct regmap * regmap , const char * name ,
2013-10-11 10:48:26 +02:00
const char * parent_name )
{
struct clk_plldiv * plldiv ;
2016-06-01 14:31:22 -07:00
struct clk_hw * hw ;
2013-10-11 10:48:26 +02:00
struct clk_init_data init ;
2016-06-01 14:31:22 -07:00
int ret ;
2013-10-11 10:48:26 +02:00
plldiv = kzalloc ( sizeof ( * plldiv ) , GFP_KERNEL ) ;
if ( ! plldiv )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & plldiv_ops ;
init . parent_names = parent_name ? & parent_name : NULL ;
init . num_parents = parent_name ? 1 : 0 ;
init . flags = CLK_SET_RATE_GATE ;
plldiv - > hw . init = & init ;
2014-09-07 08:14:29 +02:00
plldiv - > regmap = regmap ;
2013-10-11 10:48:26 +02:00
2016-06-01 14:31:22 -07:00
hw = & plldiv - > hw ;
ret = clk_hw_register ( NULL , & plldiv - > hw ) ;
if ( ret ) {
2013-10-11 10:48:26 +02:00
kfree ( plldiv ) ;
2016-06-01 14:31:22 -07:00
hw = ERR_PTR ( ret ) ;
}
2013-10-11 10:48:26 +02:00
2016-06-01 14:31:22 -07:00
return hw ;
2013-10-11 10:48:26 +02:00
}
static void __init
2014-09-07 08:14:29 +02:00
of_at91sam9x5_clk_plldiv_setup ( struct device_node * np )
2013-10-11 10:48:26 +02:00
{
2016-06-01 14:31:22 -07:00
struct clk_hw * hw ;
2013-10-11 10:48:26 +02:00
const char * parent_name ;
const char * name = np - > name ;
2014-09-07 08:14:29 +02:00
struct regmap * regmap ;
2013-10-11 10:48:26 +02:00
parent_name = of_clk_get_parent_name ( np , 0 ) ;
of_property_read_string ( np , " clock-output-names " , & name ) ;
2014-09-07 08:14:29 +02:00
regmap = syscon_node_to_regmap ( of_get_parent ( np ) ) ;
if ( IS_ERR ( regmap ) )
return ;
2013-10-11 10:48:26 +02:00
2016-06-01 14:31:22 -07:00
hw = at91_clk_register_plldiv ( regmap , name , parent_name ) ;
if ( IS_ERR ( hw ) )
2013-10-11 10:48:26 +02:00
return ;
2016-06-01 14:31:22 -07:00
of_clk_add_hw_provider ( np , of_clk_hw_simple_get , hw ) ;
2013-10-11 10:48:26 +02:00
}
2014-09-07 08:14:29 +02:00
CLK_OF_DECLARE ( at91sam9x5_clk_plldiv , " atmel,at91sam9x5-clk-plldiv " ,
of_at91sam9x5_clk_plldiv_setup ) ;