Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 87e3031b authored by Georgi Djakov's avatar Georgi Djakov Committed by Greg Kroah-Hartman
Browse files

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: default avatarEvan Green <evgreen@chromium.org>
Signed-off-by: default avatarGeorgi Djakov <georgi.djakov@linaro.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 06b3773b
Loading
Loading
Loading
Loading
+149 −0
Original line number Diff line number Diff line
@@ -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);
@@ -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()
@@ -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);

+17 −0
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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;
+7 −0
Original line number Diff line number Diff line
@@ -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);

@@ -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)
{
}