162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Exynos generic interconnect provider driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2020 Samsung Electronics Co., Ltd. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Authors: Artur Świgoń <a.swigon@samsung.com> 862306a36Sopenharmony_ci * Sylwester Nawrocki <s.nawrocki@samsung.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/interconnect-provider.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/pm_qos.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define EXYNOS_ICC_DEFAULT_BUS_CLK_RATIO 8 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct exynos_icc_priv { 2162306a36Sopenharmony_ci struct device *dev; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci /* One interconnect node per provider */ 2462306a36Sopenharmony_ci struct icc_provider provider; 2562306a36Sopenharmony_ci struct icc_node *node; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci struct dev_pm_qos_request qos_req; 2862306a36Sopenharmony_ci u32 bus_clk_ratio; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic struct icc_node *exynos_icc_get_parent(struct device_node *np) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct of_phandle_args args; 3462306a36Sopenharmony_ci struct icc_node_data *icc_node_data; 3562306a36Sopenharmony_ci struct icc_node *icc_node; 3662306a36Sopenharmony_ci int num, ret; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci num = of_count_phandle_with_args(np, "interconnects", 3962306a36Sopenharmony_ci "#interconnect-cells"); 4062306a36Sopenharmony_ci if (num < 1) 4162306a36Sopenharmony_ci return NULL; /* parent nodes are optional */ 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* Get the interconnect target node */ 4462306a36Sopenharmony_ci ret = of_parse_phandle_with_args(np, "interconnects", 4562306a36Sopenharmony_ci "#interconnect-cells", 0, &args); 4662306a36Sopenharmony_ci if (ret < 0) 4762306a36Sopenharmony_ci return ERR_PTR(ret); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci icc_node_data = of_icc_get_from_provider(&args); 5062306a36Sopenharmony_ci of_node_put(args.np); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (IS_ERR(icc_node_data)) 5362306a36Sopenharmony_ci return ERR_CAST(icc_node_data); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci icc_node = icc_node_data->node; 5662306a36Sopenharmony_ci kfree(icc_node_data); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci return icc_node; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int exynos_generic_icc_set(struct icc_node *src, struct icc_node *dst) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci struct exynos_icc_priv *src_priv = src->data, *dst_priv = dst->data; 6462306a36Sopenharmony_ci s32 src_freq = max(src->avg_bw, src->peak_bw) / src_priv->bus_clk_ratio; 6562306a36Sopenharmony_ci s32 dst_freq = max(dst->avg_bw, dst->peak_bw) / dst_priv->bus_clk_ratio; 6662306a36Sopenharmony_ci int ret; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci ret = dev_pm_qos_update_request(&src_priv->qos_req, src_freq); 6962306a36Sopenharmony_ci if (ret < 0) { 7062306a36Sopenharmony_ci dev_err(src_priv->dev, "failed to update PM QoS of %s (src)\n", 7162306a36Sopenharmony_ci src->name); 7262306a36Sopenharmony_ci return ret; 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci ret = dev_pm_qos_update_request(&dst_priv->qos_req, dst_freq); 7662306a36Sopenharmony_ci if (ret < 0) { 7762306a36Sopenharmony_ci dev_err(dst_priv->dev, "failed to update PM QoS of %s (dst)\n", 7862306a36Sopenharmony_ci dst->name); 7962306a36Sopenharmony_ci return ret; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci return 0; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic struct icc_node *exynos_generic_icc_xlate(struct of_phandle_args *spec, 8662306a36Sopenharmony_ci void *data) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct exynos_icc_priv *priv = data; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if (spec->np != priv->dev->parent->of_node) 9162306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return priv->node; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic int exynos_generic_icc_remove(struct platform_device *pdev) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct exynos_icc_priv *priv = platform_get_drvdata(pdev); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci icc_provider_deregister(&priv->provider); 10162306a36Sopenharmony_ci icc_nodes_remove(&priv->provider); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic int exynos_generic_icc_probe(struct platform_device *pdev) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct device *bus_dev = pdev->dev.parent; 10962306a36Sopenharmony_ci struct exynos_icc_priv *priv; 11062306a36Sopenharmony_ci struct icc_provider *provider; 11162306a36Sopenharmony_ci struct icc_node *icc_node, *icc_parent_node; 11262306a36Sopenharmony_ci int ret; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 11562306a36Sopenharmony_ci if (!priv) 11662306a36Sopenharmony_ci return -ENOMEM; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci priv->dev = &pdev->dev; 11962306a36Sopenharmony_ci platform_set_drvdata(pdev, priv); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci provider = &priv->provider; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci provider->set = exynos_generic_icc_set; 12462306a36Sopenharmony_ci provider->aggregate = icc_std_aggregate; 12562306a36Sopenharmony_ci provider->xlate = exynos_generic_icc_xlate; 12662306a36Sopenharmony_ci provider->dev = bus_dev; 12762306a36Sopenharmony_ci provider->inter_set = true; 12862306a36Sopenharmony_ci provider->data = priv; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci icc_provider_init(provider); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci icc_node = icc_node_create(pdev->id); 13362306a36Sopenharmony_ci if (IS_ERR(icc_node)) 13462306a36Sopenharmony_ci return PTR_ERR(icc_node); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci priv->node = icc_node; 13762306a36Sopenharmony_ci icc_node->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn", 13862306a36Sopenharmony_ci bus_dev->of_node); 13962306a36Sopenharmony_ci if (of_property_read_u32(bus_dev->of_node, "samsung,data-clock-ratio", 14062306a36Sopenharmony_ci &priv->bus_clk_ratio)) 14162306a36Sopenharmony_ci priv->bus_clk_ratio = EXYNOS_ICC_DEFAULT_BUS_CLK_RATIO; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci icc_node->data = priv; 14462306a36Sopenharmony_ci icc_node_add(icc_node, provider); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* 14762306a36Sopenharmony_ci * Register a PM QoS request for the parent (devfreq) device. 14862306a36Sopenharmony_ci */ 14962306a36Sopenharmony_ci ret = dev_pm_qos_add_request(bus_dev, &priv->qos_req, 15062306a36Sopenharmony_ci DEV_PM_QOS_MIN_FREQUENCY, 0); 15162306a36Sopenharmony_ci if (ret < 0) 15262306a36Sopenharmony_ci goto err_node_del; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci icc_parent_node = exynos_icc_get_parent(bus_dev->of_node); 15562306a36Sopenharmony_ci if (IS_ERR(icc_parent_node)) { 15662306a36Sopenharmony_ci ret = PTR_ERR(icc_parent_node); 15762306a36Sopenharmony_ci goto err_pmqos_del; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci if (icc_parent_node) { 16062306a36Sopenharmony_ci ret = icc_link_create(icc_node, icc_parent_node->id); 16162306a36Sopenharmony_ci if (ret < 0) 16262306a36Sopenharmony_ci goto err_pmqos_del; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ret = icc_provider_register(provider); 16662306a36Sopenharmony_ci if (ret < 0) 16762306a36Sopenharmony_ci goto err_pmqos_del; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci return 0; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cierr_pmqos_del: 17262306a36Sopenharmony_ci dev_pm_qos_remove_request(&priv->qos_req); 17362306a36Sopenharmony_cierr_node_del: 17462306a36Sopenharmony_ci icc_nodes_remove(provider); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return ret; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic struct platform_driver exynos_generic_icc_driver = { 18062306a36Sopenharmony_ci .driver = { 18162306a36Sopenharmony_ci .name = "exynos-generic-icc", 18262306a36Sopenharmony_ci .sync_state = icc_sync_state, 18362306a36Sopenharmony_ci }, 18462306a36Sopenharmony_ci .probe = exynos_generic_icc_probe, 18562306a36Sopenharmony_ci .remove = exynos_generic_icc_remove, 18662306a36Sopenharmony_ci}; 18762306a36Sopenharmony_cimodule_platform_driver(exynos_generic_icc_driver); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ciMODULE_DESCRIPTION("Exynos generic interconnect driver"); 19062306a36Sopenharmony_ciMODULE_AUTHOR("Artur Świgoń <a.swigon@samsung.com>"); 19162306a36Sopenharmony_ciMODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); 19262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 19362306a36Sopenharmony_ciMODULE_ALIAS("platform:exynos-generic-icc"); 194