diff options
Diffstat (limited to 'drivers/interconnect/qcom/icc-rpmh.c')
| -rw-r--r-- | drivers/interconnect/qcom/icc-rpmh.c | 179 |
1 files changed, 138 insertions, 41 deletions
diff --git a/drivers/interconnect/qcom/icc-rpmh.c b/drivers/interconnect/qcom/icc-rpmh.c index 2c8e12549804..3b445acefece 100644 --- a/drivers/interconnect/qcom/icc-rpmh.c +++ b/drivers/interconnect/qcom/icc-rpmh.c @@ -1,18 +1,54 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. */ +#include <linux/bitfield.h> +#include <linux/clk.h> #include <linux/interconnect.h> #include <linux/interconnect-provider.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> +#include <linux/of_platform.h> #include <linux/slab.h> #include "bcm-voter.h" +#include "icc-common.h" #include "icc-rpmh.h" +/* QNOC QoS */ +#define QOSGEN_MAINCTL_LO(p, qp) (0x8 + (p->port_offsets[qp])) +#define QOS_SLV_URG_MSG_EN_MASK GENMASK(3, 3) +#define QOS_DFLT_PRIO_MASK GENMASK(6, 4) +#define QOS_DISABLE_MASK GENMASK(24, 24) + +/** + * qcom_icc_set_qos - initialize static QoS configurations + * @qp: qcom icc provider to which @node belongs + * @node: qcom icc node to operate on + */ +static void qcom_icc_set_qos(struct qcom_icc_provider *qp, + struct qcom_icc_node *node) +{ + const struct qcom_icc_qosbox *qos = node->qosbox; + int port; + + for (port = 0; port < qos->num_ports; port++) { + regmap_update_bits(qp->regmap, QOSGEN_MAINCTL_LO(qos, port), + QOS_DISABLE_MASK, + FIELD_PREP(QOS_DISABLE_MASK, qos->prio_fwd_disable)); + + regmap_update_bits(qp->regmap, QOSGEN_MAINCTL_LO(qos, port), + QOS_DFLT_PRIO_MASK, + FIELD_PREP(QOS_DFLT_PRIO_MASK, qos->prio)); + + regmap_update_bits(qp->regmap, QOSGEN_MAINCTL_LO(qos, port), + QOS_SLV_URG_MSG_EN_MASK, + FIELD_PREP(QOS_SLV_URG_MSG_EN_MASK, qos->urg_fwd)); + } +} + /** * qcom_icc_pre_aggregate - cleans up stale values from prior icc_set * @node: icc node to operate on @@ -100,31 +136,6 @@ int qcom_icc_set(struct icc_node *src, struct icc_node *dst) } EXPORT_SYMBOL_GPL(qcom_icc_set); -struct icc_node_data *qcom_icc_xlate_extended(struct of_phandle_args *spec, void *data) -{ - struct icc_node_data *ndata; - struct icc_node *node; - - node = of_icc_xlate_onecell(spec, data); - if (IS_ERR(node)) - return ERR_CAST(node); - - ndata = kzalloc(sizeof(*ndata), GFP_KERNEL); - if (!ndata) - return ERR_PTR(-ENOMEM); - - ndata->node = node; - - if (spec->args_count == 2) - ndata->tag = spec->args[1]; - - if (spec->args_count > 2) - pr_warn("%pOF: Too many arguments, path tag is not parsed\n", spec->np); - - return ndata; -} -EXPORT_SYMBOL_GPL(qcom_icc_xlate_extended); - /** * qcom_icc_bcm_init - populates bcm aux data and connect qnodes * @bcm: bcm to be initialized @@ -183,13 +194,43 @@ int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev) } EXPORT_SYMBOL_GPL(qcom_icc_bcm_init); +/** + * qcom_icc_rpmh_configure_qos - configure QoS parameters + * @qp: qcom icc provider associated with QoS endpoint nodes + * + * Return: 0 on success, or an error code otherwise + */ +static int qcom_icc_rpmh_configure_qos(struct qcom_icc_provider *qp) +{ + struct qcom_icc_node *qnode; + size_t i; + int ret; + + ret = clk_bulk_prepare_enable(qp->num_clks, qp->clks); + if (ret) + return ret; + + for (i = 0; i < qp->num_nodes; i++) { + qnode = qp->nodes[i]; + if (!qnode) + continue; + + if (qnode->qosbox) + qcom_icc_set_qos(qp, qnode); + } + + clk_bulk_disable_unprepare(qp->num_clks, qp->clks); + + return ret; +} + int qcom_icc_rpmh_probe(struct platform_device *pdev) { const struct qcom_icc_desc *desc; struct device *dev = &pdev->dev; struct icc_onecell_data *data; struct icc_provider *provider; - struct qcom_icc_node **qnodes, *qn; + struct qcom_icc_node * const *qnodes, *qn; struct qcom_icc_provider *qp; struct icc_node *node; size_t num_nodes, i, j; @@ -209,6 +250,7 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev) data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes), GFP_KERNEL); if (!data) return -ENOMEM; + data->num_nodes = num_nodes; provider = &qp->provider; provider->dev = dev; @@ -216,21 +258,20 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev) provider->pre_aggregate = qcom_icc_pre_aggregate; provider->aggregate = qcom_icc_aggregate; provider->xlate_extended = qcom_icc_xlate_extended; - INIT_LIST_HEAD(&provider->nodes); provider->data = data; + icc_provider_init(provider); + qp->dev = dev; qp->bcms = desc->bcms; + qp->nodes = desc->nodes; qp->num_bcms = desc->num_bcms; + qp->num_nodes = desc->num_nodes; qp->voter = of_bcm_voter_get(qp->dev, NULL); if (IS_ERR(qp->voter)) return PTR_ERR(qp->voter); - ret = icc_provider_add(provider); - if (ret) - return ret; - for (i = 0; i < qp->num_bcms; i++) qcom_icc_bcm_init(qp->bcms[i], dev); @@ -239,40 +280,96 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev) if (!qn) continue; - node = icc_node_create(qn->id); + if (!qn->node) + qn->node = icc_node_create_dyn(); + + node = qn->node; if (IS_ERR(node)) { ret = PTR_ERR(node); - goto err; + goto err_remove_nodes; + } + + ret = icc_node_set_name(node, provider, qn->name); + if (ret) { + icc_node_destroy(node->id); + goto err_remove_nodes; } - node->name = qn->name; node->data = qn; icc_node_add(node, provider); for (j = 0; j < qn->num_links; j++) - icc_link_create(node, qn->links[j]); + icc_link_nodes(node, &qn->link_nodes[j]->node); data->nodes[i] = node; } - data->num_nodes = num_nodes; + if (desc->config) { + struct resource *res; + void __iomem *base; + + /* Try parent's regmap first */ + qp->regmap = dev_get_regmap(dev->parent, NULL); + if (!qp->regmap) { + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + goto skip_qos_config; + + qp->regmap = devm_regmap_init_mmio(dev, base, desc->config); + if (IS_ERR(qp->regmap)) { + dev_info(dev, "Skipping QoS, regmap failed; %ld\n", + PTR_ERR(qp->regmap)); + goto skip_qos_config; + } + } + + qp->num_clks = devm_clk_bulk_get_all(qp->dev, &qp->clks); + if (qp->num_clks == -EPROBE_DEFER) + return dev_err_probe(dev, qp->num_clks, "Failed to get QoS clocks\n"); + + if (qp->num_clks < 0 || (!qp->num_clks && desc->qos_requires_clocks)) { + dev_info(dev, "Skipping QoS, failed to get clk: %d\n", qp->num_clks); + goto skip_qos_config; + } + + ret = qcom_icc_rpmh_configure_qos(qp); + if (ret) + dev_info(dev, "Failed to program QoS: %d\n", ret); + } + +skip_qos_config: + ret = icc_provider_register(provider); + if (ret) + goto err_remove_nodes; + platform_set_drvdata(pdev, qp); + /* Populate child NoC devices if any */ + if (of_get_child_count(dev->of_node) > 0) { + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) + goto err_deregister_provider; + } + return 0; -err: + +err_deregister_provider: + icc_provider_deregister(provider); +err_remove_nodes: icc_nodes_remove(provider); - icc_provider_del(provider); + return ret; } EXPORT_SYMBOL_GPL(qcom_icc_rpmh_probe); -int qcom_icc_rpmh_remove(struct platform_device *pdev) +void qcom_icc_rpmh_remove(struct platform_device *pdev) { struct qcom_icc_provider *qp = platform_get_drvdata(pdev); + icc_provider_deregister(&qp->provider); icc_nodes_remove(&qp->provider); - return icc_provider_del(&qp->provider); } EXPORT_SYMBOL_GPL(qcom_icc_rpmh_remove); +MODULE_DESCRIPTION("Qualcomm RPMh interconnect driver"); MODULE_LICENSE("GPL v2"); |
