Files
2025-08-12 22:16:57 +02:00

599 lines
13 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/interconnect.h>
#include <linux/interconnect-provider.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <soc/qcom/socinfo.h>
#include "bcm-voter.h"
#include "icc-common.h"
#include "icc-debug.h"
#include "icc-rpmh.h"
#include "qnoc-qos.h"
/**
* qcom_icc_pre_aggregate - cleans up stale values from prior icc_set
* @node: icc node to operate on
*/
void qcom_icc_pre_aggregate(struct icc_node *node)
{
size_t i;
struct qcom_icc_node *qn;
struct qcom_icc_provider *qp;
qn = node->data;
qp = to_qcom_provider(node->provider);
for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) {
qn->sum_avg[i] = 0;
qn->max_peak[i] = 0;
qn->perf_mode[i] = false;
}
for (i = 0; i < qn->num_bcms; i++)
qcom_icc_bcm_voter_add(qp->voters[qn->bcms[i]->voter_idx],
qn->bcms[i]);
}
EXPORT_SYMBOL_GPL(qcom_icc_pre_aggregate);
static void qcom_icc_pre_aggregate_stub(struct icc_node *node)
{
}
/**
* qcom_icc_aggregate - aggregate bw for buckets indicated by tag
* @node: node to aggregate
* @tag: tag to indicate which buckets to aggregate
* @avg_bw: new bw to sum aggregate
* @peak_bw: new bw to max aggregate
* @agg_avg: existing aggregate avg bw val
* @agg_peak: existing aggregate peak bw val
*/
int qcom_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
{
size_t i;
struct qcom_icc_node *qn;
qn = node->data;
if (!tag)
tag = QCOM_ICC_TAG_ALWAYS;
for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) {
if (tag & BIT(i)) {
qn->sum_avg[i] += avg_bw;
qn->max_peak[i] = max_t(u32, qn->max_peak[i], peak_bw);
if (tag & QCOM_ICC_TAG_PERF_MODE && (avg_bw || peak_bw))
qn->perf_mode[i] = true;
}
if (node->init_avg || node->init_peak) {
qn->sum_avg[i] = max_t(u64, qn->sum_avg[i], node->init_avg);
qn->max_peak[i] = max_t(u64, qn->max_peak[i], node->init_peak);
}
}
*agg_avg += avg_bw;
*agg_peak = max_t(u32, *agg_peak, peak_bw);
return 0;
}
EXPORT_SYMBOL_GPL(qcom_icc_aggregate);
int qcom_icc_aggregate_stub(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
{
return 0;
}
EXPORT_SYMBOL(qcom_icc_aggregate_stub);
/**
* qcom_icc_set - set the constraints based on path
* @src: source node for the path to set constraints on
* @dst: destination node for the path to set constraints on
*
* Return: 0 on success, or an error code otherwise
*/
int qcom_icc_set(struct icc_node *src, struct icc_node *dst)
{
struct qcom_icc_provider *qp;
struct icc_node *node;
struct qcom_icc_node *qn;
u64 clk_rate;
int i, ret;
if (!src)
node = dst;
else
node = src;
qp = to_qcom_provider(node->provider);
qn = node->data;
if (qn->bw_scale_numerator && qn->bw_scale_denominator) {
node->avg_bw *= qn->bw_scale_numerator;
do_div(node->avg_bw, qn->bw_scale_denominator);
node->peak_bw *= qn->bw_scale_numerator;
do_div(node->peak_bw, qn->bw_scale_denominator);
}
if (qn->clk) {
/*
* Multiply by 1000 to convert the unit of bandwidth from KBps
* to Bps, then divide by the bandwidth to get the clk rate in Hz.
*/
clk_rate = (u64)max(node->avg_bw, node->peak_bw) * 1000 / qn->buswidth;
clk_rate = clk_rate > U32_MAX ? U32_MAX : clk_rate;
if (clk_rate > 0) {
ret = clk_set_rate(qn->clk, clk_rate);
if (ret)
dev_warn(qp->dev, "Failed to set %s rate to %llu for %s\n",
qn->clk_name, clk_rate, qn->name);
if (qn->toggle_clk && !qn->clk_enabled) {
ret = clk_prepare_enable(qn->clk);
if (ret) {
dev_err(qp->dev, "Failed to enable %s for %s\n",
qn->clk_name, qn->name);
return ret;
}
qn->clk_enabled = true;
}
} else if (qn->toggle_clk && qn->clk_enabled) {
clk_disable_unprepare(qn->clk);
qn->clk_enabled = false;
}
}
for (i = 0; i < qp->num_voters; i++)
qcom_icc_bcm_voter_commit(qp->voters[i]);
return 0;
}
EXPORT_SYMBOL_GPL(qcom_icc_set);
int qcom_icc_set_stub(struct icc_node *src, struct icc_node *dst)
{
return 0;
}
EXPORT_SYMBOL(qcom_icc_set_stub);
int qcom_icc_get_bw_stub(struct icc_node *node, u32 *avg, u32 *peak)
{
*avg = 0;
*peak = 0;
return 0;
}
EXPORT_SYMBOL(qcom_icc_get_bw_stub);
int qcom_icc_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
{
struct qcom_icc_node *qn = node->data;
*peak = qn->init_peak;
*avg = qn->init_avg;
return 0;
}
/**
* qcom_icc_bcm_init - populates bcm aux data and connect qnodes
* @bcm: bcm to be initialized
* @dev: associated provider device
*
* Return: 0 on success, or an error code otherwise
*/
int qcom_icc_bcm_init(struct qcom_icc_provider *qp, struct qcom_icc_bcm *bcm,
struct device *dev)
{
struct qcom_icc_node *qn;
const struct bcm_db *data;
struct bcm_voter *voter;
size_t data_count;
int i;
/* BCM is already initialised*/
if (bcm->disabled || bcm->addr)
return 0;
bcm->addr = cmd_db_read_addr(bcm->name);
if (!bcm->addr) {
dev_err(dev, "%s could not find RPMh address\n",
bcm->name);
return -EINVAL;
}
data = cmd_db_read_aux_data(bcm->name, &data_count);
if (IS_ERR(data)) {
dev_err(dev, "%s command db read error (%ld)\n",
bcm->name, PTR_ERR(data));
return PTR_ERR(data);
}
if (!data_count) {
dev_err(dev, "%s command db missing or partial aux data\n",
bcm->name);
return -EINVAL;
}
bcm->aux_data.unit = max_t(u32, 1, le32_to_cpu(data->unit));
bcm->aux_data.width = max_t(u16, 1, le16_to_cpu(data->width));
bcm->aux_data.vcd = data->vcd;
bcm->aux_data.reserved = data->reserved;
INIT_LIST_HEAD(&bcm->list);
INIT_LIST_HEAD(&bcm->ws_list);
if (!bcm->vote_scale)
bcm->vote_scale = 1000;
/* Link Qnodes to their respective BCMs */
for (i = 0; i < bcm->num_nodes; i++) {
qn = bcm->nodes[i];
qn->bcms[qn->num_bcms] = bcm;
qn->num_bcms++;
}
if (bcm->keepalive) {
/*
* Default vote for keepalive BCMs, use the first node as proxy node
*/
qn = bcm->nodes[0];
qn->init_avg = INT_MAX;
qn->init_peak = INT_MAX;
voter = qp->voters[bcm->voter_idx];
qcom_icc_bcm_voter_add(voter, bcm);
}
return 0;
}
EXPORT_SYMBOL_GPL(qcom_icc_bcm_init);
static bool bcm_needs_qos_proxy(struct qcom_icc_bcm *bcm)
{
int i;
if (bcm->qos_proxy)
return true;
if (bcm->voter_idx == 0)
for (i = 0; i < bcm->num_nodes; i++)
if (bcm->nodes[i]->qosbox)
return true;
return false;
}
static int enable_qos_deps(struct qcom_icc_provider *qp)
{
struct qcom_icc_bcm *bcm;
struct bcm_voter *voter;
bool keepalive;
int ret, i;
for (i = 0; i < qp->num_bcms; i++) {
bcm = qp->bcms[i];
if (bcm_needs_qos_proxy(bcm)) {
keepalive = bcm->keepalive;
bcm->keepalive = true;
voter = qp->voters[bcm->voter_idx];
qcom_icc_bcm_voter_add(voter, bcm);
ret = qcom_icc_bcm_voter_commit(voter);
bcm->keepalive = keepalive;
if (ret) {
dev_err(qp->dev, "failed to vote BW to %s for QoS\n",
bcm->name);
return ret;
}
}
}
ret = clk_bulk_prepare_enable(qp->num_clks, qp->clks);
if (ret) {
dev_err(qp->dev, "failed to enable clocks for QoS\n");
return ret;
}
return 0;
}
static void disable_qos_deps(struct qcom_icc_provider *qp)
{
struct qcom_icc_bcm *bcm;
struct bcm_voter *voter;
int i;
clk_bulk_disable_unprepare(qp->num_clks, qp->clks);
for (i = 0; i < qp->num_bcms; i++) {
bcm = qp->bcms[i];
if (bcm_needs_qos_proxy(bcm)) {
voter = qp->voters[bcm->voter_idx];
qcom_icc_bcm_voter_add(voter, bcm);
qcom_icc_bcm_voter_commit(voter);
}
}
}
int qcom_icc_rpmh_configure_qos(struct qcom_icc_provider *qp)
{
struct qcom_icc_node *qnode;
size_t i;
int ret;
ret = enable_qos_deps(qp);
if (ret)
return ret;
for (i = 0; i < qp->num_nodes; i++) {
qnode = qp->nodes[i];
if (!qnode)
continue;
if (qnode->qosbox)
qnode->noc_ops->set_qos(qnode);
}
disable_qos_deps(qp);
return ret;
}
EXPORT_SYMBOL(qcom_icc_rpmh_configure_qos);
static struct regmap *qcom_icc_rpmh_map(struct platform_device *pdev,
const struct qcom_icc_desc *desc)
{
void __iomem *base;
struct resource *res;
struct device *dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return NULL;
base = devm_ioremap(dev, res->start, resource_size(res));
if (IS_ERR(base))
return ERR_CAST(base);
return devm_regmap_init_mmio(dev, base, desc->config);
}
static bool is_voter_disabled(struct device *dev, char *voter)
{
struct device_node *np = dev->of_node;
int idx = 0;
if (!voter || !np)
return true;
idx = of_property_match_string(np, "qcom,disabled-voters", voter);
if (idx >= 0)
return true;
if ((strnstr(voter, "disp", strlen(voter)) && socinfo_get_part_info(PART_DISPLAY)) ||
(strnstr(voter, "cam", strlen(voter)) && socinfo_get_part_info(PART_CAMERA)))
return true;
return false;
}
static int qcom_icc_init_disabled_parts(struct qcom_icc_provider *qp)
{
struct qcom_icc_bcm *bcm;
struct qcom_icc_node * const *qnodes, *qn;
const struct qcom_icc_desc *desc;
int voter_idx, i, j;
char *voter_name;
desc = of_device_get_match_data(qp->dev);
if (!desc)
return -EINVAL;
for (i = 0; i < qp->num_bcms; i++) {
bcm = qp->bcms[i];
voter_idx = bcm->voter_idx;
voter_name = desc->voters[voter_idx];
/* Disable BCMs incase of NO display or No Camera */
if (is_voter_disabled(qp->dev, voter_name)) {
bcm->disabled = true;
qnodes = qp->nodes;
for (j = 0; j < qp->num_nodes; j++) {
qn = qnodes[j];
if (!qn)
continue;
if (strnstr(qn->name, voter_name, strlen(qn->name)))
qn->disabled = true;
}
}
}
return 0;
}
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 * const *qnodes, *qn;
struct qcom_icc_provider *qp;
struct icc_node *node;
size_t num_nodes, i, j;
int ret;
desc = of_device_get_match_data(dev);
if (!desc)
return -EINVAL;
qnodes = desc->nodes;
num_nodes = desc->num_nodes;
qp = devm_kzalloc(dev, sizeof(*qp), GFP_KERNEL);
if (!qp)
return -ENOMEM;
data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->num_nodes = num_nodes;
qp->stub = of_property_read_bool(pdev->dev.of_node, "qcom,stub");
qp->skip_qos = of_property_read_bool(pdev->dev.of_node, "qcom,skip-qos");
provider = &qp->provider;
provider->dev = dev;
provider->set = qcom_icc_set;
provider->pre_aggregate = qcom_icc_pre_aggregate;
provider->aggregate = qcom_icc_aggregate;
provider->xlate_extended = qcom_icc_xlate_extended;
provider->data = data;
provider->get_bw = qcom_icc_get_bw;
if (qp->stub) {
provider->set = qcom_icc_set_stub;
provider->pre_aggregate = qcom_icc_pre_aggregate_stub;
provider->aggregate = qcom_icc_aggregate_stub;
}
icc_provider_init(provider);
qp->dev = dev;
qp->bcms = desc->bcms;
qp->nodes = desc->nodes;
qp->num_nodes = desc->num_nodes;
if (!qp->stub) {
qp->num_bcms = desc->num_bcms;
qp->num_voters = desc->num_voters;
qp->voters = devm_kcalloc(&pdev->dev, qp->num_voters,
sizeof(*qp->voters), GFP_KERNEL);
if (!qp->voters)
return -ENOMEM;
ret = qcom_icc_init_disabled_parts(qp);
if (ret)
return ret;
for (i = 0; i < qp->num_voters; i++) {
if (desc->voters[i] && !is_voter_disabled(qp->dev, desc->voters[i])) {
qp->voters[i] = of_bcm_voter_get(qp->dev, desc->voters[i]);
if (IS_ERR(qp->voters[i]))
return PTR_ERR(qp->voters[i]);
}
}
}
qp->regmap = qcom_icc_rpmh_map(pdev, desc);
if (IS_ERR(qp->regmap))
return PTR_ERR(qp->regmap);
qp->num_clks = devm_clk_bulk_get_all(qp->dev, &qp->clks);
if (qp->num_clks < 0)
return qp->num_clks;
for (i = 0; i < qp->num_bcms; i++)
qcom_icc_bcm_init(qp, qp->bcms[i], dev);
for (i = 0; i < num_nodes; i++) {
qn = qnodes[i];
if (!qn || qn->disabled)
continue;
qn->regmap = dev_get_regmap(qp->dev, NULL);
node = icc_node_create(qn->id);
if (IS_ERR(node)) {
ret = PTR_ERR(node);
goto err_remove_nodes;
}
if (qn->clk_name) {
qn->clk = devm_clk_get(qp->dev, qn->clk_name);
if (IS_ERR(qn->clk)) {
ret = PTR_ERR(qn->clk);
if (ret != -EPROBE_DEFER)
dev_err(qp->dev, "failed to get %s, err:(%d)\n",
qn->clk_name, ret);
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]);
data->nodes[i] = node;
}
if (!qp->skip_qos) {
ret = qcom_icc_rpmh_configure_qos(qp);
if (ret)
goto err_remove_nodes;
}
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;
}
qcom_icc_debug_register(provider);
return 0;
err_deregister_provider:
icc_provider_deregister(provider);
err_remove_nodes:
icc_nodes_remove(provider);
return ret;
}
EXPORT_SYMBOL_GPL(qcom_icc_rpmh_probe);
int qcom_icc_rpmh_remove(struct platform_device *pdev)
{
struct qcom_icc_provider *qp = platform_get_drvdata(pdev);
qcom_icc_debug_unregister(&qp->provider);
clk_bulk_put_all(qp->num_clks, qp->clks);
icc_provider_deregister(&qp->provider);
icc_nodes_remove(&qp->provider);
return 0;
}
EXPORT_SYMBOL_GPL(qcom_icc_rpmh_remove);
MODULE_LICENSE("GPL v2");