2018-05-30 05:39:28 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( c ) 2018 , The Linux Foundation . All rights reserved .
*/
/*
* In Certain QCOM SoCs like apq8096 and msm8996 that have KRYO processors ,
* the CPU frequency subset and voltage value of each OPP varies
* based on the silicon variant in use . Qualcomm Process Voltage Scaling Tables
* defines the voltage and frequency value based on the msm - id in SMEM
* and speedbin blown in the efuse combination .
2019-07-25 13:41:31 +03:00
* The qcom - cpufreq - nvmem driver reads the msm - id and efuse value from the SoC
2018-05-30 05:39:28 +03:00
* to provide the OPP framework with required information .
* This is used to determine the voltage and frequency value for each OPP of
* operating - points - v2 table when it is parsed by the OPP framework .
*/
# include <linux/cpu.h>
# include <linux/err.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/nvmem-consumer.h>
# include <linux/of.h>
2019-07-25 13:41:31 +03:00
# include <linux/of_device.h>
2018-05-30 05:39:28 +03:00
# include <linux/platform_device.h>
2019-07-25 13:41:35 +03:00
# include <linux/pm_domain.h>
2018-05-30 05:39:28 +03:00
# include <linux/pm_opp.h>
# include <linux/slab.h>
# include <linux/soc/qcom/smem.h>
# define MSM_ID_SMEM 137
enum _msm_id {
MSM8996V3 = 0xF6ul ,
APQ8096V3 = 0x123ul ,
MSM8996SG = 0x131ul ,
APQ8096SG = 0x138ul ,
} ;
enum _msm8996_version {
MSM8996_V3 ,
MSM8996_SG ,
NUM_OF_MSM8996_VERSIONS ,
} ;
2019-07-25 13:41:33 +03:00
struct qcom_cpufreq_drv ;
struct qcom_cpufreq_match_data {
int ( * get_version ) ( struct device * cpu_dev ,
struct nvmem_cell * speedbin_nvmem ,
2020-03-13 20:52:13 +03:00
char * * pvs_name ,
2019-07-25 13:41:33 +03:00
struct qcom_cpufreq_drv * drv ) ;
2019-07-25 13:41:35 +03:00
const char * * genpd_names ;
2019-07-25 13:41:33 +03:00
} ;
struct qcom_cpufreq_drv {
2020-03-13 20:52:13 +03:00
struct opp_table * * names_opp_tables ;
struct opp_table * * hw_opp_tables ;
2019-07-25 13:41:35 +03:00
struct opp_table * * genpd_opp_tables ;
2019-07-25 13:41:33 +03:00
u32 versions ;
const struct qcom_cpufreq_match_data * data ;
} ;
2019-07-25 13:41:31 +03:00
static struct platform_device * cpufreq_dt_pdev , * cpufreq_pdev ;
2018-06-17 23:01:46 +03:00
2020-03-13 20:52:13 +03:00
static void get_krait_bin_format_a ( struct device * cpu_dev ,
int * speed , int * pvs , int * pvs_ver ,
struct nvmem_cell * pvs_nvmem , u8 * buf )
{
u32 pte_efuse ;
pte_efuse = * ( ( u32 * ) buf ) ;
* speed = pte_efuse & 0xf ;
if ( * speed = = 0xf )
* speed = ( pte_efuse > > 4 ) & 0xf ;
if ( * speed = = 0xf ) {
* speed = 0 ;
dev_warn ( cpu_dev , " Speed bin: Defaulting to %d \n " , * speed ) ;
} else {
dev_dbg ( cpu_dev , " Speed bin: %d \n " , * speed ) ;
}
* pvs = ( pte_efuse > > 10 ) & 0x7 ;
if ( * pvs = = 0x7 )
* pvs = ( pte_efuse > > 13 ) & 0x7 ;
if ( * pvs = = 0x7 ) {
* pvs = 0 ;
dev_warn ( cpu_dev , " PVS bin: Defaulting to %d \n " , * pvs ) ;
} else {
dev_dbg ( cpu_dev , " PVS bin: %d \n " , * pvs ) ;
}
}
static void get_krait_bin_format_b ( struct device * cpu_dev ,
int * speed , int * pvs , int * pvs_ver ,
struct nvmem_cell * pvs_nvmem , u8 * buf )
{
u32 pte_efuse , redundant_sel ;
pte_efuse = * ( ( u32 * ) buf ) ;
redundant_sel = ( pte_efuse > > 24 ) & 0x7 ;
* pvs_ver = ( pte_efuse > > 4 ) & 0x3 ;
switch ( redundant_sel ) {
case 1 :
* pvs = ( ( pte_efuse > > 28 ) & 0x8 ) | ( ( pte_efuse > > 6 ) & 0x7 ) ;
* speed = ( pte_efuse > > 27 ) & 0xf ;
break ;
case 2 :
* pvs = ( pte_efuse > > 27 ) & 0xf ;
* speed = pte_efuse & 0x7 ;
break ;
default :
/* 4 bits of PVS are in efuse register bits 31, 8-6. */
* pvs = ( ( pte_efuse > > 28 ) & 0x8 ) | ( ( pte_efuse > > 6 ) & 0x7 ) ;
* speed = pte_efuse & 0x7 ;
}
/* Check SPEED_BIN_BLOW_STATUS */
if ( pte_efuse & BIT ( 3 ) ) {
dev_dbg ( cpu_dev , " Speed bin: %d \n " , * speed ) ;
} else {
dev_warn ( cpu_dev , " Speed bin not set. Defaulting to 0! \n " ) ;
* speed = 0 ;
}
/* Check PVS_BLOW_STATUS */
pte_efuse = * ( ( ( u32 * ) buf ) + 4 ) ;
pte_efuse & = BIT ( 21 ) ;
if ( pte_efuse ) {
dev_dbg ( cpu_dev , " PVS bin: %d \n " , * pvs ) ;
} else {
dev_warn ( cpu_dev , " PVS bin not set. Defaulting to 0! \n " ) ;
* pvs = 0 ;
}
dev_dbg ( cpu_dev , " PVS version: %d \n " , * pvs_ver ) ;
}
2019-07-25 13:41:31 +03:00
static enum _msm8996_version qcom_cpufreq_get_msm_id ( void )
2018-05-30 05:39:28 +03:00
{
size_t len ;
u32 * msm_id ;
enum _msm8996_version version ;
msm_id = qcom_smem_get ( QCOM_SMEM_HOST_ANY , MSM_ID_SMEM , & len ) ;
if ( IS_ERR ( msm_id ) )
return NUM_OF_MSM8996_VERSIONS ;
/* The first 4 bytes are format, next to them is the actual msm-id */
msm_id + + ;
switch ( ( enum _msm_id ) * msm_id ) {
case MSM8996V3 :
case APQ8096V3 :
version = MSM8996_V3 ;
break ;
case MSM8996SG :
case APQ8096SG :
version = MSM8996_SG ;
break ;
default :
version = NUM_OF_MSM8996_VERSIONS ;
}
return version ;
}
2019-07-25 13:41:31 +03:00
static int qcom_cpufreq_kryo_name_version ( struct device * cpu_dev ,
struct nvmem_cell * speedbin_nvmem ,
2020-03-13 20:52:13 +03:00
char * * pvs_name ,
2019-07-25 13:41:33 +03:00
struct qcom_cpufreq_drv * drv )
2018-05-30 05:39:28 +03:00
{
2019-07-25 13:41:31 +03:00
size_t len ;
u8 * speedbin ;
2018-05-30 05:39:28 +03:00
enum _msm8996_version msm8996_version ;
2020-03-13 20:52:13 +03:00
* pvs_name = NULL ;
2019-07-25 13:41:31 +03:00
msm8996_version = qcom_cpufreq_get_msm_id ( ) ;
if ( NUM_OF_MSM8996_VERSIONS = = msm8996_version ) {
dev_err ( cpu_dev , " Not Snapdragon 820/821! " ) ;
return - ENODEV ;
}
speedbin = nvmem_cell_read ( speedbin_nvmem , & len ) ;
if ( IS_ERR ( speedbin ) )
return PTR_ERR ( speedbin ) ;
switch ( msm8996_version ) {
case MSM8996_V3 :
2019-07-25 13:41:33 +03:00
drv - > versions = 1 < < ( unsigned int ) ( * speedbin ) ;
2019-07-25 13:41:31 +03:00
break ;
case MSM8996_SG :
2019-07-25 13:41:33 +03:00
drv - > versions = 1 < < ( ( unsigned int ) ( * speedbin ) + 4 ) ;
2019-07-25 13:41:31 +03:00
break ;
default :
BUG ( ) ;
break ;
}
kfree ( speedbin ) ;
return 0 ;
}
2020-03-13 20:52:13 +03:00
static int qcom_cpufreq_krait_name_version ( struct device * cpu_dev ,
struct nvmem_cell * speedbin_nvmem ,
char * * pvs_name ,
struct qcom_cpufreq_drv * drv )
{
int speed = 0 , pvs = 0 , pvs_ver = 0 ;
u8 * speedbin ;
size_t len ;
speedbin = nvmem_cell_read ( speedbin_nvmem , & len ) ;
if ( IS_ERR ( speedbin ) )
return PTR_ERR ( speedbin ) ;
switch ( len ) {
case 4 :
get_krait_bin_format_a ( cpu_dev , & speed , & pvs , & pvs_ver ,
speedbin_nvmem , speedbin ) ;
break ;
case 8 :
get_krait_bin_format_b ( cpu_dev , & speed , & pvs , & pvs_ver ,
speedbin_nvmem , speedbin ) ;
break ;
default :
dev_err ( cpu_dev , " Unable to read nvmem data. Defaulting to 0! \n " ) ;
return - ENODEV ;
}
snprintf ( * pvs_name , sizeof ( " speedXX-pvsXX-vXX " ) , " speed%d-pvs%d-v%d " ,
speed , pvs , pvs_ver ) ;
drv - > versions = ( 1 < < speed ) ;
kfree ( speedbin ) ;
return 0 ;
}
2019-07-25 13:41:33 +03:00
static const struct qcom_cpufreq_match_data match_data_kryo = {
. get_version = qcom_cpufreq_kryo_name_version ,
} ;
2020-03-13 20:52:13 +03:00
static const struct qcom_cpufreq_match_data match_data_krait = {
. get_version = qcom_cpufreq_krait_name_version ,
} ;
2019-07-25 13:41:35 +03:00
static const char * qcs404_genpd_names [ ] = { " cpr " , NULL } ;
static const struct qcom_cpufreq_match_data match_data_qcs404 = {
. genpd_names = qcs404_genpd_names ,
} ;
2019-07-25 13:41:31 +03:00
static int qcom_cpufreq_probe ( struct platform_device * pdev )
{
2019-07-25 13:41:33 +03:00
struct qcom_cpufreq_drv * drv ;
2018-05-30 05:39:28 +03:00
struct nvmem_cell * speedbin_nvmem ;
struct device_node * np ;
struct device * cpu_dev ;
2020-03-13 20:52:13 +03:00
char * pvs_name = " speedXX-pvsXX-vXX " ;
2018-05-30 05:39:28 +03:00
unsigned cpu ;
2019-07-25 13:41:31 +03:00
const struct of_device_id * match ;
2018-05-30 05:39:28 +03:00
int ret ;
cpu_dev = get_cpu_device ( 0 ) ;
2018-06-21 11:06:41 +03:00
if ( ! cpu_dev )
return - ENODEV ;
2018-05-30 05:39:28 +03:00
np = dev_pm_opp_of_get_opp_desc_node ( cpu_dev ) ;
2018-06-21 11:06:41 +03:00
if ( ! np )
return - ENOENT ;
2018-05-30 05:39:28 +03:00
2020-05-01 01:22:25 +03:00
ret = of_device_is_compatible ( np , " operating-points-v2-kryo-cpu " ) ;
2018-05-30 05:39:28 +03:00
if ( ! ret ) {
of_node_put ( np ) ;
return - ENOENT ;
}
2019-07-25 13:41:33 +03:00
drv = kzalloc ( sizeof ( * drv ) , GFP_KERNEL ) ;
if ( ! drv )
return - ENOMEM ;
match = pdev - > dev . platform_data ;
drv - > data = match - > data ;
if ( ! drv - > data ) {
ret = - ENODEV ;
goto free_drv ;
2018-05-30 05:39:28 +03:00
}
2019-07-25 13:41:33 +03:00
if ( drv - > data - > get_version ) {
speedbin_nvmem = of_nvmem_cell_get ( np , NULL ) ;
if ( IS_ERR ( speedbin_nvmem ) ) {
if ( PTR_ERR ( speedbin_nvmem ) ! = - EPROBE_DEFER )
dev_err ( cpu_dev ,
" Could not get nvmem cell: %ld \n " ,
PTR_ERR ( speedbin_nvmem ) ) ;
ret = PTR_ERR ( speedbin_nvmem ) ;
goto free_drv ;
}
2018-05-30 05:39:28 +03:00
2020-03-13 20:52:13 +03:00
ret = drv - > data - > get_version ( cpu_dev ,
speedbin_nvmem , & pvs_name , drv ) ;
2019-07-25 13:41:33 +03:00
if ( ret ) {
nvmem_cell_put ( speedbin_nvmem ) ;
goto free_drv ;
}
nvmem_cell_put ( speedbin_nvmem ) ;
}
of_node_put ( np ) ;
2020-03-13 20:52:13 +03:00
drv - > names_opp_tables = kcalloc ( num_possible_cpus ( ) ,
sizeof ( * drv - > names_opp_tables ) ,
2019-07-25 13:41:33 +03:00
GFP_KERNEL ) ;
2020-03-13 20:52:13 +03:00
if ( ! drv - > names_opp_tables ) {
2019-07-25 13:41:33 +03:00
ret = - ENOMEM ;
goto free_drv ;
}
2020-03-13 20:52:13 +03:00
drv - > hw_opp_tables = kcalloc ( num_possible_cpus ( ) ,
sizeof ( * drv - > hw_opp_tables ) ,
GFP_KERNEL ) ;
if ( ! drv - > hw_opp_tables ) {
ret = - ENOMEM ;
goto free_opp_names ;
}
2019-02-20 14:11:18 +03:00
2019-07-25 13:41:35 +03:00
drv - > genpd_opp_tables = kcalloc ( num_possible_cpus ( ) ,
sizeof ( * drv - > genpd_opp_tables ) ,
GFP_KERNEL ) ;
if ( ! drv - > genpd_opp_tables ) {
ret = - ENOMEM ;
goto free_opp ;
}
2018-05-30 05:39:28 +03:00
for_each_possible_cpu ( cpu ) {
cpu_dev = get_cpu_device ( cpu ) ;
if ( NULL = = cpu_dev ) {
ret = - ENODEV ;
2019-07-25 13:41:35 +03:00
goto free_genpd_opp ;
2018-05-30 05:39:28 +03:00
}
2019-07-25 13:41:33 +03:00
if ( drv - > data - > get_version ) {
2020-03-13 20:52:13 +03:00
if ( pvs_name ) {
drv - > names_opp_tables [ cpu ] = dev_pm_opp_set_prop_name (
cpu_dev ,
pvs_name ) ;
if ( IS_ERR ( drv - > names_opp_tables [ cpu ] ) ) {
ret = PTR_ERR ( drv - > names_opp_tables [ cpu ] ) ;
dev_err ( cpu_dev , " Failed to add OPP name %s \n " ,
pvs_name ) ;
goto free_opp ;
}
}
drv - > hw_opp_tables [ cpu ] = dev_pm_opp_set_supported_hw (
cpu_dev , & drv - > versions , 1 ) ;
if ( IS_ERR ( drv - > hw_opp_tables [ cpu ] ) ) {
ret = PTR_ERR ( drv - > hw_opp_tables [ cpu ] ) ;
2019-07-25 13:41:33 +03:00
dev_err ( cpu_dev ,
" Failed to set supported hardware \n " ) ;
2019-07-25 13:41:35 +03:00
goto free_genpd_opp ;
}
}
if ( drv - > data - > genpd_names ) {
drv - > genpd_opp_tables [ cpu ] =
dev_pm_opp_attach_genpd ( cpu_dev ,
drv - > data - > genpd_names ,
NULL ) ;
if ( IS_ERR ( drv - > genpd_opp_tables [ cpu ] ) ) {
ret = PTR_ERR ( drv - > genpd_opp_tables [ cpu ] ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( cpu_dev ,
" Could not attach to pm_domain: %d \n " ,
ret ) ;
goto free_genpd_opp ;
2019-07-25 13:41:33 +03:00
}
2018-05-30 05:39:28 +03:00
}
}
cpufreq_dt_pdev = platform_device_register_simple ( " cpufreq-dt " , - 1 ,
NULL , 0 ) ;
2019-02-20 14:11:18 +03:00
if ( ! IS_ERR ( cpufreq_dt_pdev ) ) {
2019-07-25 13:41:33 +03:00
platform_set_drvdata ( pdev , drv ) ;
2018-05-30 05:39:28 +03:00
return 0 ;
2019-02-20 14:11:18 +03:00
}
2018-05-30 05:39:28 +03:00
ret = PTR_ERR ( cpufreq_dt_pdev ) ;
dev_err ( cpu_dev , " Failed to register platform device \n " ) ;
2019-07-25 13:41:35 +03:00
free_genpd_opp :
for_each_possible_cpu ( cpu ) {
2020-11-06 09:48:39 +03:00
if ( IS_ERR ( drv - > genpd_opp_tables [ cpu ] ) )
2019-07-25 13:41:35 +03:00
break ;
dev_pm_opp_detach_genpd ( drv - > genpd_opp_tables [ cpu ] ) ;
}
kfree ( drv - > genpd_opp_tables ) ;
2018-05-30 05:39:28 +03:00
free_opp :
for_each_possible_cpu ( cpu ) {
2020-11-06 09:48:39 +03:00
if ( IS_ERR ( drv - > names_opp_tables [ cpu ] ) )
2020-03-13 20:52:13 +03:00
break ;
dev_pm_opp_put_prop_name ( drv - > names_opp_tables [ cpu ] ) ;
}
for_each_possible_cpu ( cpu ) {
2020-11-06 09:48:39 +03:00
if ( IS_ERR ( drv - > hw_opp_tables [ cpu ] ) )
2018-05-30 05:39:28 +03:00
break ;
2020-03-13 20:52:13 +03:00
dev_pm_opp_put_supported_hw ( drv - > hw_opp_tables [ cpu ] ) ;
2018-05-30 05:39:28 +03:00
}
2020-03-13 20:52:13 +03:00
kfree ( drv - > hw_opp_tables ) ;
free_opp_names :
kfree ( drv - > names_opp_tables ) ;
2019-07-25 13:41:33 +03:00
free_drv :
kfree ( drv ) ;
2018-05-30 05:39:28 +03:00
return ret ;
}
2019-07-25 13:41:31 +03:00
static int qcom_cpufreq_remove ( struct platform_device * pdev )
2018-06-17 23:01:46 +03:00
{
2019-07-25 13:41:33 +03:00
struct qcom_cpufreq_drv * drv = platform_get_drvdata ( pdev ) ;
2019-02-20 14:11:18 +03:00
unsigned int cpu ;
2018-06-17 23:01:46 +03:00
platform_device_unregister ( cpufreq_dt_pdev ) ;
2019-02-20 14:11:18 +03:00
2019-07-25 13:41:35 +03:00
for_each_possible_cpu ( cpu ) {
2020-11-06 09:48:39 +03:00
dev_pm_opp_put_supported_hw ( drv - > names_opp_tables [ cpu ] ) ;
dev_pm_opp_put_supported_hw ( drv - > hw_opp_tables [ cpu ] ) ;
dev_pm_opp_detach_genpd ( drv - > genpd_opp_tables [ cpu ] ) ;
2019-07-25 13:41:35 +03:00
}
2019-02-20 14:11:18 +03:00
2020-03-13 20:52:13 +03:00
kfree ( drv - > names_opp_tables ) ;
kfree ( drv - > hw_opp_tables ) ;
2019-07-25 13:41:35 +03:00
kfree ( drv - > genpd_opp_tables ) ;
2019-07-25 13:41:33 +03:00
kfree ( drv ) ;
2019-02-20 14:11:18 +03:00
2018-06-17 23:01:46 +03:00
return 0 ;
}
2019-07-25 13:41:31 +03:00
static struct platform_driver qcom_cpufreq_driver = {
. probe = qcom_cpufreq_probe ,
. remove = qcom_cpufreq_remove ,
2018-05-30 05:39:28 +03:00
. driver = {
2019-07-25 13:41:31 +03:00
. name = " qcom-cpufreq-nvmem " ,
2018-05-30 05:39:28 +03:00
} ,
} ;
2019-07-25 13:41:31 +03:00
static const struct of_device_id qcom_cpufreq_match_list [ ] __initconst = {
2019-07-25 13:41:33 +03:00
{ . compatible = " qcom,apq8096 " , . data = & match_data_kryo } ,
{ . compatible = " qcom,msm8996 " , . data = & match_data_kryo } ,
2019-07-25 13:41:35 +03:00
{ . compatible = " qcom,qcs404 " , . data = & match_data_qcs404 } ,
2020-03-13 20:52:13 +03:00
{ . compatible = " qcom,ipq8064 " , . data = & match_data_krait } ,
{ . compatible = " qcom,apq8064 " , . data = & match_data_krait } ,
{ . compatible = " qcom,msm8974 " , . data = & match_data_krait } ,
{ . compatible = " qcom,msm8960 " , . data = & match_data_krait } ,
2019-07-25 13:41:31 +03:00
{ } ,
2018-05-30 05:39:28 +03:00
} ;
2020-11-03 18:11:34 +03:00
MODULE_DEVICE_TABLE ( of , qcom_cpufreq_match_list ) ;
2018-05-30 05:39:28 +03:00
/*
* Since the driver depends on smem and nvmem drivers , which may
* return EPROBE_DEFER , all the real activity is done in the probe ,
* which may be defered as well . The init here is only registering
* the driver and the platform device .
*/
2019-07-25 13:41:31 +03:00
static int __init qcom_cpufreq_init ( void )
2018-05-30 05:39:28 +03:00
{
struct device_node * np = of_find_node_by_path ( " / " ) ;
const struct of_device_id * match ;
int ret ;
if ( ! np )
return - ENODEV ;
2019-07-25 13:41:31 +03:00
match = of_match_node ( qcom_cpufreq_match_list , np ) ;
2018-05-30 05:39:28 +03:00
of_node_put ( np ) ;
if ( ! match )
return - ENODEV ;
2019-07-25 13:41:31 +03:00
ret = platform_driver_register ( & qcom_cpufreq_driver ) ;
2018-05-30 05:39:28 +03:00
if ( unlikely ( ret < 0 ) )
return ret ;
2019-07-25 13:41:31 +03:00
cpufreq_pdev = platform_device_register_data ( NULL , " qcom-cpufreq-nvmem " ,
- 1 , match , sizeof ( * match ) ) ;
ret = PTR_ERR_OR_ZERO ( cpufreq_pdev ) ;
2018-05-30 05:39:28 +03:00
if ( 0 = = ret )
return 0 ;
2019-07-25 13:41:31 +03:00
platform_driver_unregister ( & qcom_cpufreq_driver ) ;
2018-05-30 05:39:28 +03:00
return ret ;
}
2019-07-25 13:41:31 +03:00
module_init ( qcom_cpufreq_init ) ;
2018-05-30 05:39:28 +03:00
2019-07-25 13:41:31 +03:00
static void __exit qcom_cpufreq_exit ( void )
2018-06-17 23:01:46 +03:00
{
2019-07-25 13:41:31 +03:00
platform_device_unregister ( cpufreq_pdev ) ;
platform_driver_unregister ( & qcom_cpufreq_driver ) ;
2018-06-17 23:01:46 +03:00
}
2019-07-25 13:41:31 +03:00
module_exit ( qcom_cpufreq_exit ) ;
2018-06-17 23:01:46 +03:00
2019-07-25 13:41:31 +03:00
MODULE_DESCRIPTION ( " Qualcomm Technologies, Inc. CPUfreq driver " ) ;
2018-05-30 05:39:28 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;