| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * MediaTek External Memory Interface (EMI) Interconnect driver | |
| * | |
| * Copyright (c) 2021 MediaTek Inc. | |
| * Copyright (c) 2024 Collabora Ltd. | |
| * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> | |
| */ | |
| #include <linux/interconnect.h> | |
| #include <linux/interconnect-provider.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/of_platform.h> | |
| #include <linux/overflow.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/soc/mediatek/dvfsrc.h> | |
| #include "icc-emi.h" | |
| static int mtk_emi_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, | |
| u32 peak_bw, u32 *agg_avg, u32 *agg_peak) | |
| { | |
| struct mtk_icc_node *in = node->data; | |
| if (check_add_overflow(*agg_avg, avg_bw, agg_avg)) | |
| *agg_avg = U32_MAX; | |
| *agg_peak = max_t(u32, *agg_peak, peak_bw); | |
| in->sum_avg = *agg_avg; | |
| in->max_peak = *agg_peak; | |
| return 0; | |
| } | |
| static int mtk_emi_icc_set(struct icc_node *src, struct icc_node *dst) | |
| { | |
| struct mtk_icc_node *node = dst->data; | |
| struct device *dev; | |
| int ret; | |
| if (unlikely(!src->provider)) | |
| return -EINVAL; | |
| dev = src->provider->dev->parent; | |
| switch (node->ep) { | |
| case 0: | |
| break; | |
| case 1: | |
| ret = mtk_dvfsrc_send_request(dev, MTK_DVFSRC_CMD_PEAK_BW, node->max_peak); | |
| if (ret) { | |
| dev_err(dev, "Cannot send peak bw request: %d\n", ret); | |
| return ret; | |
| } | |
| ret = mtk_dvfsrc_send_request(dev, MTK_DVFSRC_CMD_BW, node->sum_avg); | |
| if (ret) { | |
| dev_err(dev, "Cannot send bw request: %d\n", ret); | |
| return ret; | |
| } | |
| break; | |
| case 2: | |
| ret = mtk_dvfsrc_send_request(dev, MTK_DVFSRC_CMD_HRT_BW, node->sum_avg); | |
| if (ret) { | |
| dev_err(dev, "Cannot send HRT bw request: %d\n", ret); | |
| return ret; | |
| } | |
| break; | |
| default: | |
| dev_err(src->provider->dev, "Unknown endpoint %u\n", node->ep); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| int mtk_emi_icc_probe(struct platform_device *pdev) | |
| { | |
| const struct mtk_icc_desc *desc; | |
| struct device *dev = &pdev->dev; | |
| struct icc_node *node; | |
| struct icc_onecell_data *data; | |
| struct icc_provider *provider; | |
| struct mtk_icc_node **mnodes; | |
| int i, j, ret; | |
| desc = of_device_get_match_data(dev); | |
| if (!desc) | |
| return -EINVAL; | |
| mnodes = desc->nodes; | |
| provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL); | |
| if (!provider) | |
| return -ENOMEM; | |
| data = devm_kzalloc(dev, struct_size(data, nodes, desc->num_nodes), GFP_KERNEL); | |
| if (!data) | |
| return -ENOMEM; | |
| provider->dev = dev; | |
| provider->set = mtk_emi_icc_set; | |
| provider->aggregate = mtk_emi_icc_aggregate; | |
| provider->xlate = of_icc_xlate_onecell; | |
| INIT_LIST_HEAD(&provider->nodes); | |
| provider->data = data; | |
| for (i = 0; i < desc->num_nodes; i++) { | |
| if (!mnodes[i]) | |
| continue; | |
| node = icc_node_create(mnodes[i]->id); | |
| if (IS_ERR(node)) { | |
| ret = PTR_ERR(node); | |
| goto err; | |
| } | |
| node->name = mnodes[i]->name; | |
| node->data = mnodes[i]; | |
| icc_node_add(node, provider); | |
| for (j = 0; j < mnodes[i]->num_links; j++) | |
| icc_link_create(node, mnodes[i]->links[j]); | |
| data->nodes[i] = node; | |
| } | |
| data->num_nodes = desc->num_nodes; | |
| ret = icc_provider_register(provider); | |
| if (ret) | |
| goto err; | |
| platform_set_drvdata(pdev, provider); | |
| return 0; | |
| err: | |
| icc_nodes_remove(provider); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(mtk_emi_icc_probe); | |
| void mtk_emi_icc_remove(struct platform_device *pdev) | |
| { | |
| struct icc_provider *provider = platform_get_drvdata(pdev); | |
| icc_provider_deregister(provider); | |
| icc_nodes_remove(provider); | |
| } | |
| EXPORT_SYMBOL_GPL(mtk_emi_icc_remove); | |
| MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); | |
| MODULE_AUTHOR("Henry Chen <henryc.chen@mediatek.com>"); | |
| MODULE_DESCRIPTION("MediaTek External Memory Interface interconnect driver"); | |
| MODULE_LICENSE("GPL"); |