2014-05-15 17:40:25 +04:00
/*
* Copyright ( C ) 2014 Intel Corporation
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* Adjustable fractional divider clock implementation .
* Output rate = ( m / n ) * parent_rate .
*/
# include <linux/clk-provider.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/slab.h>
# include <linux/gcd.h>
# define to_clk_fd(_hw) container_of(_hw, struct clk_fractional_divider, hw)
static unsigned long clk_fd_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct clk_fractional_divider * fd = to_clk_fd ( hw ) ;
unsigned long flags = 0 ;
u32 val , m , n ;
u64 ret ;
if ( fd - > lock )
spin_lock_irqsave ( fd - > lock , flags ) ;
2015-07-24 22:21:12 +03:00
else
__acquire ( fd - > lock ) ;
2014-05-15 17:40:25 +04:00
val = clk_readl ( fd - > reg ) ;
if ( fd - > lock )
spin_unlock_irqrestore ( fd - > lock , flags ) ;
2015-07-24 22:21:12 +03:00
else
__release ( fd - > lock ) ;
2014-05-15 17:40:25 +04:00
m = ( val & fd - > mmask ) > > fd - > mshift ;
n = ( val & fd - > nmask ) > > fd - > nshift ;
2015-02-02 16:37:04 +03:00
if ( ! n | | ! m )
return parent_rate ;
2014-08-28 14:46:10 +04:00
ret = ( u64 ) parent_rate * m ;
2014-05-15 17:40:25 +04:00
do_div ( ret , n ) ;
return ret ;
}
static long clk_fd_round_rate ( struct clk_hw * hw , unsigned long rate ,
2015-09-22 18:54:08 +03:00
unsigned long * parent_rate )
2014-05-15 17:40:25 +04:00
{
struct clk_fractional_divider * fd = to_clk_fd ( hw ) ;
unsigned maxn = ( fd - > nmask > > fd - > nshift ) + 1 ;
unsigned div ;
2015-09-22 18:54:08 +03:00
if ( ! rate | | rate > = * parent_rate )
return * parent_rate ;
2014-05-15 17:40:25 +04:00
2015-09-22 18:54:08 +03:00
div = gcd ( * parent_rate , rate ) ;
2014-05-15 17:40:25 +04:00
2015-09-22 18:54:08 +03:00
while ( ( * parent_rate / div ) > maxn ) {
2014-05-15 17:40:25 +04:00
div < < = 1 ;
rate < < = 1 ;
}
return rate ;
}
static int clk_fd_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct clk_fractional_divider * fd = to_clk_fd ( hw ) ;
unsigned long flags = 0 ;
unsigned long div ;
unsigned n , m ;
u32 val ;
div = gcd ( parent_rate , rate ) ;
m = rate / div ;
n = parent_rate / div ;
if ( fd - > lock )
spin_lock_irqsave ( fd - > lock , flags ) ;
2015-07-24 22:21:12 +03:00
else
__acquire ( fd - > lock ) ;
2014-05-15 17:40:25 +04:00
val = clk_readl ( fd - > reg ) ;
val & = ~ ( fd - > mmask | fd - > nmask ) ;
val | = ( m < < fd - > mshift ) | ( n < < fd - > nshift ) ;
clk_writel ( val , fd - > reg ) ;
if ( fd - > lock )
spin_unlock_irqrestore ( fd - > lock , flags ) ;
2015-07-24 22:21:12 +03:00
else
__release ( fd - > lock ) ;
2014-05-15 17:40:25 +04:00
return 0 ;
}
const struct clk_ops clk_fractional_divider_ops = {
. recalc_rate = clk_fd_recalc_rate ,
. round_rate = clk_fd_round_rate ,
. set_rate = clk_fd_set_rate ,
} ;
EXPORT_SYMBOL_GPL ( clk_fractional_divider_ops ) ;
struct clk * clk_register_fractional_divider ( struct device * dev ,
const char * name , const char * parent_name , unsigned long flags ,
void __iomem * reg , u8 mshift , u8 mwidth , u8 nshift , u8 nwidth ,
u8 clk_divider_flags , spinlock_t * lock )
{
struct clk_fractional_divider * fd ;
struct clk_init_data init ;
struct clk * clk ;
fd = kzalloc ( sizeof ( * fd ) , GFP_KERNEL ) ;
2015-05-15 02:47:10 +03:00
if ( ! fd )
2014-05-15 17:40:25 +04:00
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . ops = & clk_fractional_divider_ops ;
init . flags = flags | CLK_IS_BASIC ;
init . parent_names = parent_name ? & parent_name : NULL ;
init . num_parents = parent_name ? 1 : 0 ;
fd - > reg = reg ;
fd - > mshift = mshift ;
2015-09-22 18:54:09 +03:00
fd - > mwidth = mwidth ;
fd - > mmask = GENMASK ( mwidth - 1 , 0 ) < < mshift ;
2014-05-15 17:40:25 +04:00
fd - > nshift = nshift ;
2015-09-22 18:54:09 +03:00
fd - > nwidth = nwidth ;
fd - > nmask = GENMASK ( nwidth - 1 , 0 ) < < nshift ;
2014-05-15 17:40:25 +04:00
fd - > flags = clk_divider_flags ;
fd - > lock = lock ;
fd - > hw . init = & init ;
clk = clk_register ( dev , & fd - > hw ) ;
if ( IS_ERR ( clk ) )
kfree ( fd ) ;
return clk ;
}
EXPORT_SYMBOL_GPL ( clk_register_fractional_divider ) ;