diff options
author | Georgi Djakov <georgi.djakov@linaro.org> | 2019-01-16 11:10:58 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-01-22 07:37:25 -0500 |
commit | 87e3031b6fbd83ea83adf1bf9602bcce313ee787 (patch) | |
tree | a4c5607d1858e2459239003d3a205fb57d7bf056 /drivers/interconnect | |
parent | 06b3773b5038a618c578f823d5622481e96c0b56 (diff) |
interconnect: Allow endpoints translation via DT
Currently we support only platform data for specifying the interconnect
endpoints. As now the endpoints are hard-coded into the consumer driver
this may lead to complications when a single driver is used by multiple
SoCs, which may have different interconnect topology.
To avoid cluttering the consumer drivers, introduce a translation function
to help us get the board specific interconnect data from device-tree.
Reviewed-by: Evan Green <evgreen@chromium.org>
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/interconnect')
-rw-r--r-- | drivers/interconnect/core.c | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c index 2b937b4f43c4..a8c2bd35197f 100644 --- a/drivers/interconnect/core.c +++ b/drivers/interconnect/core.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include <linux/module.h> | 15 | #include <linux/module.h> |
16 | #include <linux/mutex.h> | 16 | #include <linux/mutex.h> |
17 | #include <linux/slab.h> | 17 | #include <linux/slab.h> |
18 | #include <linux/of.h> | ||
18 | #include <linux/overflow.h> | 19 | #include <linux/overflow.h> |
19 | 20 | ||
20 | static DEFINE_IDR(icc_idr); | 21 | static DEFINE_IDR(icc_idr); |
@@ -194,6 +195,152 @@ out: | |||
194 | return ret; | 195 | return ret; |
195 | } | 196 | } |
196 | 197 | ||
198 | /* of_icc_xlate_onecell() - Translate function using a single index. | ||
199 | * @spec: OF phandle args to map into an interconnect node. | ||
200 | * @data: private data (pointer to struct icc_onecell_data) | ||
201 | * | ||
202 | * This is a generic translate function that can be used to model simple | ||
203 | * interconnect providers that have one device tree node and provide | ||
204 | * multiple interconnect nodes. A single cell is used as an index into | ||
205 | * an array of icc nodes specified in the icc_onecell_data struct when | ||
206 | * registering the provider. | ||
207 | */ | ||
208 | struct icc_node *of_icc_xlate_onecell(struct of_phandle_args *spec, | ||
209 | void *data) | ||
210 | { | ||
211 | struct icc_onecell_data *icc_data = data; | ||
212 | unsigned int idx = spec->args[0]; | ||
213 | |||
214 | if (idx >= icc_data->num_nodes) { | ||
215 | pr_err("%s: invalid index %u\n", __func__, idx); | ||
216 | return ERR_PTR(-EINVAL); | ||
217 | } | ||
218 | |||
219 | return icc_data->nodes[idx]; | ||
220 | } | ||
221 | EXPORT_SYMBOL_GPL(of_icc_xlate_onecell); | ||
222 | |||
223 | /** | ||
224 | * of_icc_get_from_provider() - Look-up interconnect node | ||
225 | * @spec: OF phandle args to use for look-up | ||
226 | * | ||
227 | * Looks for interconnect provider under the node specified by @spec and if | ||
228 | * found, uses xlate function of the provider to map phandle args to node. | ||
229 | * | ||
230 | * Returns a valid pointer to struct icc_node on success or ERR_PTR() | ||
231 | * on failure. | ||
232 | */ | ||
233 | static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec) | ||
234 | { | ||
235 | struct icc_node *node = ERR_PTR(-EPROBE_DEFER); | ||
236 | struct icc_provider *provider; | ||
237 | |||
238 | if (!spec || spec->args_count != 1) | ||
239 | return ERR_PTR(-EINVAL); | ||
240 | |||
241 | mutex_lock(&icc_lock); | ||
242 | list_for_each_entry(provider, &icc_providers, provider_list) { | ||
243 | if (provider->dev->of_node == spec->np) | ||
244 | node = provider->xlate(spec, provider->data); | ||
245 | if (!IS_ERR(node)) | ||
246 | break; | ||
247 | } | ||
248 | mutex_unlock(&icc_lock); | ||
249 | |||
250 | return node; | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * of_icc_get() - get a path handle from a DT node based on name | ||
255 | * @dev: device pointer for the consumer device | ||
256 | * @name: interconnect path name | ||
257 | * | ||
258 | * This function will search for a path between two endpoints and return an | ||
259 | * icc_path handle on success. Use icc_put() to release constraints when they | ||
260 | * are not needed anymore. | ||
261 | * If the interconnect API is disabled, NULL is returned and the consumer | ||
262 | * drivers will still build. Drivers are free to handle this specifically, | ||
263 | * but they don't have to. | ||
264 | * | ||
265 | * Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned | ||
266 | * when the API is disabled or the "interconnects" DT property is missing. | ||
267 | */ | ||
268 | struct icc_path *of_icc_get(struct device *dev, const char *name) | ||
269 | { | ||
270 | struct icc_path *path = ERR_PTR(-EPROBE_DEFER); | ||
271 | struct icc_node *src_node, *dst_node; | ||
272 | struct device_node *np = NULL; | ||
273 | struct of_phandle_args src_args, dst_args; | ||
274 | int idx = 0; | ||
275 | int ret; | ||
276 | |||
277 | if (!dev || !dev->of_node) | ||
278 | return ERR_PTR(-ENODEV); | ||
279 | |||
280 | np = dev->of_node; | ||
281 | |||
282 | /* | ||
283 | * When the consumer DT node do not have "interconnects" property | ||
284 | * return a NULL path to skip setting constraints. | ||
285 | */ | ||
286 | if (!of_find_property(np, "interconnects", NULL)) | ||
287 | return NULL; | ||
288 | |||
289 | /* | ||
290 | * We use a combination of phandle and specifier for endpoint. For now | ||
291 | * lets support only global ids and extend this in the future if needed | ||
292 | * without breaking DT compatibility. | ||
293 | */ | ||
294 | if (name) { | ||
295 | idx = of_property_match_string(np, "interconnect-names", name); | ||
296 | if (idx < 0) | ||
297 | return ERR_PTR(idx); | ||
298 | } | ||
299 | |||
300 | ret = of_parse_phandle_with_args(np, "interconnects", | ||
301 | "#interconnect-cells", idx * 2, | ||
302 | &src_args); | ||
303 | if (ret) | ||
304 | return ERR_PTR(ret); | ||
305 | |||
306 | of_node_put(src_args.np); | ||
307 | |||
308 | ret = of_parse_phandle_with_args(np, "interconnects", | ||
309 | "#interconnect-cells", idx * 2 + 1, | ||
310 | &dst_args); | ||
311 | if (ret) | ||
312 | return ERR_PTR(ret); | ||
313 | |||
314 | of_node_put(dst_args.np); | ||
315 | |||
316 | src_node = of_icc_get_from_provider(&src_args); | ||
317 | |||
318 | if (IS_ERR(src_node)) { | ||
319 | if (PTR_ERR(src_node) != -EPROBE_DEFER) | ||
320 | dev_err(dev, "error finding src node: %ld\n", | ||
321 | PTR_ERR(src_node)); | ||
322 | return ERR_CAST(src_node); | ||
323 | } | ||
324 | |||
325 | dst_node = of_icc_get_from_provider(&dst_args); | ||
326 | |||
327 | if (IS_ERR(dst_node)) { | ||
328 | if (PTR_ERR(dst_node) != -EPROBE_DEFER) | ||
329 | dev_err(dev, "error finding dst node: %ld\n", | ||
330 | PTR_ERR(dst_node)); | ||
331 | return ERR_CAST(dst_node); | ||
332 | } | ||
333 | |||
334 | mutex_lock(&icc_lock); | ||
335 | path = path_find(dev, src_node, dst_node); | ||
336 | if (IS_ERR(path)) | ||
337 | dev_err(dev, "%s: invalid path=%ld\n", __func__, PTR_ERR(path)); | ||
338 | mutex_unlock(&icc_lock); | ||
339 | |||
340 | return path; | ||
341 | } | ||
342 | EXPORT_SYMBOL_GPL(of_icc_get); | ||
343 | |||
197 | /** | 344 | /** |
198 | * icc_set_bw() - set bandwidth constraints on an interconnect path | 345 | * icc_set_bw() - set bandwidth constraints on an interconnect path |
199 | * @path: reference to the path returned by icc_get() | 346 | * @path: reference to the path returned by icc_get() |
@@ -519,6 +666,8 @@ int icc_provider_add(struct icc_provider *provider) | |||
519 | { | 666 | { |
520 | if (WARN_ON(!provider->set)) | 667 | if (WARN_ON(!provider->set)) |
521 | return -EINVAL; | 668 | return -EINVAL; |
669 | if (WARN_ON(!provider->xlate)) | ||
670 | return -EINVAL; | ||
522 | 671 | ||
523 | mutex_lock(&icc_lock); | 672 | mutex_lock(&icc_lock); |
524 | 673 | ||