0788f4d575
SDM660 driver expects to have QoS registers at the beginning of NoC address space (sdm660 platform shifts NoC base address). Add support for using QoS register offset, so that other platforms do not have to change existing device trees. Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org> Tested-by: Shawn Guo <shawn.guo@linaro.org> Link: https://lore.kernel.org/r/20210903232421.1384199-6-dmitry.baryshkov@linaro.org Signed-off-by: Georgi Djakov <djakov@kernel.org>
399 lines
9.2 KiB
C
399 lines
9.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020 Linaro Ltd
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interconnect-provider.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "smd-rpm.h"
|
|
#include "icc-rpm.h"
|
|
|
|
/* BIMC QoS */
|
|
#define M_BKE_REG_BASE(n) (0x300 + (0x4000 * n))
|
|
#define M_BKE_EN_ADDR(n) (M_BKE_REG_BASE(n))
|
|
#define M_BKE_HEALTH_CFG_ADDR(i, n) (M_BKE_REG_BASE(n) + 0x40 + (0x4 * i))
|
|
|
|
#define M_BKE_HEALTH_CFG_LIMITCMDS_MASK 0x80000000
|
|
#define M_BKE_HEALTH_CFG_AREQPRIO_MASK 0x300
|
|
#define M_BKE_HEALTH_CFG_PRIOLVL_MASK 0x3
|
|
#define M_BKE_HEALTH_CFG_AREQPRIO_SHIFT 0x8
|
|
#define M_BKE_HEALTH_CFG_LIMITCMDS_SHIFT 0x1f
|
|
|
|
#define M_BKE_EN_EN_BMASK 0x1
|
|
|
|
/* NoC QoS */
|
|
#define NOC_QOS_PRIORITYn_ADDR(n) (0x8 + (n * 0x1000))
|
|
#define NOC_QOS_PRIORITY_P1_MASK 0xc
|
|
#define NOC_QOS_PRIORITY_P0_MASK 0x3
|
|
#define NOC_QOS_PRIORITY_P1_SHIFT 0x2
|
|
|
|
#define NOC_QOS_MODEn_ADDR(n) (0xc + (n * 0x1000))
|
|
#define NOC_QOS_MODEn_MASK 0x3
|
|
|
|
static int qcom_icc_bimc_set_qos_health(struct qcom_icc_provider *qp,
|
|
struct qcom_icc_qos *qos,
|
|
int regnum)
|
|
{
|
|
u32 val;
|
|
u32 mask;
|
|
|
|
val = qos->prio_level;
|
|
mask = M_BKE_HEALTH_CFG_PRIOLVL_MASK;
|
|
|
|
val |= qos->areq_prio << M_BKE_HEALTH_CFG_AREQPRIO_SHIFT;
|
|
mask |= M_BKE_HEALTH_CFG_AREQPRIO_MASK;
|
|
|
|
/* LIMITCMDS is not present on M_BKE_HEALTH_3 */
|
|
if (regnum != 3) {
|
|
val |= qos->limit_commands << M_BKE_HEALTH_CFG_LIMITCMDS_SHIFT;
|
|
mask |= M_BKE_HEALTH_CFG_LIMITCMDS_MASK;
|
|
}
|
|
|
|
return regmap_update_bits(qp->regmap,
|
|
qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->qos_port),
|
|
mask, val);
|
|
}
|
|
|
|
static int qcom_icc_set_bimc_qos(struct icc_node *src, u64 max_bw)
|
|
{
|
|
struct qcom_icc_provider *qp;
|
|
struct qcom_icc_node *qn;
|
|
struct icc_provider *provider;
|
|
u32 mode = NOC_QOS_MODE_BYPASS;
|
|
u32 val = 0;
|
|
int i, rc = 0;
|
|
|
|
qn = src->data;
|
|
provider = src->provider;
|
|
qp = to_qcom_provider(provider);
|
|
|
|
if (qn->qos.qos_mode != -1)
|
|
mode = qn->qos.qos_mode;
|
|
|
|
/* QoS Priority: The QoS Health parameters are getting considered
|
|
* only if we are NOT in Bypass Mode.
|
|
*/
|
|
if (mode != NOC_QOS_MODE_BYPASS) {
|
|
for (i = 3; i >= 0; i--) {
|
|
rc = qcom_icc_bimc_set_qos_health(qp,
|
|
&qn->qos, i);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* Set BKE_EN to 1 when Fixed, Regulator or Limiter Mode */
|
|
val = 1;
|
|
}
|
|
|
|
return regmap_update_bits(qp->regmap,
|
|
qp->qos_offset + M_BKE_EN_ADDR(qn->qos.qos_port),
|
|
M_BKE_EN_EN_BMASK, val);
|
|
}
|
|
|
|
static int qcom_icc_noc_set_qos_priority(struct qcom_icc_provider *qp,
|
|
struct qcom_icc_qos *qos)
|
|
{
|
|
u32 val;
|
|
int rc;
|
|
|
|
/* Must be updated one at a time, P1 first, P0 last */
|
|
val = qos->areq_prio << NOC_QOS_PRIORITY_P1_SHIFT;
|
|
rc = regmap_update_bits(qp->regmap,
|
|
qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->qos_port),
|
|
NOC_QOS_PRIORITY_P1_MASK, val);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return regmap_update_bits(qp->regmap,
|
|
qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->qos_port),
|
|
NOC_QOS_PRIORITY_P0_MASK, qos->prio_level);
|
|
}
|
|
|
|
static int qcom_icc_set_noc_qos(struct icc_node *src, u64 max_bw)
|
|
{
|
|
struct qcom_icc_provider *qp;
|
|
struct qcom_icc_node *qn;
|
|
struct icc_provider *provider;
|
|
u32 mode = NOC_QOS_MODE_BYPASS;
|
|
int rc = 0;
|
|
|
|
qn = src->data;
|
|
provider = src->provider;
|
|
qp = to_qcom_provider(provider);
|
|
|
|
if (qn->qos.qos_port < 0) {
|
|
dev_dbg(src->provider->dev,
|
|
"NoC QoS: Skipping %s: vote aggregated on parent.\n",
|
|
qn->name);
|
|
return 0;
|
|
}
|
|
|
|
if (qn->qos.qos_mode != -1)
|
|
mode = qn->qos.qos_mode;
|
|
|
|
if (mode == NOC_QOS_MODE_FIXED) {
|
|
dev_dbg(src->provider->dev, "NoC QoS: %s: Set Fixed mode\n",
|
|
qn->name);
|
|
rc = qcom_icc_noc_set_qos_priority(qp, &qn->qos);
|
|
if (rc)
|
|
return rc;
|
|
} else if (mode == NOC_QOS_MODE_BYPASS) {
|
|
dev_dbg(src->provider->dev, "NoC QoS: %s: Set Bypass mode\n",
|
|
qn->name);
|
|
}
|
|
|
|
return regmap_update_bits(qp->regmap,
|
|
qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.qos_port),
|
|
NOC_QOS_MODEn_MASK, mode);
|
|
}
|
|
|
|
static int qcom_icc_qos_set(struct icc_node *node, u64 sum_bw)
|
|
{
|
|
struct qcom_icc_provider *qp = to_qcom_provider(node->provider);
|
|
struct qcom_icc_node *qn = node->data;
|
|
|
|
dev_dbg(node->provider->dev, "Setting QoS for %s\n", qn->name);
|
|
|
|
if (qp->is_bimc_node)
|
|
return qcom_icc_set_bimc_qos(node, sum_bw);
|
|
|
|
return qcom_icc_set_noc_qos(node, sum_bw);
|
|
}
|
|
|
|
static int qcom_icc_rpm_set(int mas_rpm_id, int slv_rpm_id, u64 sum_bw)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (mas_rpm_id != -1) {
|
|
ret = qcom_icc_rpm_smd_send(QCOM_SMD_RPM_ACTIVE_STATE,
|
|
RPM_BUS_MASTER_REQ,
|
|
mas_rpm_id,
|
|
sum_bw);
|
|
if (ret) {
|
|
pr_err("qcom_icc_rpm_smd_send mas %d error %d\n",
|
|
mas_rpm_id, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (slv_rpm_id != -1) {
|
|
ret = qcom_icc_rpm_smd_send(QCOM_SMD_RPM_ACTIVE_STATE,
|
|
RPM_BUS_SLAVE_REQ,
|
|
slv_rpm_id,
|
|
sum_bw);
|
|
if (ret) {
|
|
pr_err("qcom_icc_rpm_smd_send slv %d error %d\n",
|
|
slv_rpm_id, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_icc_set(struct icc_node *src, struct icc_node *dst)
|
|
{
|
|
struct qcom_icc_provider *qp;
|
|
struct qcom_icc_node *qn;
|
|
struct icc_provider *provider;
|
|
struct icc_node *n;
|
|
u64 sum_bw;
|
|
u64 max_peak_bw;
|
|
u64 rate;
|
|
u32 agg_avg = 0;
|
|
u32 agg_peak = 0;
|
|
int ret, i;
|
|
|
|
qn = src->data;
|
|
provider = src->provider;
|
|
qp = to_qcom_provider(provider);
|
|
|
|
list_for_each_entry(n, &provider->nodes, node_list)
|
|
provider->aggregate(n, 0, n->avg_bw, n->peak_bw,
|
|
&agg_avg, &agg_peak);
|
|
|
|
sum_bw = icc_units_to_bps(agg_avg);
|
|
max_peak_bw = icc_units_to_bps(agg_peak);
|
|
|
|
if (!qn->qos.ap_owned) {
|
|
/* send bandwidth request message to the RPM processor */
|
|
ret = qcom_icc_rpm_set(qn->mas_rpm_id, qn->slv_rpm_id, sum_bw);
|
|
if (ret)
|
|
return ret;
|
|
} else if (qn->qos.qos_mode != -1) {
|
|
/* set bandwidth directly from the AP */
|
|
ret = qcom_icc_qos_set(src, sum_bw);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
rate = max(sum_bw, max_peak_bw);
|
|
|
|
do_div(rate, qn->buswidth);
|
|
|
|
if (qn->rate == rate)
|
|
return 0;
|
|
|
|
for (i = 0; i < qp->num_clks; i++) {
|
|
ret = clk_set_rate(qp->bus_clks[i].clk, rate);
|
|
if (ret) {
|
|
pr_err("%s clk_set_rate error: %d\n",
|
|
qp->bus_clks[i].id, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
qn->rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char * const bus_clocks[] = {
|
|
"bus", "bus_a",
|
|
};
|
|
|
|
int qnoc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
const struct qcom_icc_desc *desc;
|
|
struct icc_onecell_data *data;
|
|
struct icc_provider *provider;
|
|
struct qcom_icc_node **qnodes;
|
|
struct qcom_icc_provider *qp;
|
|
struct icc_node *node;
|
|
size_t num_nodes, i;
|
|
const char * const *cds;
|
|
int cd_num;
|
|
int ret;
|
|
|
|
/* wait for the RPM proxy */
|
|
if (!qcom_icc_rpm_smd_available())
|
|
return -EPROBE_DEFER;
|
|
|
|
desc = of_device_get_match_data(dev);
|
|
if (!desc)
|
|
return -EINVAL;
|
|
|
|
qnodes = desc->nodes;
|
|
num_nodes = desc->num_nodes;
|
|
|
|
if (desc->num_clocks) {
|
|
cds = desc->clocks;
|
|
cd_num = desc->num_clocks;
|
|
} else {
|
|
cds = bus_clocks;
|
|
cd_num = ARRAY_SIZE(bus_clocks);
|
|
}
|
|
|
|
qp = devm_kzalloc(dev, struct_size(qp, bus_clks, cd_num), GFP_KERNEL);
|
|
if (!qp)
|
|
return -ENOMEM;
|
|
|
|
data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes),
|
|
GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < cd_num; i++)
|
|
qp->bus_clks[i].id = cds[i];
|
|
qp->num_clks = cd_num;
|
|
|
|
qp->is_bimc_node = desc->is_bimc_node;
|
|
qp->qos_offset = desc->qos_offset;
|
|
|
|
if (desc->regmap_cfg) {
|
|
struct resource *res;
|
|
void __iomem *mmio;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res)
|
|
return -ENODEV;
|
|
|
|
mmio = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR(mmio)) {
|
|
dev_err(dev, "Cannot ioremap interconnect bus resource\n");
|
|
return PTR_ERR(mmio);
|
|
}
|
|
|
|
qp->regmap = devm_regmap_init_mmio(dev, mmio, desc->regmap_cfg);
|
|
if (IS_ERR(qp->regmap)) {
|
|
dev_err(dev, "Cannot regmap interconnect bus resource\n");
|
|
return PTR_ERR(qp->regmap);
|
|
}
|
|
}
|
|
|
|
ret = devm_clk_bulk_get(dev, qp->num_clks, qp->bus_clks);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_bulk_prepare_enable(qp->num_clks, qp->bus_clks);
|
|
if (ret)
|
|
return ret;
|
|
|
|
provider = &qp->provider;
|
|
INIT_LIST_HEAD(&provider->nodes);
|
|
provider->dev = dev;
|
|
provider->set = qcom_icc_set;
|
|
provider->aggregate = icc_std_aggregate;
|
|
provider->xlate = of_icc_xlate_onecell;
|
|
provider->data = data;
|
|
|
|
ret = icc_provider_add(provider);
|
|
if (ret) {
|
|
dev_err(dev, "error adding interconnect provider: %d\n", ret);
|
|
clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < num_nodes; i++) {
|
|
size_t j;
|
|
|
|
node = icc_node_create(qnodes[i]->id);
|
|
if (IS_ERR(node)) {
|
|
ret = PTR_ERR(node);
|
|
goto err;
|
|
}
|
|
|
|
node->name = qnodes[i]->name;
|
|
node->data = qnodes[i];
|
|
icc_node_add(node, provider);
|
|
|
|
for (j = 0; j < qnodes[i]->num_links; j++)
|
|
icc_link_create(node, qnodes[i]->links[j]);
|
|
|
|
data->nodes[i] = node;
|
|
}
|
|
data->num_nodes = num_nodes;
|
|
|
|
platform_set_drvdata(pdev, qp);
|
|
|
|
return 0;
|
|
err:
|
|
icc_nodes_remove(provider);
|
|
clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
|
|
icc_provider_del(provider);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(qnoc_probe);
|
|
|
|
int qnoc_remove(struct platform_device *pdev)
|
|
{
|
|
struct qcom_icc_provider *qp = platform_get_drvdata(pdev);
|
|
|
|
icc_nodes_remove(&qp->provider);
|
|
clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
|
|
return icc_provider_del(&qp->provider);
|
|
}
|
|
EXPORT_SYMBOL(qnoc_remove);
|