diff options
| -rw-r--r-- | drivers/interconnect/core.c | 149 | ||||
| -rw-r--r-- | include/linux/interconnect-provider.h | 17 | ||||
| -rw-r--r-- | include/linux/interconnect.h | 7 |
3 files changed, 173 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 | ||
diff --git a/include/linux/interconnect-provider.h b/include/linux/interconnect-provider.h index 78208a754181..63caccadc2db 100644 --- a/include/linux/interconnect-provider.h +++ b/include/linux/interconnect-provider.h | |||
| @@ -12,6 +12,21 @@ | |||
| 12 | #define icc_units_to_bps(bw) ((bw) * 1000ULL) | 12 | #define icc_units_to_bps(bw) ((bw) * 1000ULL) |
| 13 | 13 | ||
| 14 | struct icc_node; | 14 | struct icc_node; |
| 15 | struct of_phandle_args; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * struct icc_onecell_data - driver data for onecell interconnect providers | ||
| 19 | * | ||
| 20 | * @num_nodes: number of nodes in this device | ||
| 21 | * @nodes: array of pointers to the nodes in this device | ||
| 22 | */ | ||
| 23 | struct icc_onecell_data { | ||
| 24 | unsigned int num_nodes; | ||
| 25 | struct icc_node *nodes[]; | ||
| 26 | }; | ||
| 27 | |||
| 28 | struct icc_node *of_icc_xlate_onecell(struct of_phandle_args *spec, | ||
| 29 | void *data); | ||
| 15 | 30 | ||
| 16 | /** | 31 | /** |
| 17 | * struct icc_provider - interconnect provider (controller) entity that might | 32 | * struct icc_provider - interconnect provider (controller) entity that might |
| @@ -21,6 +36,7 @@ struct icc_node; | |||
| 21 | * @nodes: internal list of the interconnect provider nodes | 36 | * @nodes: internal list of the interconnect provider nodes |
| 22 | * @set: pointer to device specific set operation function | 37 | * @set: pointer to device specific set operation function |
| 23 | * @aggregate: pointer to device specific aggregate operation function | 38 | * @aggregate: pointer to device specific aggregate operation function |
| 39 | * @xlate: provider-specific callback for mapping nodes from phandle arguments | ||
| 24 | * @dev: the device this interconnect provider belongs to | 40 | * @dev: the device this interconnect provider belongs to |
| 25 | * @users: count of active users | 41 | * @users: count of active users |
| 26 | * @data: pointer to private data | 42 | * @data: pointer to private data |
| @@ -31,6 +47,7 @@ struct icc_provider { | |||
| 31 | int (*set)(struct icc_node *src, struct icc_node *dst); | 47 | int (*set)(struct icc_node *src, struct icc_node *dst); |
| 32 | int (*aggregate)(struct icc_node *node, u32 avg_bw, u32 peak_bw, | 48 | int (*aggregate)(struct icc_node *node, u32 avg_bw, u32 peak_bw, |
| 33 | u32 *agg_avg, u32 *agg_peak); | 49 | u32 *agg_avg, u32 *agg_peak); |
| 50 | struct icc_node* (*xlate)(struct of_phandle_args *spec, void *data); | ||
| 34 | struct device *dev; | 51 | struct device *dev; |
| 35 | int users; | 52 | int users; |
| 36 | void *data; | 53 | void *data; |
diff --git a/include/linux/interconnect.h b/include/linux/interconnect.h index c331afb3a2c8..dc25864755ba 100644 --- a/include/linux/interconnect.h +++ b/include/linux/interconnect.h | |||
| @@ -27,6 +27,7 @@ struct device; | |||
| 27 | 27 | ||
| 28 | struct icc_path *icc_get(struct device *dev, const int src_id, | 28 | struct icc_path *icc_get(struct device *dev, const int src_id, |
| 29 | const int dst_id); | 29 | const int dst_id); |
| 30 | struct icc_path *of_icc_get(struct device *dev, const char *name); | ||
| 30 | void icc_put(struct icc_path *path); | 31 | void icc_put(struct icc_path *path); |
| 31 | int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw); | 32 | int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw); |
| 32 | 33 | ||
| @@ -38,6 +39,12 @@ static inline struct icc_path *icc_get(struct device *dev, const int src_id, | |||
| 38 | return NULL; | 39 | return NULL; |
| 39 | } | 40 | } |
| 40 | 41 | ||
| 42 | static inline struct icc_path *of_icc_get(struct device *dev, | ||
| 43 | const char *name) | ||
| 44 | { | ||
| 45 | return NULL; | ||
| 46 | } | ||
| 47 | |||
| 41 | static inline void icc_put(struct icc_path *path) | 48 | static inline void icc_put(struct icc_path *path) |
| 42 | { | 49 | { |
| 43 | } | 50 | } |
