Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

interconnect: qcom: Add OSM L3 interconnect provider support

On some Qualcomm SoCs, Operating State Manager (OSM) controls the
resources of scaling L3 caches. Add a driver to handle bandwidth
requests to OSM L3 from CPU on SDM845 SoCs.

Signed-off-by: Sibi Sankar <sibis@codeaurora.org>
Reviewed-by: Evan Green <evgreen@chromium.org>
Link: https://lore.kernel.org/r/20200227105632.15041-4-sibis@codeaurora.org
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>

authored by

Sibi Sankar and committed by
Georgi Djakov
5bc9900a 7a077f7f

+272
+7
drivers/interconnect/qcom/Kconfig
··· 26 26 This is a driver for the Qualcomm Network-on-Chip on msm8974-based 27 27 platforms. 28 28 29 + config INTERCONNECT_QCOM_OSM_L3 30 + tristate "Qualcomm OSM L3 interconnect driver" 31 + depends on INTERCONNECT_QCOM || COMPILE_TEST 32 + help 33 + Say y here to support the Operating State Manager (OSM) interconnect 34 + driver which controls the scaling of L3 caches on Qualcomm SoCs. 35 + 29 36 config INTERCONNECT_QCOM_QCS404 30 37 tristate "Qualcomm QCS404 interconnect driver" 31 38 depends on INTERCONNECT_QCOM
+2
drivers/interconnect/qcom/Makefile
··· 3 3 icc-bcm-voter-objs := bcm-voter.o 4 4 qnoc-msm8916-objs := msm8916.o 5 5 qnoc-msm8974-objs := msm8974.o 6 + icc-osm-l3-objs := osm-l3.o 6 7 qnoc-qcs404-objs := qcs404.o 7 8 icc-rpmh-obj := icc-rpmh.o 8 9 qnoc-sc7180-objs := sc7180.o ··· 13 12 obj-$(CONFIG_INTERCONNECT_QCOM_BCM_VOTER) += icc-bcm-voter.o 14 13 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o 15 14 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o 15 + obj-$(CONFIG_INTERCONNECT_QCOM_OSM_L3) += icc-osm-l3.o 16 16 obj-$(CONFIG_INTERCONNECT_QCOM_QCS404) += qnoc-qcs404.o 17 17 obj-$(CONFIG_INTERCONNECT_QCOM_RPMH) += icc-rpmh.o 18 18 obj-$(CONFIG_INTERCONNECT_QCOM_SC7180) += qnoc-sc7180.o
+261
drivers/interconnect/qcom/osm-l3.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (c) 2020, The Linux Foundation. All rights reserved. 4 + */ 5 + 6 + #include <linux/bitfield.h> 7 + #include <linux/clk.h> 8 + #include <linux/interconnect-provider.h> 9 + #include <linux/io.h> 10 + #include <linux/kernel.h> 11 + #include <linux/module.h> 12 + #include <linux/of_device.h> 13 + #include <linux/platform_device.h> 14 + 15 + #include <dt-bindings/interconnect/qcom,osm-l3.h> 16 + 17 + #include "sdm845.h" 18 + 19 + #define LUT_MAX_ENTRIES 40U 20 + #define LUT_SRC GENMASK(31, 30) 21 + #define LUT_L_VAL GENMASK(7, 0) 22 + #define LUT_ROW_SIZE 32 23 + #define CLK_HW_DIV 2 24 + 25 + /* Register offsets */ 26 + #define REG_ENABLE 0x0 27 + #define REG_FREQ_LUT 0x110 28 + #define REG_PERF_STATE 0x920 29 + 30 + #define OSM_L3_MAX_LINKS 1 31 + 32 + #define to_qcom_provider(_provider) \ 33 + container_of(_provider, struct qcom_osm_l3_icc_provider, provider) 34 + 35 + struct qcom_osm_l3_icc_provider { 36 + void __iomem *base; 37 + unsigned int max_state; 38 + unsigned long lut_tables[LUT_MAX_ENTRIES]; 39 + struct icc_provider provider; 40 + }; 41 + 42 + /** 43 + * struct qcom_icc_node - Qualcomm specific interconnect nodes 44 + * @name: the node name used in debugfs 45 + * @links: an array of nodes where we can go next while traversing 46 + * @id: a unique node identifier 47 + * @num_links: the total number of @links 48 + * @buswidth: width of the interconnect between a node and the bus 49 + */ 50 + struct qcom_icc_node { 51 + const char *name; 52 + u16 links[OSM_L3_MAX_LINKS]; 53 + u16 id; 54 + u16 num_links; 55 + u16 buswidth; 56 + }; 57 + 58 + struct qcom_icc_desc { 59 + struct qcom_icc_node **nodes; 60 + size_t num_nodes; 61 + }; 62 + 63 + #define DEFINE_QNODE(_name, _id, _buswidth, ...) \ 64 + static struct qcom_icc_node _name = { \ 65 + .name = #_name, \ 66 + .id = _id, \ 67 + .buswidth = _buswidth, \ 68 + .num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })), \ 69 + .links = { __VA_ARGS__ }, \ 70 + } 71 + 72 + DEFINE_QNODE(sdm845_osm_apps_l3, SDM845_MASTER_OSM_L3_APPS, 16, SDM845_SLAVE_OSM_L3); 73 + DEFINE_QNODE(sdm845_osm_l3, SDM845_SLAVE_OSM_L3, 16); 74 + 75 + static struct qcom_icc_node *sdm845_osm_l3_nodes[] = { 76 + [MASTER_OSM_L3_APPS] = &sdm845_osm_apps_l3, 77 + [SLAVE_OSM_L3] = &sdm845_osm_l3, 78 + }; 79 + 80 + const static struct qcom_icc_desc sdm845_icc_osm_l3 = { 81 + .nodes = sdm845_osm_l3_nodes, 82 + .num_nodes = ARRAY_SIZE(sdm845_osm_l3_nodes), 83 + }; 84 + 85 + static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) 86 + { 87 + struct qcom_osm_l3_icc_provider *qp; 88 + struct icc_provider *provider; 89 + struct qcom_icc_node *qn; 90 + struct icc_node *n; 91 + unsigned int index; 92 + u32 agg_peak = 0; 93 + u32 agg_avg = 0; 94 + u64 rate; 95 + 96 + qn = src->data; 97 + provider = src->provider; 98 + qp = to_qcom_provider(provider); 99 + 100 + list_for_each_entry(n, &provider->nodes, node_list) 101 + provider->aggregate(n, 0, n->avg_bw, n->peak_bw, 102 + &agg_avg, &agg_peak); 103 + 104 + rate = max(agg_avg, agg_peak); 105 + rate = icc_units_to_bps(rate); 106 + do_div(rate, qn->buswidth); 107 + 108 + for (index = 0; index < qp->max_state - 1; index++) { 109 + if (qp->lut_tables[index] >= rate) 110 + break; 111 + } 112 + 113 + writel_relaxed(index, qp->base + REG_PERF_STATE); 114 + 115 + return 0; 116 + } 117 + 118 + static int qcom_osm_l3_remove(struct platform_device *pdev) 119 + { 120 + struct qcom_osm_l3_icc_provider *qp = platform_get_drvdata(pdev); 121 + 122 + icc_nodes_remove(&qp->provider); 123 + return icc_provider_del(&qp->provider); 124 + } 125 + 126 + static int qcom_osm_l3_probe(struct platform_device *pdev) 127 + { 128 + u32 info, src, lval, i, prev_freq = 0, freq; 129 + static unsigned long hw_rate, xo_rate; 130 + struct qcom_osm_l3_icc_provider *qp; 131 + const struct qcom_icc_desc *desc; 132 + struct icc_onecell_data *data; 133 + struct icc_provider *provider; 134 + struct qcom_icc_node **qnodes; 135 + struct icc_node *node; 136 + size_t num_nodes; 137 + struct clk *clk; 138 + int ret; 139 + 140 + clk = clk_get(&pdev->dev, "xo"); 141 + if (IS_ERR(clk)) 142 + return PTR_ERR(clk); 143 + 144 + xo_rate = clk_get_rate(clk); 145 + clk_put(clk); 146 + 147 + clk = clk_get(&pdev->dev, "alternate"); 148 + if (IS_ERR(clk)) 149 + return PTR_ERR(clk); 150 + 151 + hw_rate = clk_get_rate(clk) / CLK_HW_DIV; 152 + clk_put(clk); 153 + 154 + qp = devm_kzalloc(&pdev->dev, sizeof(*qp), GFP_KERNEL); 155 + if (!qp) 156 + return -ENOMEM; 157 + 158 + qp->base = devm_platform_ioremap_resource(pdev, 0); 159 + if (IS_ERR(qp->base)) 160 + return PTR_ERR(qp->base); 161 + 162 + /* HW should be in enabled state to proceed */ 163 + if (!(readl_relaxed(qp->base + REG_ENABLE) & 0x1)) { 164 + dev_err(&pdev->dev, "error hardware not enabled\n"); 165 + return -ENODEV; 166 + } 167 + 168 + for (i = 0; i < LUT_MAX_ENTRIES; i++) { 169 + info = readl_relaxed(qp->base + REG_FREQ_LUT + 170 + i * LUT_ROW_SIZE); 171 + src = FIELD_GET(LUT_SRC, info); 172 + lval = FIELD_GET(LUT_L_VAL, info); 173 + if (src) 174 + freq = xo_rate * lval; 175 + else 176 + freq = hw_rate; 177 + 178 + /* Two of the same frequencies signify end of table */ 179 + if (i > 0 && prev_freq == freq) 180 + break; 181 + 182 + dev_dbg(&pdev->dev, "index=%d freq=%d\n", i, freq); 183 + 184 + qp->lut_tables[i] = freq; 185 + prev_freq = freq; 186 + } 187 + qp->max_state = i; 188 + 189 + desc = device_get_match_data(&pdev->dev); 190 + if (!desc) 191 + return -EINVAL; 192 + 193 + qnodes = desc->nodes; 194 + num_nodes = desc->num_nodes; 195 + 196 + data = devm_kcalloc(&pdev->dev, num_nodes, sizeof(*node), GFP_KERNEL); 197 + if (!data) 198 + return -ENOMEM; 199 + 200 + provider = &qp->provider; 201 + provider->dev = &pdev->dev; 202 + provider->set = qcom_icc_set; 203 + provider->aggregate = icc_std_aggregate; 204 + provider->xlate = of_icc_xlate_onecell; 205 + INIT_LIST_HEAD(&provider->nodes); 206 + provider->data = data; 207 + 208 + ret = icc_provider_add(provider); 209 + if (ret) { 210 + dev_err(&pdev->dev, "error adding interconnect provider\n"); 211 + return ret; 212 + } 213 + 214 + for (i = 0; i < num_nodes; i++) { 215 + size_t j; 216 + 217 + node = icc_node_create(qnodes[i]->id); 218 + if (IS_ERR(node)) { 219 + ret = PTR_ERR(node); 220 + goto err; 221 + } 222 + 223 + node->name = qnodes[i]->name; 224 + node->data = qnodes[i]; 225 + icc_node_add(node, provider); 226 + 227 + for (j = 0; j < qnodes[i]->num_links; j++) 228 + icc_link_create(node, qnodes[i]->links[j]); 229 + 230 + data->nodes[i] = node; 231 + } 232 + data->num_nodes = num_nodes; 233 + 234 + platform_set_drvdata(pdev, qp); 235 + 236 + return 0; 237 + err: 238 + icc_nodes_remove(provider); 239 + icc_provider_del(provider); 240 + 241 + return ret; 242 + } 243 + 244 + static const struct of_device_id osm_l3_of_match[] = { 245 + { .compatible = "qcom,sdm845-osm-l3", .data = &sdm845_icc_osm_l3 }, 246 + { } 247 + }; 248 + MODULE_DEVICE_TABLE(of, osm_l3_of_match); 249 + 250 + static struct platform_driver osm_l3_driver = { 251 + .probe = qcom_osm_l3_probe, 252 + .remove = qcom_osm_l3_remove, 253 + .driver = { 254 + .name = "osm-l3", 255 + .of_match_table = osm_l3_of_match, 256 + }, 257 + }; 258 + module_platform_driver(osm_l3_driver); 259 + 260 + MODULE_DESCRIPTION("Qualcomm OSM L3 interconnect driver"); 261 + MODULE_LICENSE("GPL v2");
+2
drivers/interconnect/qcom/sdm845.h
··· 136 136 #define SDM845_SLAVE_SERVICE_SNOC 128 137 137 #define SDM845_SLAVE_QDSS_STM 129 138 138 #define SDM845_SLAVE_TCU 130 139 + #define SDM845_MASTER_OSM_L3_APPS 131 140 + #define SDM845_SLAVE_OSM_L3 132 139 141 140 142 #endif /* __DRIVERS_INTERCONNECT_QCOM_SDM845_H__ */