1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2020, The Linux Foundation. All rights reserved.
4  */
5 
6 #include <asm/div64.h>
7 #include <linux/interconnect-provider.h>
8 #include <linux/list_sort.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/platform_device.h>
12 
13 #include <soc/qcom/rpmh.h>
14 #include <soc/qcom/tcs.h>
15 
16 #include "bcm-voter.h"
17 #include "icc-rpmh.h"
18 
19 static LIST_HEAD(bcm_voters);
20 static DEFINE_MUTEX(bcm_voter_lock);
21 
22 /**
23  * struct bcm_voter - Bus Clock Manager voter
24  * @dev: reference to the device that communicates with the BCM
25  * @np: reference to the device node to match bcm voters
26  * @lock: mutex to protect commit and wake/sleep lists in the voter
27  * @commit_list: list containing bcms to be committed to hardware
28  * @ws_list: list containing bcms that have different wake/sleep votes
29  * @voter_node: list of bcm voters
30  */
31 struct bcm_voter {
32 	struct device *dev;
33 	struct device_node *np;
34 	struct mutex lock;
35 	struct list_head commit_list;
36 	struct list_head ws_list;
37 	struct list_head voter_node;
38 };
39 
40 static int cmp_vcd(void *priv, struct list_head *a, struct list_head *b)
41 {
42 	const struct qcom_icc_bcm *bcm_a =
43 			list_entry(a, struct qcom_icc_bcm, list);
44 	const struct qcom_icc_bcm *bcm_b =
45 			list_entry(b, struct qcom_icc_bcm, list);
46 
47 	if (bcm_a->aux_data.vcd < bcm_b->aux_data.vcd)
48 		return -1;
49 	else if (bcm_a->aux_data.vcd == bcm_b->aux_data.vcd)
50 		return 0;
51 	else
52 		return 1;
53 }
54 
55 static void bcm_aggregate(struct qcom_icc_bcm *bcm)
56 {
57 	size_t i, bucket;
58 	u64 agg_avg[QCOM_ICC_NUM_BUCKETS] = {0};
59 	u64 agg_peak[QCOM_ICC_NUM_BUCKETS] = {0};
60 	u64 temp;
61 
62 	for (bucket = 0; bucket < QCOM_ICC_NUM_BUCKETS; bucket++) {
63 		for (i = 0; i < bcm->num_nodes; i++) {
64 			temp = bcm->nodes[i]->sum_avg[bucket] * bcm->aux_data.width;
65 			do_div(temp, bcm->nodes[i]->buswidth * bcm->nodes[i]->channels);
66 			agg_avg[bucket] = max(agg_avg[bucket], temp);
67 
68 			temp = bcm->nodes[i]->max_peak[bucket] * bcm->aux_data.width;
69 			do_div(temp, bcm->nodes[i]->buswidth);
70 			agg_peak[bucket] = max(agg_peak[bucket], temp);
71 		}
72 
73 		temp = agg_avg[bucket] * 1000ULL;
74 		do_div(temp, bcm->aux_data.unit);
75 		bcm->vote_x[bucket] = temp;
76 
77 		temp = agg_peak[bucket] * 1000ULL;
78 		do_div(temp, bcm->aux_data.unit);
79 		bcm->vote_y[bucket] = temp;
80 	}
81 
82 	if (bcm->keepalive && bcm->vote_x[QCOM_ICC_BUCKET_AMC] == 0 &&
83 	    bcm->vote_y[QCOM_ICC_BUCKET_AMC] == 0) {
84 		bcm->vote_x[QCOM_ICC_BUCKET_AMC] = 1;
85 		bcm->vote_x[QCOM_ICC_BUCKET_WAKE] = 1;
86 		bcm->vote_y[QCOM_ICC_BUCKET_AMC] = 1;
87 		bcm->vote_y[QCOM_ICC_BUCKET_WAKE] = 1;
88 	}
89 }
90 
91 static inline void tcs_cmd_gen(struct tcs_cmd *cmd, u64 vote_x, u64 vote_y,
92 			       u32 addr, bool commit)
93 {
94 	bool valid = true;
95 
96 	if (!cmd)
97 		return;
98 
99 	memset(cmd, 0, sizeof(*cmd));
100 
101 	if (vote_x == 0 && vote_y == 0)
102 		valid = false;
103 
104 	if (vote_x > BCM_TCS_CMD_VOTE_MASK)
105 		vote_x = BCM_TCS_CMD_VOTE_MASK;
106 
107 	if (vote_y > BCM_TCS_CMD_VOTE_MASK)
108 		vote_y = BCM_TCS_CMD_VOTE_MASK;
109 
110 	cmd->addr = addr;
111 	cmd->data = BCM_TCS_CMD(commit, valid, vote_x, vote_y);
112 
113 	/*
114 	 * Set the wait for completion flag on command that need to be completed
115 	 * before the next command.
116 	 */
117 	cmd->wait = commit;
118 }
119 
120 static void tcs_list_gen(struct list_head *bcm_list, int bucket,
121 			 struct tcs_cmd tcs_list[MAX_BCMS],
122 			 int n[MAX_VCD + 1])
123 {
124 	struct qcom_icc_bcm *bcm;
125 	bool commit;
126 	size_t idx = 0, batch = 0, cur_vcd_size = 0;
127 
128 	memset(n, 0, sizeof(int) * (MAX_VCD + 1));
129 
130 	list_for_each_entry(bcm, bcm_list, list) {
131 		commit = false;
132 		cur_vcd_size++;
133 		if ((list_is_last(&bcm->list, bcm_list)) ||
134 		    bcm->aux_data.vcd != list_next_entry(bcm, list)->aux_data.vcd) {
135 			commit = true;
136 			cur_vcd_size = 0;
137 		}
138 		tcs_cmd_gen(&tcs_list[idx], bcm->vote_x[bucket],
139 			    bcm->vote_y[bucket], bcm->addr, commit);
140 		idx++;
141 		n[batch]++;
142 		/*
143 		 * Batch the BCMs in such a way that we do not split them in
144 		 * multiple payloads when they are under the same VCD. This is
145 		 * to ensure that every BCM is committed since we only set the
146 		 * commit bit on the last BCM request of every VCD.
147 		 */
148 		if (n[batch] >= MAX_RPMH_PAYLOAD) {
149 			if (!commit) {
150 				n[batch] -= cur_vcd_size;
151 				n[batch + 1] = cur_vcd_size;
152 			}
153 			batch++;
154 		}
155 	}
156 }
157 
158 /**
159  * of_bcm_voter_get - gets a bcm voter handle from DT node
160  * @dev: device pointer for the consumer device
161  * @name: name for the bcm voter device
162  *
163  * This function will match a device_node pointer for the phandle
164  * specified in the device DT and return a bcm_voter handle on success.
165  *
166  * Returns bcm_voter pointer or ERR_PTR() on error. EPROBE_DEFER is returned
167  * when matching bcm voter is yet to be found.
168  */
169 struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name)
170 {
171 	struct bcm_voter *voter = ERR_PTR(-EPROBE_DEFER);
172 	struct bcm_voter *temp;
173 	struct device_node *np, *node;
174 	int idx = 0;
175 
176 	if (!dev || !dev->of_node)
177 		return ERR_PTR(-ENODEV);
178 
179 	np = dev->of_node;
180 
181 	if (name) {
182 		idx = of_property_match_string(np, "qcom,bcm-voter-names", name);
183 		if (idx < 0)
184 			return ERR_PTR(idx);
185 	}
186 
187 	node = of_parse_phandle(np, "qcom,bcm-voters", idx);
188 
189 	mutex_lock(&bcm_voter_lock);
190 	list_for_each_entry(temp, &bcm_voters, voter_node) {
191 		if (temp->np == node) {
192 			voter = temp;
193 			break;
194 		}
195 	}
196 	mutex_unlock(&bcm_voter_lock);
197 
198 	return voter;
199 }
200 EXPORT_SYMBOL_GPL(of_bcm_voter_get);
201 
202 /**
203  * qcom_icc_bcm_voter_add - queues up the bcm nodes that require updates
204  * @voter: voter that the bcms are being added to
205  * @bcm: bcm to add to the commit and wake sleep list
206  */
207 void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm)
208 {
209 	if (!voter)
210 		return;
211 
212 	mutex_lock(&voter->lock);
213 	if (list_empty(&bcm->list))
214 		list_add_tail(&bcm->list, &voter->commit_list);
215 
216 	if (list_empty(&bcm->ws_list))
217 		list_add_tail(&bcm->ws_list, &voter->ws_list);
218 
219 	mutex_unlock(&voter->lock);
220 }
221 EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_add);
222 
223 /**
224  * qcom_icc_bcm_voter_commit - generates and commits tcs cmds based on bcms
225  * @voter: voter that needs flushing
226  *
227  * This function generates a set of AMC commands and flushes to the BCM device
228  * associated with the voter. It conditionally generate WAKE and SLEEP commands
229  * based on deltas between WAKE/SLEEP requirements. The ws_list persists
230  * through multiple commit requests and bcm nodes are removed only when the
231  * requirements for WAKE matches SLEEP.
232  *
233  * Returns 0 on success, or an appropriate error code otherwise.
234  */
235 int qcom_icc_bcm_voter_commit(struct bcm_voter *voter)
236 {
237 	struct qcom_icc_bcm *bcm;
238 	struct qcom_icc_bcm *bcm_tmp;
239 	int commit_idx[MAX_VCD + 1];
240 	struct tcs_cmd cmds[MAX_BCMS];
241 	int ret = 0;
242 
243 	if (!voter)
244 		return 0;
245 
246 	mutex_lock(&voter->lock);
247 	list_for_each_entry(bcm, &voter->commit_list, list)
248 		bcm_aggregate(bcm);
249 
250 	/*
251 	 * Pre sort the BCMs based on VCD for ease of generating a command list
252 	 * that groups the BCMs with the same VCD together. VCDs are numbered
253 	 * with lowest being the most expensive time wise, ensuring that
254 	 * those commands are being sent the earliest in the queue. This needs
255 	 * to be sorted every commit since we can't guarantee the order in which
256 	 * the BCMs are added to the list.
257 	 */
258 	list_sort(NULL, &voter->commit_list, cmp_vcd);
259 
260 	/*
261 	 * Construct the command list based on a pre ordered list of BCMs
262 	 * based on VCD.
263 	 */
264 	tcs_list_gen(&voter->commit_list, QCOM_ICC_BUCKET_AMC, cmds, commit_idx);
265 
266 	if (!commit_idx[0])
267 		goto out;
268 
269 	rpmh_invalidate(voter->dev);
270 
271 	ret = rpmh_write_batch(voter->dev, RPMH_ACTIVE_ONLY_STATE,
272 			       cmds, commit_idx);
273 	if (ret) {
274 		pr_err("Error sending AMC RPMH requests (%d)\n", ret);
275 		goto out;
276 	}
277 
278 	list_for_each_entry_safe(bcm, bcm_tmp, &voter->commit_list, list)
279 		list_del_init(&bcm->list);
280 
281 	list_for_each_entry_safe(bcm, bcm_tmp, &voter->ws_list, ws_list) {
282 		/*
283 		 * Only generate WAKE and SLEEP commands if a resource's
284 		 * requirements change as the execution environment transitions
285 		 * between different power states.
286 		 */
287 		if (bcm->vote_x[QCOM_ICC_BUCKET_WAKE] !=
288 		    bcm->vote_x[QCOM_ICC_BUCKET_SLEEP] ||
289 		    bcm->vote_y[QCOM_ICC_BUCKET_WAKE] !=
290 		    bcm->vote_y[QCOM_ICC_BUCKET_SLEEP])
291 			list_add_tail(&bcm->list, &voter->commit_list);
292 		else
293 			list_del_init(&bcm->ws_list);
294 	}
295 
296 	if (list_empty(&voter->commit_list))
297 		goto out;
298 
299 	list_sort(NULL, &voter->commit_list, cmp_vcd);
300 
301 	tcs_list_gen(&voter->commit_list, QCOM_ICC_BUCKET_WAKE, cmds, commit_idx);
302 
303 	ret = rpmh_write_batch(voter->dev, RPMH_WAKE_ONLY_STATE, cmds, commit_idx);
304 	if (ret) {
305 		pr_err("Error sending WAKE RPMH requests (%d)\n", ret);
306 		goto out;
307 	}
308 
309 	tcs_list_gen(&voter->commit_list, QCOM_ICC_BUCKET_SLEEP, cmds, commit_idx);
310 
311 	ret = rpmh_write_batch(voter->dev, RPMH_SLEEP_STATE, cmds, commit_idx);
312 	if (ret) {
313 		pr_err("Error sending SLEEP RPMH requests (%d)\n", ret);
314 		goto out;
315 	}
316 
317 out:
318 	list_for_each_entry_safe(bcm, bcm_tmp, &voter->commit_list, list)
319 		list_del_init(&bcm->list);
320 
321 	mutex_unlock(&voter->lock);
322 	return ret;
323 }
324 EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_commit);
325 
326 static int qcom_icc_bcm_voter_probe(struct platform_device *pdev)
327 {
328 	struct bcm_voter *voter;
329 
330 	voter = devm_kzalloc(&pdev->dev, sizeof(*voter), GFP_KERNEL);
331 	if (!voter)
332 		return -ENOMEM;
333 
334 	voter->dev = &pdev->dev;
335 	voter->np = pdev->dev.of_node;
336 	mutex_init(&voter->lock);
337 	INIT_LIST_HEAD(&voter->commit_list);
338 	INIT_LIST_HEAD(&voter->ws_list);
339 
340 	mutex_lock(&bcm_voter_lock);
341 	list_add_tail(&voter->voter_node, &bcm_voters);
342 	mutex_unlock(&bcm_voter_lock);
343 
344 	return 0;
345 }
346 
347 static const struct of_device_id bcm_voter_of_match[] = {
348 	{ .compatible = "qcom,bcm-voter" },
349 	{ }
350 };
351 
352 static struct platform_driver qcom_icc_bcm_voter_driver = {
353 	.probe = qcom_icc_bcm_voter_probe,
354 	.driver = {
355 		.name		= "bcm_voter",
356 		.of_match_table = bcm_voter_of_match,
357 	},
358 };
359 module_platform_driver(qcom_icc_bcm_voter_driver);
360 
361 MODULE_AUTHOR("David Dai <daidavid1@codeaurora.org>");
362 MODULE_DESCRIPTION("Qualcomm BCM Voter interconnect driver");
363 MODULE_LICENSE("GPL v2");
364