2016-06-29 22:05:30 +03:00
/*
* Copyright ( C ) 2016 Maxime Ripard
* Maxime Ripard < maxime . ripard @ 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 "ccu_gate.h"
# include "ccu_nk.h"
2016-09-30 00:50:21 +03:00
struct _ccu_nk {
2016-09-29 23:57:26 +03:00
unsigned long n , min_n , max_n ;
unsigned long k , min_k , max_k ;
2016-09-30 00:50:21 +03:00
} ;
2016-07-26 10:04:24 +03:00
static void ccu_nk_find_best ( unsigned long parent , unsigned long rate ,
2016-09-30 00:50:21 +03:00
struct _ccu_nk * nk )
2016-06-29 22:05:30 +03:00
{
unsigned long best_rate = 0 ;
unsigned int best_k = 0 , best_n = 0 ;
unsigned int _k , _n ;
2016-09-29 23:57:26 +03:00
for ( _k = nk - > min_k ; _k < = nk - > max_k ; _k + + ) {
for ( _n = nk - > min_n ; _n < = nk - > max_n ; _n + + ) {
2016-06-29 22:05:30 +03:00
unsigned long tmp_rate = parent * _n * _k ;
if ( tmp_rate > rate )
continue ;
if ( ( rate - tmp_rate ) < ( rate - best_rate ) ) {
best_rate = tmp_rate ;
best_k = _k ;
best_n = _n ;
}
}
}
2016-09-30 00:50:21 +03:00
nk - > k = best_k ;
nk - > n = best_n ;
2016-06-29 22:05:30 +03:00
}
static void ccu_nk_disable ( struct clk_hw * hw )
{
struct ccu_nk * nk = hw_to_ccu_nk ( hw ) ;
return ccu_gate_helper_disable ( & nk - > common , nk - > enable ) ;
}
static int ccu_nk_enable ( struct clk_hw * hw )
{
struct ccu_nk * nk = hw_to_ccu_nk ( hw ) ;
return ccu_gate_helper_enable ( & nk - > common , nk - > enable ) ;
}
static int ccu_nk_is_enabled ( struct clk_hw * hw )
{
struct ccu_nk * nk = hw_to_ccu_nk ( hw ) ;
return ccu_gate_helper_is_enabled ( & nk - > common , nk - > enable ) ;
}
static unsigned long ccu_nk_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct ccu_nk * nk = hw_to_ccu_nk ( hw ) ;
unsigned long rate , n , k ;
u32 reg ;
reg = readl ( nk - > common . base + nk - > common . reg ) ;
n = reg > > nk - > n . shift ;
n & = ( 1 < < nk - > n . width ) - 1 ;
2016-11-08 20:12:34 +03:00
n + = nk - > n . offset ;
if ( ! n )
n + + ;
2016-06-29 22:05:30 +03:00
k = reg > > nk - > k . shift ;
k & = ( 1 < < nk - > k . width ) - 1 ;
2016-11-08 20:12:34 +03:00
k + = nk - > k . offset ;
if ( ! k )
k + + ;
2016-06-29 22:05:30 +03:00
2016-11-08 20:12:34 +03:00
rate = parent_rate * n * k ;
2016-06-29 22:05:30 +03:00
if ( nk - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nk - > fixed_post_div ;
return rate ;
}
static long ccu_nk_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
struct ccu_nk * nk = hw_to_ccu_nk ( hw ) ;
2016-09-30 00:50:21 +03:00
struct _ccu_nk _nk ;
2016-06-29 22:05:30 +03:00
if ( nk - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate * = nk - > fixed_post_div ;
2017-03-24 11:33:05 +03:00
_nk . min_n = nk - > n . min ? : 1 ;
2016-10-14 13:08:19 +03:00
_nk . max_n = nk - > n . max ? : 1 < < nk - > n . width ;
2017-03-24 11:33:05 +03:00
_nk . min_k = nk - > k . min ? : 1 ;
2016-10-14 13:08:19 +03:00
_nk . max_k = nk - > k . max ? : 1 < < nk - > k . width ;
2016-09-30 00:50:21 +03:00
ccu_nk_find_best ( * parent_rate , rate , & _nk ) ;
rate = * parent_rate * _nk . n * _nk . k ;
2016-06-29 22:05:30 +03:00
if ( nk - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate = rate / nk - > fixed_post_div ;
return rate ;
}
static int ccu_nk_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct ccu_nk * nk = hw_to_ccu_nk ( hw ) ;
unsigned long flags ;
2016-09-30 00:50:21 +03:00
struct _ccu_nk _nk ;
2016-06-29 22:05:30 +03:00
u32 reg ;
if ( nk - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate = rate * nk - > fixed_post_div ;
2017-03-24 11:33:05 +03:00
_nk . min_n = nk - > n . min ? : 1 ;
2016-10-14 13:08:19 +03:00
_nk . max_n = nk - > n . max ? : 1 < < nk - > n . width ;
2017-03-24 11:33:05 +03:00
_nk . min_k = nk - > k . min ? : 1 ;
2016-10-14 13:08:19 +03:00
_nk . max_k = nk - > k . max ? : 1 < < nk - > k . width ;
2016-09-30 00:50:21 +03:00
ccu_nk_find_best ( parent_rate , rate , & _nk ) ;
2016-06-29 22:05:30 +03:00
spin_lock_irqsave ( nk - > common . lock , flags ) ;
reg = readl ( nk - > common . base + nk - > common . reg ) ;
reg & = ~ GENMASK ( nk - > n . width + nk - > n . shift - 1 , nk - > n . shift ) ;
reg & = ~ GENMASK ( nk - > k . width + nk - > k . shift - 1 , nk - > k . shift ) ;
2016-11-08 20:12:34 +03:00
reg | = ( _nk . k - nk - > k . offset ) < < nk - > k . shift ;
reg | = ( _nk . n - nk - > n . offset ) < < nk - > n . shift ;
writel ( reg , nk - > common . base + nk - > common . reg ) ;
2016-06-29 22:05:30 +03:00
spin_unlock_irqrestore ( nk - > common . lock , flags ) ;
ccu_helper_wait_for_lock ( & nk - > common , nk - > lock ) ;
return 0 ;
}
const struct clk_ops ccu_nk_ops = {
. disable = ccu_nk_disable ,
. enable = ccu_nk_enable ,
. is_enabled = ccu_nk_is_enabled ,
. recalc_rate = ccu_nk_recalc_rate ,
. round_rate = ccu_nk_round_rate ,
. set_rate = ccu_nk_set_rate ,
} ;