2019-05-29 17:18:02 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-04-11 11:09:15 +03:00
/*
* Copyright ( c ) 2017 , NVIDIA CORPORATION . All rights reserved
*/
# include <linux/cpufreq.h>
# include <linux/dma-mapping.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <soc/tegra/bpmp.h>
# include <soc/tegra/bpmp-abi.h>
2020-12-02 12:14:17 +03:00
# define TEGRA186_NUM_CLUSTERS 2
2020-12-02 12:14:16 +03:00
# define EDVD_OFFSET_A57(core) ((SZ_64K * 6) + (0x20 + (core) * 0x4))
# define EDVD_OFFSET_DENVER(core) ((SZ_64K * 7) + (0x20 + (core) * 0x4))
# define EDVD_CORE_VOLT_FREQ_F_SHIFT 0
# define EDVD_CORE_VOLT_FREQ_F_MASK 0xffff
# define EDVD_CORE_VOLT_FREQ_V_SHIFT 16
struct tegra186_cpufreq_cpu {
2020-12-02 12:14:17 +03:00
unsigned int bpmp_cluster_id ;
2020-12-02 12:14:16 +03:00
unsigned int edvd_offset ;
} ;
static const struct tegra186_cpufreq_cpu tegra186_cpus [ ] = {
/* CPU0 - A57 Cluster */
{
2020-12-02 12:14:17 +03:00
. bpmp_cluster_id = 1 ,
2020-12-02 12:14:16 +03:00
. edvd_offset = EDVD_OFFSET_A57 ( 0 )
} ,
/* CPU1 - Denver Cluster */
{
2020-12-02 12:14:17 +03:00
. bpmp_cluster_id = 0 ,
2020-12-02 12:14:16 +03:00
. edvd_offset = EDVD_OFFSET_DENVER ( 0 )
} ,
/* CPU2 - Denver Cluster */
{
2020-12-02 12:14:17 +03:00
. bpmp_cluster_id = 0 ,
2020-12-02 12:14:16 +03:00
. edvd_offset = EDVD_OFFSET_DENVER ( 1 )
} ,
/* CPU3 - A57 Cluster */
{
2020-12-02 12:14:17 +03:00
. bpmp_cluster_id = 1 ,
2020-12-02 12:14:16 +03:00
. edvd_offset = EDVD_OFFSET_A57 ( 1 )
} ,
/* CPU4 - A57 Cluster */
{
2020-12-02 12:14:17 +03:00
. bpmp_cluster_id = 1 ,
2020-12-02 12:14:16 +03:00
. edvd_offset = EDVD_OFFSET_A57 ( 2 )
} ,
/* CPU5 - A57 Cluster */
{
2017-04-11 11:09:15 +03:00
. bpmp_cluster_id = 1 ,
2020-12-02 12:14:17 +03:00
. edvd_offset = EDVD_OFFSET_A57 ( 3 )
2017-04-11 11:09:15 +03:00
} ,
} ;
struct tegra186_cpufreq_cluster {
struct cpufreq_frequency_table * table ;
2020-11-03 14:55:14 +03:00
u32 ref_clk_khz ;
u32 div ;
2017-04-11 11:09:15 +03:00
} ;
struct tegra186_cpufreq_data {
void __iomem * regs ;
struct tegra186_cpufreq_cluster * clusters ;
2020-12-02 12:14:16 +03:00
const struct tegra186_cpufreq_cpu * cpus ;
2017-04-11 11:09:15 +03:00
} ;
static int tegra186_cpufreq_init ( struct cpufreq_policy * policy )
{
struct tegra186_cpufreq_data * data = cpufreq_get_driver_data ( ) ;
2020-12-02 12:14:17 +03:00
unsigned int cluster = data - > cpus [ policy - > cpu ] . bpmp_cluster_id ;
2017-04-11 11:09:15 +03:00
2020-12-02 12:14:17 +03:00
policy - > freq_table = data - > clusters [ cluster ] . table ;
2017-04-11 11:09:15 +03:00
policy - > cpuinfo . transition_latency = 300 * 1000 ;
2020-12-02 12:14:16 +03:00
policy - > driver_data = NULL ;
2017-04-11 11:09:15 +03:00
return 0 ;
}
static int tegra186_cpufreq_set_target ( struct cpufreq_policy * policy ,
unsigned int index )
{
2020-12-02 12:14:16 +03:00
struct tegra186_cpufreq_data * data = cpufreq_get_driver_data ( ) ;
2017-04-11 11:09:15 +03:00
struct cpufreq_frequency_table * tbl = policy - > freq_table + index ;
2020-12-02 12:14:16 +03:00
unsigned int edvd_offset = data - > cpus [ policy - > cpu ] . edvd_offset ;
2017-04-11 11:09:15 +03:00
u32 edvd_val = tbl - > driver_data ;
2020-12-02 12:14:16 +03:00
writel ( edvd_val , data - > regs + edvd_offset ) ;
2017-04-11 11:09:15 +03:00
return 0 ;
}
2020-08-24 17:59:07 +03:00
static unsigned int tegra186_cpufreq_get ( unsigned int cpu )
{
2020-11-03 14:55:14 +03:00
struct tegra186_cpufreq_data * data = cpufreq_get_driver_data ( ) ;
2020-12-02 12:14:17 +03:00
struct tegra186_cpufreq_cluster * cluster ;
2020-08-24 17:59:07 +03:00
struct cpufreq_policy * policy ;
2020-12-02 12:14:17 +03:00
unsigned int edvd_offset , cluster_id ;
2020-08-24 17:59:07 +03:00
u32 ndiv ;
policy = cpufreq_cpu_get ( cpu ) ;
if ( ! policy )
return 0 ;
2020-12-02 12:14:16 +03:00
edvd_offset = data - > cpus [ policy - > cpu ] . edvd_offset ;
ndiv = readl ( data - > regs + edvd_offset ) & EDVD_CORE_VOLT_FREQ_F_MASK ;
2020-12-02 12:14:17 +03:00
cluster_id = data - > cpus [ policy - > cpu ] . bpmp_cluster_id ;
cluster = & data - > clusters [ cluster_id ] ;
2020-08-24 17:59:07 +03:00
cpufreq_cpu_put ( policy ) ;
2020-12-02 12:14:17 +03:00
return ( cluster - > ref_clk_khz * ndiv ) / cluster - > div ;
2020-08-24 17:59:07 +03:00
}
2017-04-11 11:09:15 +03:00
static struct cpufreq_driver tegra186_cpufreq_driver = {
. name = " tegra186 " ,
2021-02-02 07:55:11 +03:00
. flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
2019-10-23 12:27:03 +03:00
CPUFREQ_NEED_INITIAL_FREQ_CHECK ,
2020-08-24 17:59:07 +03:00
. get = tegra186_cpufreq_get ,
2017-04-11 11:09:15 +03:00
. verify = cpufreq_generic_frequency_table_verify ,
. target_index = tegra186_cpufreq_set_target ,
. init = tegra186_cpufreq_init ,
. attr = cpufreq_generic_attr ,
} ;
static struct cpufreq_frequency_table * init_vhint_table (
struct platform_device * pdev , struct tegra_bpmp * bpmp ,
2020-12-02 12:14:17 +03:00
struct tegra186_cpufreq_cluster * cluster , unsigned int cluster_id )
2017-04-11 11:09:15 +03:00
{
struct cpufreq_frequency_table * table ;
struct mrq_cpu_vhint_request req ;
struct tegra_bpmp_message msg ;
struct cpu_vhint_data * data ;
int err , i , j , num_rates = 0 ;
dma_addr_t phys ;
void * virt ;
virt = dma_alloc_coherent ( bpmp - > dev , sizeof ( * data ) , & phys ,
2018-10-13 18:17:00 +03:00
GFP_KERNEL ) ;
2017-04-11 11:09:15 +03:00
if ( ! virt )
return ERR_PTR ( - ENOMEM ) ;
data = ( struct cpu_vhint_data * ) virt ;
memset ( & req , 0 , sizeof ( req ) ) ;
req . addr = phys ;
2020-12-02 12:14:17 +03:00
req . cluster_id = cluster_id ;
2017-04-11 11:09:15 +03:00
memset ( & msg , 0 , sizeof ( msg ) ) ;
msg . mrq = MRQ_CPU_VHINT ;
msg . tx . data = & req ;
msg . tx . size = sizeof ( req ) ;
err = tegra_bpmp_transfer ( bpmp , & msg ) ;
if ( err ) {
table = ERR_PTR ( err ) ;
goto free ;
}
for ( i = data - > vfloor ; i < = data - > vceil ; i + + ) {
u16 ndiv = data - > ndiv [ i ] ;
if ( ndiv < data - > ndiv_min | | ndiv > data - > ndiv_max )
continue ;
/* Only store lowest voltage index for each rate */
if ( i > 0 & & ndiv = = data - > ndiv [ i - 1 ] )
continue ;
num_rates + + ;
}
table = devm_kcalloc ( & pdev - > dev , num_rates + 1 , sizeof ( * table ) ,
GFP_KERNEL ) ;
if ( ! table ) {
table = ERR_PTR ( - ENOMEM ) ;
goto free ;
}
2020-11-03 14:55:14 +03:00
cluster - > ref_clk_khz = data - > ref_clk_hz / 1000 ;
cluster - > div = data - > pdiv * data - > mdiv ;
2017-04-11 11:09:15 +03:00
for ( i = data - > vfloor , j = 0 ; i < = data - > vceil ; i + + ) {
struct cpufreq_frequency_table * point ;
u16 ndiv = data - > ndiv [ i ] ;
u32 edvd_val = 0 ;
if ( ndiv < data - > ndiv_min | | ndiv > data - > ndiv_max )
continue ;
/* Only store lowest voltage index for each rate */
if ( i > 0 & & ndiv = = data - > ndiv [ i - 1 ] )
continue ;
edvd_val | = i < < EDVD_CORE_VOLT_FREQ_V_SHIFT ;
edvd_val | = ndiv < < EDVD_CORE_VOLT_FREQ_F_SHIFT ;
point = & table [ j + + ] ;
point - > driver_data = edvd_val ;
2020-11-03 14:55:14 +03:00
point - > frequency = ( cluster - > ref_clk_khz * ndiv ) / cluster - > div ;
2017-04-11 11:09:15 +03:00
}
table [ j ] . frequency = CPUFREQ_TABLE_END ;
free :
dma_free_coherent ( bpmp - > dev , sizeof ( * data ) , virt , phys ) ;
return table ;
}
static int tegra186_cpufreq_probe ( struct platform_device * pdev )
{
struct tegra186_cpufreq_data * data ;
struct tegra_bpmp * bpmp ;
unsigned int i = 0 , err ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2020-12-02 12:14:17 +03:00
data - > clusters = devm_kcalloc ( & pdev - > dev , TEGRA186_NUM_CLUSTERS ,
2017-04-11 11:09:15 +03:00
sizeof ( * data - > clusters ) , GFP_KERNEL ) ;
if ( ! data - > clusters )
return - ENOMEM ;
2020-12-02 12:14:16 +03:00
data - > cpus = tegra186_cpus ;
2017-04-11 11:09:15 +03:00
bpmp = tegra_bpmp_get ( & pdev - > dev ) ;
if ( IS_ERR ( bpmp ) )
return PTR_ERR ( bpmp ) ;
2019-12-15 17:05:22 +03:00
data - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-04-11 11:09:15 +03:00
if ( IS_ERR ( data - > regs ) ) {
err = PTR_ERR ( data - > regs ) ;
goto put_bpmp ;
}
2020-12-02 12:14:17 +03:00
for ( i = 0 ; i < TEGRA186_NUM_CLUSTERS ; i + + ) {
2017-04-11 11:09:15 +03:00
struct tegra186_cpufreq_cluster * cluster = & data - > clusters [ i ] ;
2020-12-02 12:14:17 +03:00
cluster - > table = init_vhint_table ( pdev , bpmp , cluster , i ) ;
2017-04-11 11:09:15 +03:00
if ( IS_ERR ( cluster - > table ) ) {
err = PTR_ERR ( cluster - > table ) ;
goto put_bpmp ;
}
}
tegra186_cpufreq_driver . driver_data = data ;
err = cpufreq_register_driver ( & tegra186_cpufreq_driver ) ;
put_bpmp :
tegra_bpmp_put ( bpmp ) ;
return err ;
}
static int tegra186_cpufreq_remove ( struct platform_device * pdev )
{
cpufreq_unregister_driver ( & tegra186_cpufreq_driver ) ;
return 0 ;
}
static const struct of_device_id tegra186_cpufreq_of_match [ ] = {
{ . compatible = " nvidia,tegra186-ccplex-cluster " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , tegra186_cpufreq_of_match ) ;
static struct platform_driver tegra186_cpufreq_platform_driver = {
. driver = {
. name = " tegra186-cpufreq " ,
. of_match_table = tegra186_cpufreq_of_match ,
} ,
. probe = tegra186_cpufreq_probe ,
. remove = tegra186_cpufreq_remove ,
} ;
module_platform_driver ( tegra186_cpufreq_platform_driver ) ;
MODULE_AUTHOR ( " Mikko Perttunen <mperttunen@nvidia.com> " ) ;
MODULE_DESCRIPTION ( " NVIDIA Tegra186 cpufreq driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;