2014-09-15 18:15:53 +02:00
/*
* clk - h32mx . c
*
* Copyright ( C ) 2014 Atmel
*
* Alexandre Belloni < alexandre . belloni @ free - electrons . 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/regmap.h>
# include <linux/mfd/syscon.h>
2014-09-15 18:15:53 +02:00
# include "pmc.h"
# define H32MX_MAX_FREQ 90000000
struct clk_sama5d4_h32mx {
struct clk_hw hw ;
2014-09-07 08:14:29 +02:00
struct regmap * regmap ;
2014-09-15 18:15:53 +02:00
} ;
# define to_clk_sama5d4_h32mx(hw) container_of(hw, struct clk_sama5d4_h32mx, hw)
static unsigned long clk_sama5d4_h32mx_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_sama5d4_h32mx * h32mxclk = to_clk_sama5d4_h32mx ( hw ) ;
2014-09-07 08:14:29 +02:00
unsigned int mckr ;
2014-09-15 18:15:53 +02:00
2014-09-07 08:14:29 +02:00
regmap_read ( h32mxclk - > regmap , AT91_PMC_MCKR , & mckr ) ;
if ( mckr & AT91_PMC_H32MXDIV )
2014-09-15 18:15:53 +02:00
return parent_rate / 2 ;
if ( parent_rate > H32MX_MAX_FREQ )
pr_warn ( " H32MX clock is too fast \n " ) ;
return parent_rate ;
}
static long clk_sama5d4_h32mx_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_sama5d4_h32mx_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct clk_sama5d4_h32mx * h32mxclk = to_clk_sama5d4_h32mx ( hw ) ;
2014-09-07 08:14:29 +02:00
u32 mckr = 0 ;
2014-09-15 18:15:53 +02:00
if ( parent_rate ! = rate & & ( parent_rate / 2 ) ! = rate )
return - EINVAL ;
if ( ( parent_rate / 2 ) = = rate )
2014-09-07 08:14:29 +02:00
mckr = AT91_PMC_H32MXDIV ;
regmap_update_bits ( h32mxclk - > regmap , AT91_PMC_MCKR ,
AT91_PMC_H32MXDIV , mckr ) ;
2014-09-15 18:15:53 +02:00
return 0 ;
}
static const struct clk_ops h32mx_ops = {
. recalc_rate = clk_sama5d4_h32mx_recalc_rate ,
. round_rate = clk_sama5d4_h32mx_round_rate ,
. set_rate = clk_sama5d4_h32mx_set_rate ,
} ;
2014-09-07 08:14:29 +02:00
static void __init of_sama5d4_clk_h32mx_setup ( struct device_node * np )
2014-09-15 18:15:53 +02:00
{
struct clk_sama5d4_h32mx * h32mxclk ;
struct clk_init_data init ;
const char * parent_name ;
2014-09-07 08:14:29 +02:00
struct regmap * regmap ;
2016-06-01 14:31:22 -07:00
int ret ;
2014-09-15 18:15:53 +02:00
2014-09-07 08:14:29 +02:00
regmap = syscon_node_to_regmap ( of_get_parent ( np ) ) ;
if ( IS_ERR ( regmap ) )
return ;
2014-09-15 18:15:53 +02:00
h32mxclk = kzalloc ( sizeof ( * h32mxclk ) , GFP_KERNEL ) ;
if ( ! h32mxclk )
return ;
parent_name = of_clk_get_parent_name ( np , 0 ) ;
init . name = np - > name ;
init . ops = & h32mx_ops ;
init . parent_names = parent_name ? & parent_name : NULL ;
init . num_parents = parent_name ? 1 : 0 ;
init . flags = CLK_SET_RATE_GATE ;
h32mxclk - > hw . init = & init ;
2014-09-07 08:14:29 +02:00
h32mxclk - > regmap = regmap ;
2014-09-15 18:15:53 +02:00
2016-06-01 14:31:22 -07:00
ret = clk_hw_register ( NULL , & h32mxclk - > hw ) ;
if ( ret ) {
2015-06-26 15:30:22 +02:00
kfree ( h32mxclk ) ;
2014-09-15 18:15:53 +02:00
return ;
2015-06-26 15:30:22 +02:00
}
2014-09-15 18:15:53 +02:00
2016-06-01 14:31:22 -07:00
of_clk_add_hw_provider ( np , of_clk_hw_simple_get , & h32mxclk - > hw ) ;
2014-09-15 18:15:53 +02:00
}
2014-09-07 08:14:29 +02:00
CLK_OF_DECLARE ( of_sama5d4_clk_h32mx_setup , " atmel,sama5d4-clk-h32mx " ,
of_sama5d4_clk_h32mx_setup ) ;