2020-11-12 15:09:28 +01:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Exynos generic interconnect provider driver
*
* Copyright ( c ) 2020 Samsung Electronics Co . , Ltd .
*
* Authors : Artur Ś wigoń < a . swigon @ samsung . com >
* Sylwester Nawrocki < s . nawrocki @ samsung . com >
*/
# include <linux/device.h>
# include <linux/interconnect-provider.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pm_qos.h>
# include <linux/slab.h>
# define EXYNOS_ICC_DEFAULT_BUS_CLK_RATIO 8
struct exynos_icc_priv {
struct device * dev ;
/* One interconnect node per provider */
struct icc_provider provider ;
struct icc_node * node ;
struct dev_pm_qos_request qos_req ;
u32 bus_clk_ratio ;
} ;
static struct icc_node * exynos_icc_get_parent ( struct device_node * np )
{
struct of_phandle_args args ;
struct icc_node_data * icc_node_data ;
struct icc_node * icc_node ;
int num , ret ;
num = of_count_phandle_with_args ( np , " interconnects " ,
" #interconnect-cells " ) ;
if ( num < 1 )
return NULL ; /* parent nodes are optional */
/* Get the interconnect target node */
ret = of_parse_phandle_with_args ( np , " interconnects " ,
" #interconnect-cells " , 0 , & args ) ;
if ( ret < 0 )
return ERR_PTR ( ret ) ;
icc_node_data = of_icc_get_from_provider ( & args ) ;
of_node_put ( args . np ) ;
if ( IS_ERR ( icc_node_data ) )
return ERR_CAST ( icc_node_data ) ;
icc_node = icc_node_data - > node ;
kfree ( icc_node_data ) ;
return icc_node ;
}
static int exynos_generic_icc_set ( struct icc_node * src , struct icc_node * dst )
{
struct exynos_icc_priv * src_priv = src - > data , * dst_priv = dst - > data ;
s32 src_freq = max ( src - > avg_bw , src - > peak_bw ) / src_priv - > bus_clk_ratio ;
s32 dst_freq = max ( dst - > avg_bw , dst - > peak_bw ) / dst_priv - > bus_clk_ratio ;
int ret ;
ret = dev_pm_qos_update_request ( & src_priv - > qos_req , src_freq ) ;
if ( ret < 0 ) {
dev_err ( src_priv - > dev , " failed to update PM QoS of %s (src) \n " ,
src - > name ) ;
return ret ;
}
ret = dev_pm_qos_update_request ( & dst_priv - > qos_req , dst_freq ) ;
if ( ret < 0 ) {
dev_err ( dst_priv - > dev , " failed to update PM QoS of %s (dst) \n " ,
dst - > name ) ;
return ret ;
}
return 0 ;
}
static struct icc_node * exynos_generic_icc_xlate ( struct of_phandle_args * spec ,
void * data )
{
struct exynos_icc_priv * priv = data ;
if ( spec - > np ! = priv - > dev - > parent - > of_node )
return ERR_PTR ( - EINVAL ) ;
return priv - > node ;
}
static int exynos_generic_icc_remove ( struct platform_device * pdev )
{
struct exynos_icc_priv * priv = platform_get_drvdata ( pdev ) ;
2023-03-06 08:56:43 +01:00
icc_provider_deregister ( & priv - > provider ) ;
2020-11-12 15:09:28 +01:00
icc_nodes_remove ( & priv - > provider ) ;
return 0 ;
}
static int exynos_generic_icc_probe ( struct platform_device * pdev )
{
struct device * bus_dev = pdev - > dev . parent ;
struct exynos_icc_priv * priv ;
struct icc_provider * provider ;
struct icc_node * icc_node , * icc_parent_node ;
int ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , priv ) ;
provider = & priv - > provider ;
provider - > set = exynos_generic_icc_set ;
provider - > aggregate = icc_std_aggregate ;
provider - > xlate = exynos_generic_icc_xlate ;
provider - > dev = bus_dev ;
provider - > inter_set = true ;
provider - > data = priv ;
2023-03-06 08:56:43 +01:00
icc_provider_init ( provider ) ;
2020-11-12 15:09:28 +01:00
icc_node = icc_node_create ( pdev - > id ) ;
2023-03-06 08:56:43 +01:00
if ( IS_ERR ( icc_node ) )
return PTR_ERR ( icc_node ) ;
2020-11-12 15:09:28 +01:00
priv - > node = icc_node ;
icc_node - > name = devm_kasprintf ( & pdev - > dev , GFP_KERNEL , " %pOFn " ,
bus_dev - > of_node ) ;
if ( of_property_read_u32 ( bus_dev - > of_node , " samsung,data-clock-ratio " ,
& priv - > bus_clk_ratio ) )
priv - > bus_clk_ratio = EXYNOS_ICC_DEFAULT_BUS_CLK_RATIO ;
2023-03-06 08:56:42 +01:00
icc_node - > data = priv ;
icc_node_add ( icc_node , provider ) ;
2020-11-12 15:09:28 +01:00
/*
* Register a PM QoS request for the parent ( devfreq ) device .
*/
ret = dev_pm_qos_add_request ( bus_dev , & priv - > qos_req ,
DEV_PM_QOS_MIN_FREQUENCY , 0 ) ;
if ( ret < 0 )
goto err_node_del ;
icc_parent_node = exynos_icc_get_parent ( bus_dev - > of_node ) ;
if ( IS_ERR ( icc_parent_node ) ) {
ret = PTR_ERR ( icc_parent_node ) ;
goto err_pmqos_del ;
}
if ( icc_parent_node ) {
ret = icc_link_create ( icc_node , icc_parent_node - > id ) ;
if ( ret < 0 )
goto err_pmqos_del ;
}
2023-03-06 08:56:43 +01:00
ret = icc_provider_register ( provider ) ;
if ( ret < 0 )
goto err_pmqos_del ;
2020-11-12 15:09:28 +01:00
return 0 ;
err_pmqos_del :
dev_pm_qos_remove_request ( & priv - > qos_req ) ;
err_node_del :
icc_nodes_remove ( provider ) ;
2023-03-06 08:56:43 +01:00
2020-11-12 15:09:28 +01:00
return ret ;
}
static struct platform_driver exynos_generic_icc_driver = {
. driver = {
. name = " exynos-generic-icc " ,
. sync_state = icc_sync_state ,
} ,
. probe = exynos_generic_icc_probe ,
. remove = exynos_generic_icc_remove ,
} ;
module_platform_driver ( exynos_generic_icc_driver ) ;
MODULE_DESCRIPTION ( " Exynos generic interconnect driver " ) ;
MODULE_AUTHOR ( " Artur Świgoń <a.swigon@samsung.com> " ) ;
MODULE_AUTHOR ( " Sylwester Nawrocki <s.nawrocki@samsung.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:exynos-generic-icc " ) ;