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 12:41:31 +02: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 12:41:31 +02:00
# include <linux/of_device.h>
2018-05-30 05:39:28 +03:00
# include <linux/platform_device.h>
2019-07-25 12:41:35 +02: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 12:41:33 +02:00
struct qcom_cpufreq_drv ;
struct qcom_cpufreq_match_data {
int ( * get_version ) ( struct device * cpu_dev ,
struct nvmem_cell * speedbin_nvmem ,
struct qcom_cpufreq_drv * drv ) ;
2019-07-25 12:41:35 +02:00
const char * * genpd_names ;
2019-07-25 12:41:33 +02:00
} ;
struct qcom_cpufreq_drv {
struct opp_table * * opp_tables ;
2019-07-25 12:41:35 +02:00
struct opp_table * * genpd_opp_tables ;
2019-07-25 12:41:33 +02:00
u32 versions ;
const struct qcom_cpufreq_match_data * data ;
} ;
2019-07-25 12:41:31 +02:00
static struct platform_device * cpufreq_dt_pdev , * cpufreq_pdev ;
2018-06-17 22:01:46 +02:00
2019-07-25 12:41:31 +02: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 12:41:31 +02:00
static int qcom_cpufreq_kryo_name_version ( struct device * cpu_dev ,
struct nvmem_cell * speedbin_nvmem ,
2019-07-25 12:41:33 +02:00
struct qcom_cpufreq_drv * drv )
2018-05-30 05:39:28 +03:00
{
2019-07-25 12:41:31 +02:00
size_t len ;
u8 * speedbin ;
2018-05-30 05:39:28 +03:00
enum _msm8996_version msm8996_version ;
2019-07-25 12:41:31 +02: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 12:41:33 +02:00
drv - > versions = 1 < < ( unsigned int ) ( * speedbin ) ;
2019-07-25 12:41:31 +02:00
break ;
case MSM8996_SG :
2019-07-25 12:41:33 +02:00
drv - > versions = 1 < < ( ( unsigned int ) ( * speedbin ) + 4 ) ;
2019-07-25 12:41:31 +02:00
break ;
default :
BUG ( ) ;
break ;
}
kfree ( speedbin ) ;
return 0 ;
}
2019-07-25 12:41:33 +02:00
static const struct qcom_cpufreq_match_data match_data_kryo = {
. get_version = qcom_cpufreq_kryo_name_version ,
} ;
2019-07-25 12:41:35 +02: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 12:41:31 +02:00
static int qcom_cpufreq_probe ( struct platform_device * pdev )
{
2019-07-25 12:41:33 +02: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 ;
unsigned cpu ;
2019-07-25 12:41:31 +02: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
ret = of_device_is_compatible ( np , " operating-points-v2-kryo-cpu " ) ;
if ( ! ret ) {
of_node_put ( np ) ;
return - ENOENT ;
}
2019-07-25 12:41:33 +02: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 12:41:33 +02: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
2019-07-25 12:41:33 +02:00
ret = drv - > data - > get_version ( cpu_dev , speedbin_nvmem , drv ) ;
if ( ret ) {
nvmem_cell_put ( speedbin_nvmem ) ;
goto free_drv ;
}
nvmem_cell_put ( speedbin_nvmem ) ;
}
of_node_put ( np ) ;
drv - > opp_tables = kcalloc ( num_possible_cpus ( ) , sizeof ( * drv - > opp_tables ) ,
GFP_KERNEL ) ;
if ( ! drv - > opp_tables ) {
ret = - ENOMEM ;
goto free_drv ;
}
2019-02-20 16:41:18 +05:30
2019-07-25 12:41:35 +02: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 12:41:35 +02:00
goto free_genpd_opp ;
2018-05-30 05:39:28 +03:00
}
2019-07-25 12:41:33 +02:00
if ( drv - > data - > get_version ) {
drv - > opp_tables [ cpu ] =
dev_pm_opp_set_supported_hw ( cpu_dev ,
& drv - > versions , 1 ) ;
if ( IS_ERR ( drv - > opp_tables [ cpu ] ) ) {
ret = PTR_ERR ( drv - > opp_tables [ cpu ] ) ;
dev_err ( cpu_dev ,
" Failed to set supported hardware \n " ) ;
2019-07-25 12:41:35 +02: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 12:41:33 +02:00
}
2018-05-30 05:39:28 +03:00
}
}
cpufreq_dt_pdev = platform_device_register_simple ( " cpufreq-dt " , - 1 ,
NULL , 0 ) ;
2019-02-20 16:41:18 +05:30
if ( ! IS_ERR ( cpufreq_dt_pdev ) ) {
2019-07-25 12:41:33 +02:00
platform_set_drvdata ( pdev , drv ) ;
2018-05-30 05:39:28 +03:00
return 0 ;
2019-02-20 16:41:18 +05:30
}
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 12:41:35 +02:00
free_genpd_opp :
for_each_possible_cpu ( cpu ) {
if ( IS_ERR_OR_NULL ( drv - > genpd_opp_tables [ cpu ] ) )
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 ) {
2019-07-25 12:41:33 +02:00
if ( IS_ERR_OR_NULL ( drv - > opp_tables [ cpu ] ) )
2018-05-30 05:39:28 +03:00
break ;
2019-07-25 12:41:33 +02:00
dev_pm_opp_put_supported_hw ( drv - > opp_tables [ cpu ] ) ;
2018-05-30 05:39:28 +03:00
}
2019-07-25 12:41:33 +02:00
kfree ( drv - > opp_tables ) ;
free_drv :
kfree ( drv ) ;
2018-05-30 05:39:28 +03:00
return ret ;
}
2019-07-25 12:41:31 +02:00
static int qcom_cpufreq_remove ( struct platform_device * pdev )
2018-06-17 22:01:46 +02:00
{
2019-07-25 12:41:33 +02:00
struct qcom_cpufreq_drv * drv = platform_get_drvdata ( pdev ) ;
2019-02-20 16:41:18 +05:30
unsigned int cpu ;
2018-06-17 22:01:46 +02:00
platform_device_unregister ( cpufreq_dt_pdev ) ;
2019-02-20 16:41:18 +05:30
2019-07-25 12:41:35 +02:00
for_each_possible_cpu ( cpu ) {
2019-07-25 12:41:33 +02:00
if ( drv - > opp_tables [ cpu ] )
dev_pm_opp_put_supported_hw ( drv - > opp_tables [ cpu ] ) ;
2019-07-25 12:41:35 +02:00
if ( drv - > genpd_opp_tables [ cpu ] )
dev_pm_opp_detach_genpd ( drv - > genpd_opp_tables [ cpu ] ) ;
}
2019-02-20 16:41:18 +05:30
2019-07-25 12:41:33 +02:00
kfree ( drv - > opp_tables ) ;
2019-07-25 12:41:35 +02:00
kfree ( drv - > genpd_opp_tables ) ;
2019-07-25 12:41:33 +02:00
kfree ( drv ) ;
2019-02-20 16:41:18 +05:30
2018-06-17 22:01:46 +02:00
return 0 ;
}
2019-07-25 12:41:31 +02: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 12:41:31 +02:00
. name = " qcom-cpufreq-nvmem " ,
2018-05-30 05:39:28 +03:00
} ,
} ;
2019-07-25 12:41:31 +02:00
static const struct of_device_id qcom_cpufreq_match_list [ ] __initconst = {
2019-07-25 12:41:33 +02:00
{ . compatible = " qcom,apq8096 " , . data = & match_data_kryo } ,
{ . compatible = " qcom,msm8996 " , . data = & match_data_kryo } ,
2019-07-25 12:41:35 +02:00
{ . compatible = " qcom,qcs404 " , . data = & match_data_qcs404 } ,
2019-07-25 12:41:31 +02:00
{ } ,
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 12:41:31 +02: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 12:41:31 +02: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 12:41:31 +02: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 12:41:31 +02: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 12:41:31 +02:00
platform_driver_unregister ( & qcom_cpufreq_driver ) ;
2018-05-30 05:39:28 +03:00
return ret ;
}
2019-07-25 12:41:31 +02:00
module_init ( qcom_cpufreq_init ) ;
2018-05-30 05:39:28 +03:00
2019-07-25 12:41:31 +02:00
static void __exit qcom_cpufreq_exit ( void )
2018-06-17 22:01:46 +02:00
{
2019-07-25 12:41:31 +02:00
platform_device_unregister ( cpufreq_pdev ) ;
platform_driver_unregister ( & qcom_cpufreq_driver ) ;
2018-06-17 22:01:46 +02:00
}
2019-07-25 12:41:31 +02:00
module_exit ( qcom_cpufreq_exit ) ;
2018-06-17 22:01:46 +02:00
2019-07-25 12:41:31 +02:00
MODULE_DESCRIPTION ( " Qualcomm Technologies, Inc. CPUfreq driver " ) ;
2018-05-30 05:39:28 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;