2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-06-29 21:05:26 +02:00
/*
* Copyright ( C ) 2016 Maxime Ripard
* Maxime Ripard < maxime . ripard @ free - electrons . com >
*/
2016-08-25 14:21:58 +08:00
# include <linux/clk.h>
2016-06-29 21:05:26 +02:00
# include <linux/clk-provider.h>
2016-08-25 14:21:58 +08:00
# include <linux/delay.h>
2019-04-18 15:20:22 -07:00
# include <linux/io.h>
2016-06-29 21:05:26 +02:00
# include "ccu_gate.h"
# include "ccu_mux.h"
2017-05-17 09:40:34 +02:00
static u16 ccu_mux_get_prediv ( struct ccu_common * common ,
struct ccu_mux_internal * cm ,
int parent_index )
2016-06-29 21:05:26 +02:00
{
2016-07-26 15:04:25 +08:00
u16 prediv = 1 ;
2016-06-29 21:05:26 +02:00
u32 reg ;
if ( ! ( ( common - > features & CCU_FEATURE_FIXED_PREDIV ) | |
2017-01-19 22:49:26 +01:00
( common - > features & CCU_FEATURE_VARIABLE_PREDIV ) | |
( common - > features & CCU_FEATURE_ALL_PREDIV ) ) )
2017-05-17 09:40:34 +02:00
return 1 ;
2016-06-29 21:05:26 +02:00
2017-05-17 09:40:34 +02:00
if ( common - > features & CCU_FEATURE_ALL_PREDIV )
return common - > prediv ;
2017-01-19 22:49:26 +01:00
2016-06-29 21:05:26 +02:00
reg = readl ( common - > base + common - > reg ) ;
if ( parent_index < 0 ) {
parent_index = reg > > cm - > shift ;
parent_index & = ( 1 < < cm - > width ) - 1 ;
}
2017-05-17 09:40:34 +02:00
if ( common - > features & CCU_FEATURE_FIXED_PREDIV ) {
int i ;
2016-08-25 14:21:57 +08:00
for ( i = 0 ; i < cm - > n_predivs ; i + + )
if ( parent_index = = cm - > fixed_predivs [ i ] . index )
prediv = cm - > fixed_predivs [ i ] . div ;
2017-05-17 09:40:34 +02:00
}
2016-06-29 21:05:26 +02:00
2017-05-19 15:06:08 +08:00
if ( common - > features & CCU_FEATURE_VARIABLE_PREDIV ) {
int i ;
for ( i = 0 ; i < cm - > n_var_predivs ; i + + )
if ( parent_index = = cm - > var_predivs [ i ] . index ) {
u8 div ;
2016-06-29 21:05:26 +02:00
2017-05-19 15:06:08 +08:00
div = reg > > cm - > var_predivs [ i ] . shift ;
div & = ( 1 < < cm - > var_predivs [ i ] . width ) - 1 ;
prediv = div + 1 ;
}
}
2016-06-29 21:05:26 +02:00
2017-05-17 09:40:34 +02:00
return prediv ;
}
2017-05-17 09:40:35 +02:00
unsigned long ccu_mux_helper_apply_prediv ( struct ccu_common * common ,
struct ccu_mux_internal * cm ,
int parent_index ,
unsigned long parent_rate )
2017-05-17 09:40:34 +02:00
{
2017-05-17 09:40:35 +02:00
return parent_rate / ccu_mux_get_prediv ( common , cm , parent_index ) ;
2016-06-29 21:05:26 +02:00
}
2017-06-16 14:50:41 -07:00
static unsigned long ccu_mux_helper_unapply_prediv ( struct ccu_common * common ,
2017-05-17 09:40:36 +02:00
struct ccu_mux_internal * cm ,
int parent_index ,
unsigned long parent_rate )
{
return parent_rate * ccu_mux_get_prediv ( common , cm , parent_index ) ;
}
2016-06-29 21:05:26 +02:00
int ccu_mux_helper_determine_rate ( struct ccu_common * common ,
struct ccu_mux_internal * cm ,
struct clk_rate_request * req ,
unsigned long ( * round ) ( struct ccu_mux_internal * ,
2017-05-17 09:40:31 +02:00
struct clk_hw * ,
unsigned long * ,
2016-06-29 21:05:26 +02:00
unsigned long ,
void * ) ,
void * data )
{
unsigned long best_parent_rate = 0 , best_rate = 0 ;
struct clk_hw * best_parent , * hw = & common - > hw ;
unsigned int i ;
2017-01-28 20:22:31 +08:00
if ( clk_hw_get_flags ( hw ) & CLK_SET_RATE_NO_REPARENT ) {
unsigned long adj_parent_rate ;
best_parent = clk_hw_get_parent ( hw ) ;
best_parent_rate = clk_hw_get_rate ( best_parent ) ;
2017-05-17 09:40:35 +02:00
adj_parent_rate = ccu_mux_helper_apply_prediv ( common , cm , - 1 ,
best_parent_rate ) ;
2017-01-28 20:22:31 +08:00
2017-05-17 09:40:31 +02:00
best_rate = round ( cm , best_parent , & adj_parent_rate ,
req - > rate , data ) ;
2017-01-28 20:22:31 +08:00
2017-05-17 09:40:36 +02:00
/*
* adj_parent_rate might have been modified by our clock .
* Unapply the pre - divider if there ' s one , and give
* the actual frequency the parent needs to run at .
*/
best_parent_rate = ccu_mux_helper_unapply_prediv ( common , cm , - 1 ,
adj_parent_rate ) ;
2017-01-28 20:22:31 +08:00
goto out ;
}
2016-06-29 21:05:26 +02:00
for ( i = 0 ; i < clk_hw_get_num_parents ( hw ) ; i + + ) {
2017-05-17 09:40:36 +02:00
unsigned long tmp_rate , parent_rate ;
2016-06-29 21:05:26 +02:00
struct clk_hw * parent ;
parent = clk_hw_get_parent_by_index ( hw , i ) ;
if ( ! parent )
continue ;
2017-05-17 09:40:36 +02:00
parent_rate = ccu_mux_helper_apply_prediv ( common , cm , i ,
clk_hw_get_rate ( parent ) ) ;
tmp_rate = round ( cm , parent , & parent_rate , req - > rate , data ) ;
2016-06-29 21:05:26 +02:00
2017-05-17 09:40:36 +02:00
/*
* parent_rate might have been modified by our clock .
* Unapply the pre - divider if there ' s one , and give
* the actual frequency the parent needs to run at .
*/
parent_rate = ccu_mux_helper_unapply_prediv ( common , cm , i ,
parent_rate ) ;
2016-06-29 21:05:26 +02:00
if ( tmp_rate = = req - > rate ) {
best_parent = parent ;
best_parent_rate = parent_rate ;
best_rate = tmp_rate ;
goto out ;
}
if ( ( req - > rate - tmp_rate ) < ( req - > rate - best_rate ) ) {
best_rate = tmp_rate ;
best_parent_rate = parent_rate ;
best_parent = parent ;
}
}
if ( best_rate = = 0 )
return - EINVAL ;
out :
req - > best_parent_hw = best_parent ;
req - > best_parent_rate = best_parent_rate ;
req - > rate = best_rate ;
return 0 ;
}
u8 ccu_mux_helper_get_parent ( struct ccu_common * common ,
struct ccu_mux_internal * cm )
{
u32 reg ;
u8 parent ;
reg = readl ( common - > base + common - > reg ) ;
parent = reg > > cm - > shift ;
parent & = ( 1 < < cm - > width ) - 1 ;
2016-08-25 14:21:56 +08:00
if ( cm - > table ) {
int num_parents = clk_hw_get_num_parents ( & common - > hw ) ;
int i ;
for ( i = 0 ; i < num_parents ; i + + )
if ( cm - > table [ i ] = = parent )
return i ;
}
2016-06-29 21:05:26 +02:00
return parent ;
}
int ccu_mux_helper_set_parent ( struct ccu_common * common ,
struct ccu_mux_internal * cm ,
u8 index )
{
unsigned long flags ;
u32 reg ;
2016-08-25 14:21:56 +08:00
if ( cm - > table )
index = cm - > table [ index ] ;
2016-06-29 21:05:26 +02:00
spin_lock_irqsave ( common - > lock , flags ) ;
reg = readl ( common - > base + common - > reg ) ;
reg & = ~ GENMASK ( cm - > width + cm - > shift - 1 , cm - > shift ) ;
writel ( reg | ( index < < cm - > shift ) , common - > base + common - > reg ) ;
spin_unlock_irqrestore ( common - > lock , flags ) ;
return 0 ;
}
static void ccu_mux_disable ( struct clk_hw * hw )
{
struct ccu_mux * cm = hw_to_ccu_mux ( hw ) ;
return ccu_gate_helper_disable ( & cm - > common , cm - > enable ) ;
}
static int ccu_mux_enable ( struct clk_hw * hw )
{
struct ccu_mux * cm = hw_to_ccu_mux ( hw ) ;
return ccu_gate_helper_enable ( & cm - > common , cm - > enable ) ;
}
static int ccu_mux_is_enabled ( struct clk_hw * hw )
{
struct ccu_mux * cm = hw_to_ccu_mux ( hw ) ;
return ccu_gate_helper_is_enabled ( & cm - > common , cm - > enable ) ;
}
static u8 ccu_mux_get_parent ( struct clk_hw * hw )
{
struct ccu_mux * cm = hw_to_ccu_mux ( hw ) ;
return ccu_mux_helper_get_parent ( & cm - > common , & cm - > mux ) ;
}
static int ccu_mux_set_parent ( struct clk_hw * hw , u8 index )
{
struct ccu_mux * cm = hw_to_ccu_mux ( hw ) ;
return ccu_mux_helper_set_parent ( & cm - > common , & cm - > mux , index ) ;
}
static unsigned long ccu_mux_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct ccu_mux * cm = hw_to_ccu_mux ( hw ) ;
2017-05-17 09:40:35 +02:00
return ccu_mux_helper_apply_prediv ( & cm - > common , & cm - > mux , - 1 ,
parent_rate ) ;
2016-06-29 21:05:26 +02:00
}
const struct clk_ops ccu_mux_ops = {
. disable = ccu_mux_disable ,
. enable = ccu_mux_enable ,
. is_enabled = ccu_mux_is_enabled ,
. get_parent = ccu_mux_get_parent ,
. set_parent = ccu_mux_set_parent ,
. determine_rate = __clk_mux_determine_rate ,
. recalc_rate = ccu_mux_recalc_rate ,
} ;
2016-08-25 14:21:58 +08:00
/*
* This clock notifier is called when the frequency of the of the parent
* PLL clock is to be changed . The idea is to switch the parent to a
* stable clock , such as the main oscillator , while the PLL frequency
* stabilizes .
*/
static int ccu_mux_notifier_cb ( struct notifier_block * nb ,
unsigned long event , void * data )
{
struct ccu_mux_nb * mux = to_ccu_mux_nb ( nb ) ;
int ret = 0 ;
if ( event = = PRE_RATE_CHANGE ) {
mux - > original_index = ccu_mux_helper_get_parent ( mux - > common ,
mux - > cm ) ;
ret = ccu_mux_helper_set_parent ( mux - > common , mux - > cm ,
mux - > bypass_index ) ;
} else if ( event = = POST_RATE_CHANGE ) {
ret = ccu_mux_helper_set_parent ( mux - > common , mux - > cm ,
mux - > original_index ) ;
}
udelay ( mux - > delay_us ) ;
return notifier_from_errno ( ret ) ;
}
int ccu_mux_notifier_register ( struct clk * clk , struct ccu_mux_nb * mux_nb )
{
mux_nb - > clk_nb . notifier_call = ccu_mux_notifier_cb ;
return clk_notifier_register ( clk , & mux_nb - > clk_nb ) ;
}