2019-08-12 00:00:29 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Based on drivers / clk / tegra / clk - emc . c
* Copyright ( c ) 2014 , NVIDIA CORPORATION . All rights reserved .
*
* Author : Dmitry Osipenko < digetx @ gmail . com >
* Copyright ( C ) 2019 GRATE - DRIVER project
*/
# define pr_fmt(fmt) "tegra-emc-clk: " fmt
# include <linux/bits.h>
# include <linux/clk-provider.h>
# include <linux/clk/tegra.h>
# include <linux/err.h>
2020-11-04 19:48:37 +03:00
# include <linux/export.h>
2019-08-12 00:00:29 +03:00
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/slab.h>
# include "clk.h"
# define CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK GENMASK(7, 0)
# define CLK_SOURCE_EMC_2X_CLK_SRC_MASK GENMASK(31, 30)
# define CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT 30
# define MC_EMC_SAME_FREQ BIT(16)
# define USE_PLLM_UD BIT(29)
# define EMC_SRC_PLL_M 0
# define EMC_SRC_PLL_C 1
# define EMC_SRC_PLL_P 2
# define EMC_SRC_CLK_M 3
static const char * const emc_parent_clk_names [ ] = {
" pll_m " , " pll_c " , " pll_p " , " clk_m " ,
} ;
struct tegra_clk_emc {
struct clk_hw hw ;
void __iomem * reg ;
bool mc_same_freq ;
bool want_low_jitter ;
tegra20_clk_emc_round_cb * round_cb ;
void * cb_arg ;
} ;
static inline struct tegra_clk_emc * to_tegra_clk_emc ( struct clk_hw * hw )
{
return container_of ( hw , struct tegra_clk_emc , hw ) ;
}
static unsigned long emc_recalc_rate ( struct clk_hw * hw ,
unsigned long parent_rate )
{
struct tegra_clk_emc * emc = to_tegra_clk_emc ( hw ) ;
u32 val , div ;
val = readl_relaxed ( emc - > reg ) ;
div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ;
return DIV_ROUND_UP ( parent_rate * 2 , div + 2 ) ;
}
static u8 emc_get_parent ( struct clk_hw * hw )
{
struct tegra_clk_emc * emc = to_tegra_clk_emc ( hw ) ;
return readl_relaxed ( emc - > reg ) > > CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ;
}
static int emc_set_parent ( struct clk_hw * hw , u8 index )
{
struct tegra_clk_emc * emc = to_tegra_clk_emc ( hw ) ;
u32 val , div ;
val = readl_relaxed ( emc - > reg ) ;
val & = ~ CLK_SOURCE_EMC_2X_CLK_SRC_MASK ;
val | = index < < CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ;
div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ;
if ( index = = EMC_SRC_PLL_M & & div = = 0 & & emc - > want_low_jitter )
val | = USE_PLLM_UD ;
else
val & = ~ USE_PLLM_UD ;
if ( emc - > mc_same_freq )
val | = MC_EMC_SAME_FREQ ;
else
val & = ~ MC_EMC_SAME_FREQ ;
writel_relaxed ( val , emc - > reg ) ;
fence_udelay ( 1 , emc - > reg ) ;
return 0 ;
}
static int emc_set_rate ( struct clk_hw * hw , unsigned long rate ,
unsigned long parent_rate )
{
struct tegra_clk_emc * emc = to_tegra_clk_emc ( hw ) ;
unsigned int index ;
u32 val , div ;
div = div_frac_get ( rate , parent_rate , 8 , 1 , 0 ) ;
val = readl_relaxed ( emc - > reg ) ;
val & = ~ CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ;
val | = div ;
index = val > > CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ;
if ( index = = EMC_SRC_PLL_M & & div = = 0 & & emc - > want_low_jitter )
val | = USE_PLLM_UD ;
else
val & = ~ USE_PLLM_UD ;
if ( emc - > mc_same_freq )
val | = MC_EMC_SAME_FREQ ;
else
val & = ~ MC_EMC_SAME_FREQ ;
writel_relaxed ( val , emc - > reg ) ;
fence_udelay ( 1 , emc - > reg ) ;
return 0 ;
}
static int emc_set_rate_and_parent ( struct clk_hw * hw ,
unsigned long rate ,
unsigned long parent_rate ,
u8 index )
{
struct tegra_clk_emc * emc = to_tegra_clk_emc ( hw ) ;
u32 val , div ;
div = div_frac_get ( rate , parent_rate , 8 , 1 , 0 ) ;
val = readl_relaxed ( emc - > reg ) ;
val & = ~ CLK_SOURCE_EMC_2X_CLK_SRC_MASK ;
val | = index < < CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ;
val & = ~ CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ;
val | = div ;
if ( index = = EMC_SRC_PLL_M & & div = = 0 & & emc - > want_low_jitter )
val | = USE_PLLM_UD ;
else
val & = ~ USE_PLLM_UD ;
if ( emc - > mc_same_freq )
val | = MC_EMC_SAME_FREQ ;
else
val & = ~ MC_EMC_SAME_FREQ ;
writel_relaxed ( val , emc - > reg ) ;
fence_udelay ( 1 , emc - > reg ) ;
return 0 ;
}
static int emc_determine_rate ( struct clk_hw * hw , struct clk_rate_request * req )
{
struct tegra_clk_emc * emc = to_tegra_clk_emc ( hw ) ;
struct clk_hw * parent_hw ;
unsigned long divided_rate ;
unsigned long parent_rate ;
unsigned int i ;
long emc_rate ;
int div ;
emc_rate = emc - > round_cb ( req - > rate , req - > min_rate , req - > max_rate ,
emc - > cb_arg ) ;
if ( emc_rate < 0 )
return emc_rate ;
for ( i = 0 ; i < ARRAY_SIZE ( emc_parent_clk_names ) ; i + + ) {
parent_hw = clk_hw_get_parent_by_index ( hw , i ) ;
if ( req - > best_parent_hw = = parent_hw )
parent_rate = req - > best_parent_rate ;
else
parent_rate = clk_hw_get_rate ( parent_hw ) ;
if ( emc_rate > parent_rate )
continue ;
div = div_frac_get ( emc_rate , parent_rate , 8 , 1 , 0 ) ;
divided_rate = DIV_ROUND_UP ( parent_rate * 2 , div + 2 ) ;
if ( divided_rate ! = emc_rate )
continue ;
req - > best_parent_rate = parent_rate ;
req - > best_parent_hw = parent_hw ;
req - > rate = emc_rate ;
break ;
}
if ( i = = ARRAY_SIZE ( emc_parent_clk_names ) ) {
pr_err_once ( " can't find parent for rate %lu emc_rate %lu \n " ,
req - > rate , emc_rate ) ;
return - EINVAL ;
}
return 0 ;
}
static const struct clk_ops tegra_clk_emc_ops = {
. recalc_rate = emc_recalc_rate ,
. get_parent = emc_get_parent ,
. set_parent = emc_set_parent ,
. set_rate = emc_set_rate ,
. set_rate_and_parent = emc_set_rate_and_parent ,
. determine_rate = emc_determine_rate ,
} ;
void tegra20_clk_set_emc_round_callback ( tegra20_clk_emc_round_cb * round_cb ,
void * cb_arg )
{
struct clk * clk = __clk_lookup ( " emc " ) ;
struct tegra_clk_emc * emc ;
struct clk_hw * hw ;
if ( clk ) {
hw = __clk_get_hw ( clk ) ;
emc = to_tegra_clk_emc ( hw ) ;
emc - > round_cb = round_cb ;
emc - > cb_arg = cb_arg ;
}
}
2020-11-04 19:48:37 +03:00
EXPORT_SYMBOL_GPL ( tegra20_clk_set_emc_round_callback ) ;
2019-08-12 00:00:29 +03:00
bool tegra20_clk_emc_driver_available ( struct clk_hw * emc_hw )
{
return to_tegra_clk_emc ( emc_hw ) - > round_cb ! = NULL ;
}
struct clk * tegra20_clk_register_emc ( void __iomem * ioaddr , bool low_jitter )
{
struct tegra_clk_emc * emc ;
struct clk_init_data init ;
struct clk * clk ;
emc = kzalloc ( sizeof ( * emc ) , GFP_KERNEL ) ;
if ( ! emc )
return NULL ;
/*
* EMC stands for External Memory Controller .
*
* We don ' t want EMC clock to be disabled ever by gating its
* parent and whatnot because system is busted immediately in that
* case , hence the clock is marked as critical .
*/
init . name = " emc " ;
init . ops = & tegra_clk_emc_ops ;
init . flags = CLK_IS_CRITICAL ;
init . parent_names = emc_parent_clk_names ;
init . num_parents = ARRAY_SIZE ( emc_parent_clk_names ) ;
emc - > reg = ioaddr ;
emc - > hw . init = & init ;
emc - > want_low_jitter = low_jitter ;
clk = clk_register ( NULL , & emc - > hw ) ;
if ( IS_ERR ( clk ) ) {
kfree ( emc ) ;
return NULL ;
}
return clk ;
}
int tegra20_clk_prepare_emc_mc_same_freq ( struct clk * emc_clk , bool same )
{
struct tegra_clk_emc * emc ;
struct clk_hw * hw ;
if ( ! emc_clk )
return - EINVAL ;
hw = __clk_get_hw ( emc_clk ) ;
emc = to_tegra_clk_emc ( hw ) ;
emc - > mc_same_freq = same ;
return 0 ;
}
2020-11-04 19:48:37 +03:00
EXPORT_SYMBOL_GPL ( tegra20_clk_prepare_emc_mc_same_freq ) ;