2016-06-29 21:05:26 +02: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 .
*/
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>
2016-06-29 21:05:26 +02:00
# include "ccu_gate.h"
# include "ccu_mux.h"
void ccu_mux_helper_adjust_parent_for_prediv ( struct ccu_common * common ,
struct ccu_mux_internal * cm ,
int parent_index ,
unsigned long * parent_rate )
{
2016-07-26 15:04:25 +08:00
u16 prediv = 1 ;
2016-06-29 21:05:26 +02:00
u32 reg ;
2016-08-25 14:21:57 +08:00
int i ;
2016-06-29 21:05:26 +02:00
if ( ! ( ( common - > features & CCU_FEATURE_FIXED_PREDIV ) | |
( common - > features & CCU_FEATURE_VARIABLE_PREDIV ) ) )
return ;
reg = readl ( common - > base + common - > reg ) ;
if ( parent_index < 0 ) {
parent_index = reg > > cm - > shift ;
parent_index & = ( 1 < < cm - > width ) - 1 ;
}
if ( common - > features & CCU_FEATURE_FIXED_PREDIV )
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 ;
2016-06-29 21:05:26 +02:00
if ( common - > features & CCU_FEATURE_VARIABLE_PREDIV )
if ( parent_index = = cm - > variable_prediv . index ) {
u8 div ;
div = reg > > cm - > variable_prediv . shift ;
div & = ( 1 < < cm - > variable_prediv . width ) - 1 ;
prediv = div + 1 ;
}
* parent_rate = * parent_rate / prediv ;
}
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 * ,
unsigned long ,
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 ;
for ( i = 0 ; i < clk_hw_get_num_parents ( hw ) ; i + + ) {
unsigned long tmp_rate , parent_rate ;
struct clk_hw * parent ;
parent = clk_hw_get_parent_by_index ( hw , i ) ;
if ( ! parent )
continue ;
parent_rate = clk_hw_get_rate ( parent ) ;
ccu_mux_helper_adjust_parent_for_prediv ( common , cm , i ,
& parent_rate ) ;
tmp_rate = round ( cm , clk_hw_get_rate ( parent ) , req - > rate , data ) ;
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 ) ;
ccu_mux_helper_adjust_parent_for_prediv ( & cm - > common , & cm - > mux , - 1 ,
& parent_rate ) ;
return parent_rate ;
}
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 ) ;
}