2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-06-29 22:05:31 +03:00
/*
* Copyright ( C ) 2016 Maxime Ripard
* Maxime Ripard < maxime . ripard @ free - electrons . com >
*/
# include <linux/clk-provider.h>
2019-04-19 01:20:22 +03:00
# include <linux/io.h>
2016-06-29 22:05:31 +03:00
# include "ccu_frac.h"
# include "ccu_gate.h"
# include "ccu_nm.h"
2016-09-29 23:53:12 +03:00
struct _ccu_nm {
2016-09-29 23:57:26 +03:00
unsigned long n , min_n , max_n ;
unsigned long m , min_m , max_m ;
2016-09-29 23:53:12 +03:00
} ;
2018-11-04 21:26:40 +03:00
static unsigned long ccu_nm_calc_rate ( unsigned long parent ,
unsigned long n , unsigned long m )
{
u64 rate = parent ;
rate * = n ;
do_div ( rate , m ) ;
return rate ;
}
2023-08-07 15:43:39 +03:00
static unsigned long ccu_nm_find_best ( struct ccu_common * common , unsigned long parent ,
unsigned long rate , struct _ccu_nm * nm )
2016-09-29 23:53:12 +03:00
{
unsigned long best_rate = 0 ;
unsigned long best_n = 0 , best_m = 0 ;
unsigned long _n , _m ;
2016-09-29 23:57:26 +03:00
for ( _n = nm - > min_n ; _n < = nm - > max_n ; _n + + ) {
for ( _m = nm - > min_m ; _m < = nm - > max_m ; _m + + ) {
2018-11-04 21:26:40 +03:00
unsigned long tmp_rate = ccu_nm_calc_rate ( parent ,
_n , _m ) ;
2016-09-29 23:53:12 +03:00
2023-08-07 15:43:39 +03:00
if ( ccu_is_better_rate ( common , rate , tmp_rate , best_rate ) ) {
2016-09-29 23:53:12 +03:00
best_rate = tmp_rate ;
best_n = _n ;
best_m = _m ;
}
}
}
nm - > n = best_n ;
nm - > m = best_m ;
2022-12-31 20:30:55 +03:00
return best_rate ;
2016-09-29 23:53:12 +03:00
}
2016-06-29 22:05:31 +03:00
static void ccu_nm_disable ( struct clk_hw * hw )
{
struct ccu_nm * nm = hw_to_ccu_nm ( hw ) ;
return ccu_gate_helper_disable ( & nm - > common , nm - > enable ) ;
}
static int ccu_nm_enable ( struct clk_hw * hw )
{
struct ccu_nm * nm = hw_to_ccu_nm ( hw ) ;
return ccu_gate_helper_enable ( & nm - > common , nm - > enable ) ;
}
static int ccu_nm_is_enabled ( struct clk_hw * hw )
{
struct ccu_nm * nm = hw_to_ccu_nm ( hw ) ;
return ccu_gate_helper_is_enabled ( & nm - > common , nm - > enable ) ;
}
static unsigned long ccu_nm_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct ccu_nm * nm = hw_to_ccu_nm ( hw ) ;
2017-12-08 11:35:10 +03:00
unsigned long rate ;
2016-06-29 22:05:31 +03:00
unsigned long n , m ;
u32 reg ;
2017-12-08 11:35:10 +03:00
if ( ccu_frac_helper_is_enabled ( & nm - > common , & nm - > frac ) ) {
rate = ccu_frac_helper_read_rate ( & nm - > common , & nm - > frac ) ;
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
return rate ;
}
2016-06-29 22:05:31 +03:00
reg = readl ( nm - > common . base + nm - > common . reg ) ;
n = reg > > nm - > n . shift ;
n & = ( 1 < < nm - > n . width ) - 1 ;
2016-11-08 20:12:34 +03:00
n + = nm - > n . offset ;
if ( ! n )
n + + ;
2016-06-29 22:05:31 +03:00
m = reg > > nm - > m . shift ;
m & = ( 1 < < nm - > m . width ) - 1 ;
2016-11-08 20:12:34 +03:00
m + = nm - > m . offset ;
if ( ! m )
m + + ;
2016-06-29 22:05:31 +03:00
2017-12-08 11:35:10 +03:00
if ( ccu_sdm_helper_is_enabled ( & nm - > common , & nm - > sdm ) )
rate = ccu_sdm_helper_read_rate ( & nm - > common , & nm - > sdm , m , n ) ;
else
2018-11-04 21:26:40 +03:00
rate = ccu_nm_calc_rate ( parent_rate , n , m ) ;
2017-12-08 11:35:10 +03:00
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
2017-10-12 11:37:00 +03:00
2017-12-08 11:35:10 +03:00
return rate ;
2016-06-29 22:05:31 +03:00
}
static long ccu_nm_round_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long * parent_rate )
{
struct ccu_nm * nm = hw_to_ccu_nm ( hw ) ;
2016-09-29 23:53:12 +03:00
struct _ccu_nm _nm ;
2016-06-29 22:05:31 +03:00
2017-12-08 11:35:10 +03:00
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate * = nm - > fixed_post_div ;
2018-03-02 00:34:27 +03:00
if ( rate < nm - > min_rate ) {
rate = nm - > min_rate ;
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
return rate ;
}
2018-08-09 19:52:13 +03:00
if ( nm - > max_rate & & rate > nm - > max_rate ) {
rate = nm - > max_rate ;
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
return rate ;
}
2017-12-08 11:35:10 +03:00
if ( ccu_frac_helper_has_rate ( & nm - > common , & nm - > frac , rate ) ) {
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
2017-10-12 11:36:58 +03:00
return rate ;
2017-12-08 11:35:10 +03:00
}
2017-10-12 11:36:58 +03:00
2017-12-08 11:35:10 +03:00
if ( ccu_sdm_helper_has_rate ( & nm - > common , & nm - > sdm , rate ) ) {
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
2017-10-12 11:37:00 +03:00
return rate ;
2017-12-08 11:35:10 +03:00
}
2017-10-12 11:37:00 +03:00
2017-03-24 11:33:05 +03:00
_nm . min_n = nm - > n . min ? : 1 ;
2016-10-14 13:08:19 +03:00
_nm . max_n = nm - > n . max ? : 1 < < nm - > n . width ;
2016-09-29 23:57:26 +03:00
_nm . min_m = 1 ;
2016-09-29 23:53:12 +03:00
_nm . max_m = nm - > m . max ? : 1 < < nm - > m . width ;
2016-09-06 13:29:04 +03:00
2023-08-07 15:43:39 +03:00
rate = ccu_nm_find_best ( & nm - > common , * parent_rate , rate , & _nm ) ;
2017-12-08 11:35:10 +03:00
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate / = nm - > fixed_post_div ;
2016-06-29 22:05:31 +03:00
2017-12-08 11:35:10 +03:00
return rate ;
2016-06-29 22:05:31 +03:00
}
static int ccu_nm_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct ccu_nm * nm = hw_to_ccu_nm ( hw ) ;
2016-09-29 23:53:12 +03:00
struct _ccu_nm _nm ;
2016-06-29 22:05:31 +03:00
unsigned long flags ;
u32 reg ;
2017-12-08 11:35:10 +03:00
/* Adjust target rate according to post-dividers */
if ( nm - > common . features & CCU_FEATURE_FIXED_POSTDIV )
rate = rate * nm - > fixed_post_div ;
2017-07-30 19:41:47 +03:00
if ( ccu_frac_helper_has_rate ( & nm - > common , & nm - > frac , rate ) ) {
spin_lock_irqsave ( nm - > common . lock , flags ) ;
/* most SoCs require M to be 0 if fractional mode is used */
reg = readl ( nm - > common . base + nm - > common . reg ) ;
reg & = ~ GENMASK ( nm - > m . width + nm - > m . shift - 1 , nm - > m . shift ) ;
writel ( reg , nm - > common . base + nm - > common . reg ) ;
spin_unlock_irqrestore ( nm - > common . lock , flags ) ;
ccu_frac_helper_enable ( & nm - > common , & nm - > frac ) ;
2017-07-30 19:41:50 +03:00
return ccu_frac_helper_set_rate ( & nm - > common , & nm - > frac ,
rate , nm - > lock ) ;
2017-07-30 19:41:47 +03:00
} else {
2016-06-29 22:05:31 +03:00
ccu_frac_helper_disable ( & nm - > common , & nm - > frac ) ;
2017-07-30 19:41:47 +03:00
}
2016-06-29 22:05:31 +03:00
2017-03-24 11:33:06 +03:00
_nm . min_n = nm - > n . min ? : 1 ;
2016-10-14 13:08:19 +03:00
_nm . max_n = nm - > n . max ? : 1 < < nm - > n . width ;
2016-09-29 23:57:26 +03:00
_nm . min_m = 1 ;
2016-09-29 23:53:12 +03:00
_nm . max_m = nm - > m . max ? : 1 < < nm - > m . width ;
2016-09-06 13:29:04 +03:00
2017-10-12 11:37:00 +03:00
if ( ccu_sdm_helper_has_rate ( & nm - > common , & nm - > sdm , rate ) ) {
ccu_sdm_helper_enable ( & nm - > common , & nm - > sdm , rate ) ;
/* Sigma delta modulation requires specific N and M factors */
ccu_sdm_helper_get_factors ( & nm - > common , & nm - > sdm , rate ,
& _nm . m , & _nm . n ) ;
} else {
ccu_sdm_helper_disable ( & nm - > common , & nm - > sdm ) ;
2023-08-07 15:43:39 +03:00
ccu_nm_find_best ( & nm - > common , parent_rate , rate , & _nm ) ;
2017-10-12 11:37:00 +03:00
}
2016-06-29 22:05:31 +03:00
spin_lock_irqsave ( nm - > common . lock , flags ) ;
reg = readl ( nm - > common . base + nm - > common . reg ) ;
reg & = ~ GENMASK ( nm - > n . width + nm - > n . shift - 1 , nm - > n . shift ) ;
reg & = ~ GENMASK ( nm - > m . width + nm - > m . shift - 1 , nm - > m . shift ) ;
2016-11-08 20:12:34 +03:00
reg | = ( _nm . n - nm - > n . offset ) < < nm - > n . shift ;
reg | = ( _nm . m - nm - > m . offset ) < < nm - > m . shift ;
writel ( reg , nm - > common . base + nm - > common . reg ) ;
2016-06-29 22:05:31 +03:00
spin_unlock_irqrestore ( nm - > common . lock , flags ) ;
ccu_helper_wait_for_lock ( & nm - > common , nm - > lock ) ;
return 0 ;
}
const struct clk_ops ccu_nm_ops = {
. disable = ccu_nm_disable ,
. enable = ccu_nm_enable ,
. is_enabled = ccu_nm_is_enabled ,
. recalc_rate = ccu_nm_recalc_rate ,
. round_rate = ccu_nm_round_rate ,
. set_rate = ccu_nm_set_rate ,
} ;
2021-11-19 06:33:34 +03:00
EXPORT_SYMBOL_NS_GPL ( ccu_nm_ops , SUNXI_CCU ) ;