mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-09 11:30:02 +00:00
EPSS on SA8775P has two instances, necessitating the creation of two device nodes with different compatibles due to the unique ICC node ID and name limitations in the interconnect framework. Add multidevice support for the OSM-L3 provider to dynamically obtain unique node IDs and register with the framework. EPSS topology includes a single master-slave pair within the same provider, the node linking logic is simplified by directly connecting the master node to the slave node. Signed-off-by: Raviteja Laggyshetty <quic_rlaggysh@quicinc.com> Link: https://lore.kernel.org/r/20250415095343.32125-4-quic_rlaggysh@quicinc.com Signed-off-by: Georgi Djakov <djakov@kernel.org>
290 lines
7.2 KiB
C
290 lines
7.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/args.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/interconnect-provider.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <dt-bindings/interconnect/qcom,osm-l3.h>
|
|
|
|
#define LUT_MAX_ENTRIES 40U
|
|
#define LUT_SRC GENMASK(31, 30)
|
|
#define LUT_L_VAL GENMASK(7, 0)
|
|
#define CLK_HW_DIV 2
|
|
|
|
/* OSM Register offsets */
|
|
#define REG_ENABLE 0x0
|
|
#define OSM_LUT_ROW_SIZE 32
|
|
#define OSM_REG_FREQ_LUT 0x110
|
|
#define OSM_REG_PERF_STATE 0x920
|
|
|
|
/* EPSS Register offsets */
|
|
#define EPSS_LUT_ROW_SIZE 4
|
|
#define EPSS_REG_L3_VOTE 0x90
|
|
#define EPSS_REG_FREQ_LUT 0x100
|
|
#define EPSS_REG_PERF_STATE 0x320
|
|
|
|
#define to_osm_l3_provider(_provider) \
|
|
container_of(_provider, struct qcom_osm_l3_icc_provider, provider)
|
|
|
|
struct qcom_osm_l3_icc_provider {
|
|
void __iomem *base;
|
|
unsigned int max_state;
|
|
unsigned int reg_perf_state;
|
|
unsigned long lut_tables[LUT_MAX_ENTRIES];
|
|
struct icc_provider provider;
|
|
};
|
|
|
|
/**
|
|
* struct qcom_osm_l3_node - Qualcomm specific interconnect nodes
|
|
* @name: the node name used in debugfs
|
|
* @buswidth: width of the interconnect between a node and the bus
|
|
*/
|
|
struct qcom_osm_l3_node {
|
|
const char *name;
|
|
u16 buswidth;
|
|
};
|
|
|
|
struct qcom_osm_l3_desc {
|
|
const struct qcom_osm_l3_node * const *nodes;
|
|
size_t num_nodes;
|
|
unsigned int lut_row_size;
|
|
unsigned int reg_freq_lut;
|
|
unsigned int reg_perf_state;
|
|
};
|
|
|
|
#define DEFINE_QNODE(_name, _buswidth) \
|
|
static const struct qcom_osm_l3_node _name = { \
|
|
.name = #_name, \
|
|
.buswidth = _buswidth, \
|
|
}
|
|
|
|
DEFINE_QNODE(osm_l3_slave, 16);
|
|
DEFINE_QNODE(osm_l3_master, 16);
|
|
|
|
static const struct qcom_osm_l3_node * const osm_l3_nodes[] = {
|
|
[MASTER_OSM_L3_APPS] = &osm_l3_master,
|
|
[SLAVE_OSM_L3] = &osm_l3_slave,
|
|
};
|
|
|
|
DEFINE_QNODE(epss_l3_slave, 32);
|
|
DEFINE_QNODE(epss_l3_master, 32);
|
|
|
|
static const struct qcom_osm_l3_node * const epss_l3_nodes[] = {
|
|
[MASTER_EPSS_L3_APPS] = &epss_l3_master,
|
|
[SLAVE_EPSS_L3_SHARED] = &epss_l3_slave,
|
|
};
|
|
|
|
static const struct qcom_osm_l3_desc osm_l3 = {
|
|
.nodes = osm_l3_nodes,
|
|
.num_nodes = ARRAY_SIZE(osm_l3_nodes),
|
|
.lut_row_size = OSM_LUT_ROW_SIZE,
|
|
.reg_freq_lut = OSM_REG_FREQ_LUT,
|
|
.reg_perf_state = OSM_REG_PERF_STATE,
|
|
};
|
|
|
|
static const struct qcom_osm_l3_desc epss_l3_perf_state = {
|
|
.nodes = epss_l3_nodes,
|
|
.num_nodes = ARRAY_SIZE(epss_l3_nodes),
|
|
.lut_row_size = EPSS_LUT_ROW_SIZE,
|
|
.reg_freq_lut = EPSS_REG_FREQ_LUT,
|
|
.reg_perf_state = EPSS_REG_PERF_STATE,
|
|
};
|
|
|
|
static const struct qcom_osm_l3_desc epss_l3_l3_vote = {
|
|
.nodes = epss_l3_nodes,
|
|
.num_nodes = ARRAY_SIZE(epss_l3_nodes),
|
|
.lut_row_size = EPSS_LUT_ROW_SIZE,
|
|
.reg_freq_lut = EPSS_REG_FREQ_LUT,
|
|
.reg_perf_state = EPSS_REG_L3_VOTE,
|
|
};
|
|
|
|
static int qcom_osm_l3_set(struct icc_node *src, struct icc_node *dst)
|
|
{
|
|
struct qcom_osm_l3_icc_provider *qp;
|
|
struct icc_provider *provider;
|
|
const struct qcom_osm_l3_node *qn;
|
|
unsigned int index;
|
|
u64 rate;
|
|
|
|
qn = src->data;
|
|
provider = src->provider;
|
|
qp = to_osm_l3_provider(provider);
|
|
|
|
rate = icc_units_to_bps(dst->peak_bw);
|
|
do_div(rate, qn->buswidth);
|
|
|
|
for (index = 0; index < qp->max_state - 1; index++) {
|
|
if (qp->lut_tables[index] >= rate)
|
|
break;
|
|
}
|
|
|
|
writel_relaxed(index, qp->base + qp->reg_perf_state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void qcom_osm_l3_remove(struct platform_device *pdev)
|
|
{
|
|
struct qcom_osm_l3_icc_provider *qp = platform_get_drvdata(pdev);
|
|
|
|
icc_provider_deregister(&qp->provider);
|
|
icc_nodes_remove(&qp->provider);
|
|
}
|
|
|
|
static int qcom_osm_l3_probe(struct platform_device *pdev)
|
|
{
|
|
u32 info, src, lval, i, prev_freq = 0, freq;
|
|
static unsigned long hw_rate, xo_rate;
|
|
struct qcom_osm_l3_icc_provider *qp;
|
|
const struct qcom_osm_l3_desc *desc;
|
|
struct icc_onecell_data *data;
|
|
struct icc_provider *provider;
|
|
const struct qcom_osm_l3_node * const *qnodes;
|
|
struct icc_node *node;
|
|
size_t num_nodes;
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
clk = clk_get(&pdev->dev, "xo");
|
|
if (IS_ERR(clk))
|
|
return PTR_ERR(clk);
|
|
|
|
xo_rate = clk_get_rate(clk);
|
|
clk_put(clk);
|
|
|
|
clk = clk_get(&pdev->dev, "alternate");
|
|
if (IS_ERR(clk))
|
|
return PTR_ERR(clk);
|
|
|
|
hw_rate = clk_get_rate(clk) / CLK_HW_DIV;
|
|
clk_put(clk);
|
|
|
|
qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL);
|
|
if (!qp)
|
|
return -ENOMEM;
|
|
|
|
qp->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(qp->base))
|
|
return PTR_ERR(qp->base);
|
|
|
|
/* HW should be in enabled state to proceed */
|
|
if (!(readl_relaxed(qp->base + REG_ENABLE) & 0x1)) {
|
|
dev_err(&pdev->dev, "error hardware not enabled\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
desc = device_get_match_data(&pdev->dev);
|
|
if (!desc)
|
|
return -EINVAL;
|
|
|
|
qp->reg_perf_state = desc->reg_perf_state;
|
|
|
|
for (i = 0; i < LUT_MAX_ENTRIES; i++) {
|
|
info = readl_relaxed(qp->base + desc->reg_freq_lut +
|
|
i * desc->lut_row_size);
|
|
src = FIELD_GET(LUT_SRC, info);
|
|
lval = FIELD_GET(LUT_L_VAL, info);
|
|
if (src)
|
|
freq = xo_rate * lval;
|
|
else
|
|
freq = hw_rate;
|
|
|
|
/* Two of the same frequencies signify end of table */
|
|
if (i > 0 && prev_freq == freq)
|
|
break;
|
|
|
|
dev_dbg(&pdev->dev, "index=%d freq=%d\n", i, freq);
|
|
|
|
qp->lut_tables[i] = freq;
|
|
prev_freq = freq;
|
|
}
|
|
qp->max_state = i;
|
|
|
|
qnodes = desc->nodes;
|
|
num_nodes = desc->num_nodes;
|
|
|
|
data = devm_kzalloc(&pdev->dev, struct_size(data, nodes, num_nodes), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
data->num_nodes = num_nodes;
|
|
|
|
provider = &qp->provider;
|
|
provider->dev = &pdev->dev;
|
|
provider->set = qcom_osm_l3_set;
|
|
provider->aggregate = icc_std_aggregate;
|
|
provider->xlate = of_icc_xlate_onecell;
|
|
provider->data = data;
|
|
|
|
icc_provider_init(provider);
|
|
|
|
/* Create nodes */
|
|
for (i = 0; i < num_nodes; i++) {
|
|
node = icc_node_create_dyn();
|
|
|
|
if (IS_ERR(node)) {
|
|
ret = PTR_ERR(node);
|
|
goto err;
|
|
}
|
|
|
|
node->name = qnodes[i]->name;
|
|
/* Cast away const and add it back in qcom_osm_l3_set() */
|
|
node->data = (void *)qnodes[i];
|
|
icc_node_add(node, provider);
|
|
|
|
data->nodes[i] = node;
|
|
}
|
|
|
|
/* Create link */
|
|
icc_link_nodes(data->nodes[MASTER_OSM_L3_APPS], &data->nodes[SLAVE_OSM_L3]);
|
|
|
|
ret = icc_provider_register(provider);
|
|
if (ret)
|
|
goto err;
|
|
|
|
platform_set_drvdata(pdev, qp);
|
|
|
|
return 0;
|
|
err:
|
|
icc_nodes_remove(provider);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id osm_l3_of_match[] = {
|
|
{ .compatible = "qcom,epss-l3", .data = &epss_l3_l3_vote },
|
|
{ .compatible = "qcom,osm-l3", .data = &osm_l3 },
|
|
{ .compatible = "qcom,sa8775p-epss-l3", .data = &epss_l3_perf_state },
|
|
{ .compatible = "qcom,sc7180-osm-l3", .data = &osm_l3 },
|
|
{ .compatible = "qcom,sc7280-epss-l3", .data = &epss_l3_perf_state },
|
|
{ .compatible = "qcom,sdm845-osm-l3", .data = &osm_l3 },
|
|
{ .compatible = "qcom,sm8150-osm-l3", .data = &osm_l3 },
|
|
{ .compatible = "qcom,sc8180x-osm-l3", .data = &osm_l3 },
|
|
{ .compatible = "qcom,sm8250-epss-l3", .data = &epss_l3_perf_state },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, osm_l3_of_match);
|
|
|
|
static struct platform_driver osm_l3_driver = {
|
|
.probe = qcom_osm_l3_probe,
|
|
.remove = qcom_osm_l3_remove,
|
|
.driver = {
|
|
.name = "osm-l3",
|
|
.of_match_table = osm_l3_of_match,
|
|
.sync_state = icc_sync_state,
|
|
},
|
|
};
|
|
module_platform_driver(osm_l3_driver);
|
|
|
|
MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver");
|
|
MODULE_LICENSE("GPL v2");
|