Skip to content

Commit

Permalink
interconnect: Allow endpoints translation via DT
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Georgi Djakov authored and Greg Kroah-Hartman committed Jan 22, 2019
1 parent 06b3773 commit 87e3031
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
149 changes: 149 additions & 0 deletions drivers/interconnect/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/overflow.h>

static DEFINE_IDR(icc_idr);
Expand Down Expand Up @@ -194,6 +195,152 @@ static int apply_constraints(struct icc_path *path)
return ret;
}

/* of_icc_xlate_onecell() - Translate function using a single index.
* @spec: OF phandle args to map into an interconnect node.
* @data: private data (pointer to struct icc_onecell_data)
*
* This is a generic translate function that can be used to model simple
* interconnect providers that have one device tree node and provide
* multiple interconnect nodes. A single cell is used as an index into
* an array of icc nodes specified in the icc_onecell_data struct when
* registering the provider.
*/
struct icc_node *of_icc_xlate_onecell(struct of_phandle_args *spec,
void *data)
{
struct icc_onecell_data *icc_data = data;
unsigned int idx = spec->args[0];

if (idx >= icc_data->num_nodes) {
pr_err("%s: invalid index %u\n", __func__, idx);
return ERR_PTR(-EINVAL);
}

return icc_data->nodes[idx];
}
EXPORT_SYMBOL_GPL(of_icc_xlate_onecell);

/**
* of_icc_get_from_provider() - Look-up interconnect node
* @spec: OF phandle args to use for look-up
*
* Looks for interconnect provider under the node specified by @spec and if
* found, uses xlate function of the provider to map phandle args to node.
*
* Returns a valid pointer to struct icc_node on success or ERR_PTR()
* on failure.
*/
static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
{
struct icc_node *node = ERR_PTR(-EPROBE_DEFER);
struct icc_provider *provider;

if (!spec || spec->args_count != 1)
return ERR_PTR(-EINVAL);

mutex_lock(&icc_lock);
list_for_each_entry(provider, &icc_providers, provider_list) {
if (provider->dev->of_node == spec->np)
node = provider->xlate(spec, provider->data);
if (!IS_ERR(node))
break;
}
mutex_unlock(&icc_lock);

return node;
}

/**
* of_icc_get() - get a path handle from a DT node based on name
* @dev: device pointer for the consumer device
* @name: interconnect path name
*
* This function will search for a path between two endpoints and return an
* icc_path handle on success. Use icc_put() to release constraints when they
* are not needed anymore.
* If the interconnect API is disabled, NULL is returned and the consumer
* drivers will still build. Drivers are free to handle this specifically,
* but they don't have to.
*
* Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned
* when the API is disabled or the "interconnects" DT property is missing.
*/
struct icc_path *of_icc_get(struct device *dev, const char *name)
{
struct icc_path *path = ERR_PTR(-EPROBE_DEFER);
struct icc_node *src_node, *dst_node;
struct device_node *np = NULL;
struct of_phandle_args src_args, dst_args;
int idx = 0;
int ret;

if (!dev || !dev->of_node)
return ERR_PTR(-ENODEV);

np = dev->of_node;

/*
* When the consumer DT node do not have "interconnects" property
* return a NULL path to skip setting constraints.
*/
if (!of_find_property(np, "interconnects", NULL))
return NULL;

/*
* We use a combination of phandle and specifier for endpoint. For now
* lets support only global ids and extend this in the future if needed
* without breaking DT compatibility.
*/
if (name) {
idx = of_property_match_string(np, "interconnect-names", name);
if (idx < 0)
return ERR_PTR(idx);
}

ret = of_parse_phandle_with_args(np, "interconnects",
"#interconnect-cells", idx * 2,
&src_args);
if (ret)
return ERR_PTR(ret);

of_node_put(src_args.np);

ret = of_parse_phandle_with_args(np, "interconnects",
"#interconnect-cells", idx * 2 + 1,
&dst_args);
if (ret)
return ERR_PTR(ret);

of_node_put(dst_args.np);

src_node = of_icc_get_from_provider(&src_args);

if (IS_ERR(src_node)) {
if (PTR_ERR(src_node) != -EPROBE_DEFER)
dev_err(dev, "error finding src node: %ld\n",
PTR_ERR(src_node));
return ERR_CAST(src_node);
}

dst_node = of_icc_get_from_provider(&dst_args);

if (IS_ERR(dst_node)) {
if (PTR_ERR(dst_node) != -EPROBE_DEFER)
dev_err(dev, "error finding dst node: %ld\n",
PTR_ERR(dst_node));
return ERR_CAST(dst_node);
}

mutex_lock(&icc_lock);
path = path_find(dev, src_node, dst_node);
if (IS_ERR(path))
dev_err(dev, "%s: invalid path=%ld\n", __func__, PTR_ERR(path));
mutex_unlock(&icc_lock);

return path;
}
EXPORT_SYMBOL_GPL(of_icc_get);

/**
* icc_set_bw() - set bandwidth constraints on an interconnect path
* @path: reference to the path returned by icc_get()
Expand Down Expand Up @@ -519,6 +666,8 @@ int icc_provider_add(struct icc_provider *provider)
{
if (WARN_ON(!provider->set))
return -EINVAL;
if (WARN_ON(!provider->xlate))
return -EINVAL;

mutex_lock(&icc_lock);

Expand Down
17 changes: 17 additions & 0 deletions include/linux/interconnect-provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
#define icc_units_to_bps(bw) ((bw) * 1000ULL)

struct icc_node;
struct of_phandle_args;

/**
* struct icc_onecell_data - driver data for onecell interconnect providers
*
* @num_nodes: number of nodes in this device
* @nodes: array of pointers to the nodes in this device
*/
struct icc_onecell_data {
unsigned int num_nodes;
struct icc_node *nodes[];
};

struct icc_node *of_icc_xlate_onecell(struct of_phandle_args *spec,
void *data);

/**
* struct icc_provider - interconnect provider (controller) entity that might
Expand All @@ -21,6 +36,7 @@ struct icc_node;
* @nodes: internal list of the interconnect provider nodes
* @set: pointer to device specific set operation function
* @aggregate: pointer to device specific aggregate operation function
* @xlate: provider-specific callback for mapping nodes from phandle arguments
* @dev: the device this interconnect provider belongs to
* @users: count of active users
* @data: pointer to private data
Expand All @@ -31,6 +47,7 @@ struct icc_provider {
int (*set)(struct icc_node *src, struct icc_node *dst);
int (*aggregate)(struct icc_node *node, u32 avg_bw, u32 peak_bw,
u32 *agg_avg, u32 *agg_peak);
struct icc_node* (*xlate)(struct of_phandle_args *spec, void *data);
struct device *dev;
int users;
void *data;
Expand Down
7 changes: 7 additions & 0 deletions include/linux/interconnect.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct device;

struct icc_path *icc_get(struct device *dev, const int src_id,
const int dst_id);
struct icc_path *of_icc_get(struct device *dev, const char *name);
void icc_put(struct icc_path *path);
int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw);

Expand All @@ -38,6 +39,12 @@ static inline struct icc_path *icc_get(struct device *dev, const int src_id,
return NULL;
}

static inline struct icc_path *of_icc_get(struct device *dev,
const char *name)
{
return NULL;
}

static inline void icc_put(struct icc_path *path)
{
}
Expand Down

0 comments on commit 87e3031

Please sign in to comment.