1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2020, The Linux Foundation. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/interconnect.h> |
7 | #include <linux/interconnect-provider.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/of_platform.h> |
11 | #include <linux/slab.h> |
12 | |
13 | #include "bcm-voter.h" |
14 | #include "icc-common.h" |
15 | #include "icc-rpmh.h" |
16 | |
17 | /** |
18 | * qcom_icc_pre_aggregate - cleans up stale values from prior icc_set |
19 | * @node: icc node to operate on |
20 | */ |
21 | void qcom_icc_pre_aggregate(struct icc_node *node) |
22 | { |
23 | size_t i; |
24 | struct qcom_icc_node *qn; |
25 | struct qcom_icc_provider *qp; |
26 | |
27 | qn = node->data; |
28 | qp = to_qcom_provider(node->provider); |
29 | |
30 | for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { |
31 | qn->sum_avg[i] = 0; |
32 | qn->max_peak[i] = 0; |
33 | } |
34 | |
35 | for (i = 0; i < qn->num_bcms; i++) |
36 | qcom_icc_bcm_voter_add(voter: qp->voter, bcm: qn->bcms[i]); |
37 | } |
38 | EXPORT_SYMBOL_GPL(qcom_icc_pre_aggregate); |
39 | |
40 | /** |
41 | * qcom_icc_aggregate - aggregate bw for buckets indicated by tag |
42 | * @node: node to aggregate |
43 | * @tag: tag to indicate which buckets to aggregate |
44 | * @avg_bw: new bw to sum aggregate |
45 | * @peak_bw: new bw to max aggregate |
46 | * @agg_avg: existing aggregate avg bw val |
47 | * @agg_peak: existing aggregate peak bw val |
48 | */ |
49 | int qcom_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, |
50 | u32 peak_bw, u32 *agg_avg, u32 *agg_peak) |
51 | { |
52 | size_t i; |
53 | struct qcom_icc_node *qn; |
54 | |
55 | qn = node->data; |
56 | |
57 | if (!tag) |
58 | tag = QCOM_ICC_TAG_ALWAYS; |
59 | |
60 | for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { |
61 | if (tag & BIT(i)) { |
62 | qn->sum_avg[i] += avg_bw; |
63 | qn->max_peak[i] = max_t(u32, qn->max_peak[i], peak_bw); |
64 | } |
65 | |
66 | if (node->init_avg || node->init_peak) { |
67 | qn->sum_avg[i] = max_t(u64, qn->sum_avg[i], node->init_avg); |
68 | qn->max_peak[i] = max_t(u64, qn->max_peak[i], node->init_peak); |
69 | } |
70 | } |
71 | |
72 | *agg_avg += avg_bw; |
73 | *agg_peak = max_t(u32, *agg_peak, peak_bw); |
74 | |
75 | return 0; |
76 | } |
77 | EXPORT_SYMBOL_GPL(qcom_icc_aggregate); |
78 | |
79 | /** |
80 | * qcom_icc_set - set the constraints based on path |
81 | * @src: source node for the path to set constraints on |
82 | * @dst: destination node for the path to set constraints on |
83 | * |
84 | * Return: 0 on success, or an error code otherwise |
85 | */ |
86 | int qcom_icc_set(struct icc_node *src, struct icc_node *dst) |
87 | { |
88 | struct qcom_icc_provider *qp; |
89 | struct icc_node *node; |
90 | |
91 | if (!src) |
92 | node = dst; |
93 | else |
94 | node = src; |
95 | |
96 | qp = to_qcom_provider(node->provider); |
97 | |
98 | qcom_icc_bcm_voter_commit(voter: qp->voter); |
99 | |
100 | return 0; |
101 | } |
102 | EXPORT_SYMBOL_GPL(qcom_icc_set); |
103 | |
104 | /** |
105 | * qcom_icc_bcm_init - populates bcm aux data and connect qnodes |
106 | * @bcm: bcm to be initialized |
107 | * @dev: associated provider device |
108 | * |
109 | * Return: 0 on success, or an error code otherwise |
110 | */ |
111 | int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev) |
112 | { |
113 | struct qcom_icc_node *qn; |
114 | const struct bcm_db *data; |
115 | size_t data_count; |
116 | int i; |
117 | |
118 | /* BCM is already initialised*/ |
119 | if (bcm->addr) |
120 | return 0; |
121 | |
122 | bcm->addr = cmd_db_read_addr(resource_id: bcm->name); |
123 | if (!bcm->addr) { |
124 | dev_err(dev, "%s could not find RPMh address\n" , |
125 | bcm->name); |
126 | return -EINVAL; |
127 | } |
128 | |
129 | data = cmd_db_read_aux_data(resource_id: bcm->name, len: &data_count); |
130 | if (IS_ERR(ptr: data)) { |
131 | dev_err(dev, "%s command db read error (%ld)\n" , |
132 | bcm->name, PTR_ERR(data)); |
133 | return PTR_ERR(ptr: data); |
134 | } |
135 | if (!data_count) { |
136 | dev_err(dev, "%s command db missing or partial aux data\n" , |
137 | bcm->name); |
138 | return -EINVAL; |
139 | } |
140 | |
141 | bcm->aux_data.unit = le32_to_cpu(data->unit); |
142 | bcm->aux_data.width = le16_to_cpu(data->width); |
143 | bcm->aux_data.vcd = data->vcd; |
144 | bcm->aux_data.reserved = data->reserved; |
145 | INIT_LIST_HEAD(list: &bcm->list); |
146 | INIT_LIST_HEAD(list: &bcm->ws_list); |
147 | |
148 | if (!bcm->vote_scale) |
149 | bcm->vote_scale = 1000; |
150 | |
151 | /* Link Qnodes to their respective BCMs */ |
152 | for (i = 0; i < bcm->num_nodes; i++) { |
153 | qn = bcm->nodes[i]; |
154 | qn->bcms[qn->num_bcms] = bcm; |
155 | qn->num_bcms++; |
156 | } |
157 | |
158 | return 0; |
159 | } |
160 | EXPORT_SYMBOL_GPL(qcom_icc_bcm_init); |
161 | |
162 | int qcom_icc_rpmh_probe(struct platform_device *pdev) |
163 | { |
164 | const struct qcom_icc_desc *desc; |
165 | struct device *dev = &pdev->dev; |
166 | struct icc_onecell_data *data; |
167 | struct icc_provider *provider; |
168 | struct qcom_icc_node * const *qnodes, *qn; |
169 | struct qcom_icc_provider *qp; |
170 | struct icc_node *node; |
171 | size_t num_nodes, i, j; |
172 | int ret; |
173 | |
174 | desc = of_device_get_match_data(dev); |
175 | if (!desc) |
176 | return -EINVAL; |
177 | |
178 | qnodes = desc->nodes; |
179 | num_nodes = desc->num_nodes; |
180 | |
181 | qp = devm_kzalloc(dev, size: sizeof(*qp), GFP_KERNEL); |
182 | if (!qp) |
183 | return -ENOMEM; |
184 | |
185 | data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes), GFP_KERNEL); |
186 | if (!data) |
187 | return -ENOMEM; |
188 | data->num_nodes = num_nodes; |
189 | |
190 | provider = &qp->provider; |
191 | provider->dev = dev; |
192 | provider->set = qcom_icc_set; |
193 | provider->pre_aggregate = qcom_icc_pre_aggregate; |
194 | provider->aggregate = qcom_icc_aggregate; |
195 | provider->xlate_extended = qcom_icc_xlate_extended; |
196 | provider->data = data; |
197 | |
198 | icc_provider_init(provider); |
199 | |
200 | qp->dev = dev; |
201 | qp->bcms = desc->bcms; |
202 | qp->num_bcms = desc->num_bcms; |
203 | |
204 | qp->voter = of_bcm_voter_get(dev: qp->dev, NULL); |
205 | if (IS_ERR(ptr: qp->voter)) |
206 | return PTR_ERR(ptr: qp->voter); |
207 | |
208 | for (i = 0; i < qp->num_bcms; i++) |
209 | qcom_icc_bcm_init(qp->bcms[i], dev); |
210 | |
211 | for (i = 0; i < num_nodes; i++) { |
212 | qn = qnodes[i]; |
213 | if (!qn) |
214 | continue; |
215 | |
216 | node = icc_node_create(id: qn->id); |
217 | if (IS_ERR(ptr: node)) { |
218 | ret = PTR_ERR(ptr: node); |
219 | goto err_remove_nodes; |
220 | } |
221 | |
222 | node->name = qn->name; |
223 | node->data = qn; |
224 | icc_node_add(node, provider); |
225 | |
226 | for (j = 0; j < qn->num_links; j++) |
227 | icc_link_create(node, dst_id: qn->links[j]); |
228 | |
229 | data->nodes[i] = node; |
230 | } |
231 | |
232 | ret = icc_provider_register(provider); |
233 | if (ret) |
234 | goto err_remove_nodes; |
235 | |
236 | platform_set_drvdata(pdev, data: qp); |
237 | |
238 | /* Populate child NoC devices if any */ |
239 | if (of_get_child_count(np: dev->of_node) > 0) { |
240 | ret = of_platform_populate(root: dev->of_node, NULL, NULL, parent: dev); |
241 | if (ret) |
242 | goto err_deregister_provider; |
243 | } |
244 | |
245 | return 0; |
246 | |
247 | err_deregister_provider: |
248 | icc_provider_deregister(provider); |
249 | err_remove_nodes: |
250 | icc_nodes_remove(provider); |
251 | |
252 | return ret; |
253 | } |
254 | EXPORT_SYMBOL_GPL(qcom_icc_rpmh_probe); |
255 | |
256 | void qcom_icc_rpmh_remove(struct platform_device *pdev) |
257 | { |
258 | struct qcom_icc_provider *qp = platform_get_drvdata(pdev); |
259 | |
260 | icc_provider_deregister(provider: &qp->provider); |
261 | icc_nodes_remove(provider: &qp->provider); |
262 | } |
263 | EXPORT_SYMBOL_GPL(qcom_icc_rpmh_remove); |
264 | |
265 | MODULE_LICENSE("GPL v2" ); |
266 | |