| // 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); |