2020-03-19 22:02:18 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Based on clk - super . c
* Copyright ( c ) 2012 , NVIDIA CORPORATION . All rights reserved .
*
* Based on older tegra20 - cpufreq driver by Colin Cross < ccross @ google . com >
* Copyright ( C ) 2010 Google , Inc .
*
* Author : Dmitry Osipenko < digetx @ gmail . com >
* Copyright ( C ) 2019 GRATE - DRIVER project
*/
# include <linux/bits.h>
# include <linux/clk-provider.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/types.h>
# include "clk.h"
# define PLLP_INDEX 4
# define PLLX_INDEX 8
# define SUPER_CDIV_ENB BIT(31)
2021-05-16 19:30:38 +03:00
# define TSENSOR_SLOWDOWN BIT(23)
2020-03-19 22:02:20 +03:00
static struct tegra_clk_super_mux * cclk_super ;
static bool cclk_on_pllx ;
2020-03-19 22:02:18 +03:00
static u8 cclk_super_get_parent ( struct clk_hw * hw )
{
return tegra_clk_super_ops . get_parent ( hw ) ;
}
static int cclk_super_set_parent ( struct clk_hw * hw , u8 index )
{
return tegra_clk_super_ops . set_parent ( hw , index ) ;
}
static int cclk_super_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
return tegra_clk_super_ops . set_rate ( hw , rate , parent_rate ) ;
}
static unsigned long cclk_super_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
2021-05-16 19:30:38 +03:00
struct tegra_clk_super_mux * super = to_clk_super_mux ( hw ) ;
u32 val = readl_relaxed ( super - > reg ) ;
unsigned int div2 ;
/* check whether thermal throttling is active */
if ( val & TSENSOR_SLOWDOWN )
div2 = 1 ;
else
div2 = 0 ;
2020-03-19 22:02:18 +03:00
if ( cclk_super_get_parent ( hw ) = = PLLX_INDEX )
2021-05-16 19:30:38 +03:00
return parent_rate > > div2 ;
2020-03-19 22:02:18 +03:00
2021-05-16 19:30:38 +03:00
return tegra_clk_super_ops . recalc_rate ( hw , parent_rate ) > > div2 ;
2020-03-19 22:02:18 +03:00
}
static int cclk_super_determine_rate ( struct clk_hw * hw ,
struct clk_rate_request * req )
{
struct clk_hw * pllp_hw = clk_hw_get_parent_by_index ( hw , PLLP_INDEX ) ;
struct clk_hw * pllx_hw = clk_hw_get_parent_by_index ( hw , PLLX_INDEX ) ;
struct tegra_clk_super_mux * super = to_clk_super_mux ( hw ) ;
unsigned long pllp_rate ;
long rate = req - > rate ;
if ( WARN_ON_ONCE ( ! pllp_hw | | ! pllx_hw ) )
return - EINVAL ;
/*
* Switch parent to PLLP for all CCLK rates that are suitable for PLLP .
* PLLX will be disabled in this case , saving some power .
*/
pllp_rate = clk_hw_get_rate ( pllp_hw ) ;
if ( rate < = pllp_rate ) {
if ( super - > flags & TEGRA20_SUPER_CLK )
rate = pllp_rate ;
2023-06-30 15:07:48 +02:00
else {
struct clk_rate_request parent = {
. rate = req - > rate ,
. best_parent_rate = pllp_rate ,
} ;
clk_hw_get_rate_range ( hw , & parent . min_rate ,
& parent . max_rate ) ;
tegra_clk_super_ops . determine_rate ( hw , & parent ) ;
pllp_rate = parent . best_parent_rate ;
rate = parent . rate ;
}
2020-03-19 22:02:18 +03:00
req - > best_parent_rate = pllp_rate ;
req - > best_parent_hw = pllp_hw ;
req - > rate = rate ;
} else {
rate = clk_hw_round_rate ( pllx_hw , rate ) ;
req - > best_parent_rate = rate ;
req - > best_parent_hw = pllx_hw ;
req - > rate = rate ;
}
if ( WARN_ON_ONCE ( rate < = 0 ) )
return - EINVAL ;
return 0 ;
}
static const struct clk_ops tegra_cclk_super_ops = {
. get_parent = cclk_super_get_parent ,
. set_parent = cclk_super_set_parent ,
. set_rate = cclk_super_set_rate ,
. recalc_rate = cclk_super_recalc_rate ,
. determine_rate = cclk_super_determine_rate ,
} ;
static const struct clk_ops tegra_cclk_super_mux_ops = {
. get_parent = cclk_super_get_parent ,
. set_parent = cclk_super_set_parent ,
. determine_rate = cclk_super_determine_rate ,
} ;
struct clk * tegra_clk_register_super_cclk ( const char * name ,
const char * const * parent_names , u8 num_parents ,
unsigned long flags , void __iomem * reg , u8 clk_super_flags ,
spinlock_t * lock )
{
struct tegra_clk_super_mux * super ;
struct clk * clk ;
struct clk_init_data init ;
u32 val ;
2020-03-19 22:02:20 +03:00
if ( WARN_ON ( cclk_super ) )
return ERR_PTR ( - EBUSY ) ;
2020-03-19 22:02:18 +03:00
super = kzalloc ( sizeof ( * super ) , GFP_KERNEL ) ;
if ( ! super )
return ERR_PTR ( - ENOMEM ) ;
init . name = name ;
init . flags = flags ;
init . parent_names = parent_names ;
init . num_parents = num_parents ;
super - > reg = reg ;
super - > lock = lock ;
super - > width = 4 ;
super - > flags = clk_super_flags ;
super - > hw . init = & init ;
if ( super - > flags & TEGRA20_SUPER_CLK ) {
init . ops = & tegra_cclk_super_mux_ops ;
} else {
init . ops = & tegra_cclk_super_ops ;
super - > frac_div . reg = reg + 4 ;
super - > frac_div . shift = 16 ;
super - > frac_div . width = 8 ;
super - > frac_div . frac_width = 1 ;
super - > frac_div . lock = lock ;
super - > div_ops = & tegra_clk_frac_div_ops ;
}
/*
* Tegra30 + has the following CPUG clock topology :
*
* + - - - + + - - - - - - - + + - + + - + + - +
* PLLP + - > + + - > + DIVIDER + - > + 0 | + - - - - - - - - > + 0 | - - - - - - - - - - - - - > + 0 |
* | | + - - - - - - - + | | | + - - - + | | | | |
* PLLC + - > + MUX | | + - > + | S | | + - > + | + - > + CPU
* . . . | | | | | | K | | | | + - - - - - - - + | |
* PLLX + - > + - - > + - - - - - - - - - - - - > + 1 | + - > + I + - > + 1 | + - > + DIV2 + - > + 1 |
* + - - - + + + + | P | + + + | SKIPPER | + + +
* ^ | P | ^ + - - - - - - - + ^
* | | E | | |
* PLLX_SEL + - - + | R | | OVERHEAT + - - +
* + - - - + |
* |
* SUPER_CDIV_ENB + - - +
*
* Tegra20 is similar , but simpler . It doesn ' t have the divider and
* thermal DIV2 skipper .
*
* At least for now we ' re not going to use clock - skipper , hence let ' s
* ensure that it is disabled .
*/
val = readl_relaxed ( reg + 4 ) ;
val & = ~ SUPER_CDIV_ENB ;
writel_relaxed ( val , reg + 4 ) ;
clk = clk_register ( NULL , & super - > hw ) ;
if ( IS_ERR ( clk ) )
kfree ( super ) ;
2020-03-19 22:02:20 +03:00
else
cclk_super = super ;
2020-03-19 22:02:18 +03:00
return clk ;
}
2020-03-19 22:02:20 +03:00
int tegra_cclk_pre_pllx_rate_change ( void )
{
if ( IS_ERR_OR_NULL ( cclk_super ) )
return - EINVAL ;
if ( cclk_super_get_parent ( & cclk_super - > hw ) = = PLLX_INDEX )
cclk_on_pllx = true ;
else
cclk_on_pllx = false ;
/*
* CPU needs to be temporarily re - parented away from PLLX if PLLX
* changes its rate . PLLP is a safe parent for CPU on all Tegra SoCs .
*/
if ( cclk_on_pllx )
cclk_super_set_parent ( & cclk_super - > hw , PLLP_INDEX ) ;
return 0 ;
}
void tegra_cclk_post_pllx_rate_change ( void )
{
if ( cclk_on_pllx )
cclk_super_set_parent ( & cclk_super - > hw , PLLX_INDEX ) ;
}