diff mbox series

[RFC,v5,05/10] interconnect: Add imx core driver

Message ID 3f8b65aa7a7eabaedeee27d5bcf8220982ac3597.1572562150.git.leonard.crestez@nxp.com (mailing list archive)
State RFC, archived
Headers show
Series interconnect: Add imx support via devfreq | expand

Commit Message

Leonard Crestez Oct. 31, 2019, 10:52 p.m. UTC
This adds support for i.MX SoC family to interconnect framework.

Platform drivers can describe the interconnect graph and several
adjustment knobs where icc node bandwidth is converted to a
DEV_PM_QOS_MIN_FREQUENCY request.

The adjustable nodes are found based on an "interconnect-node-id"
property by scanning the entire device tree.

The interconnect provider doesn't need an virtual OF node, instead those
same adjustable nodes are registered as proxies which xlate to the
platform-level provider.

The platform device for the interconnect needs to be registered from a
SOC driver (similar to cpufreq).

Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
---
 drivers/interconnect/Kconfig      |   1 +
 drivers/interconnect/Makefile     |   1 +
 drivers/interconnect/imx/Kconfig  |   5 +
 drivers/interconnect/imx/Makefile |   1 +
 drivers/interconnect/imx/imx.c    | 273 ++++++++++++++++++++++++++++++
 drivers/interconnect/imx/imx.h    |  60 +++++++
 6 files changed, 341 insertions(+)
 create mode 100644 drivers/interconnect/imx/Kconfig
 create mode 100644 drivers/interconnect/imx/Makefile
 create mode 100644 drivers/interconnect/imx/imx.c
 create mode 100644 drivers/interconnect/imx/imx.h

Comments

Georgi Djakov Nov. 12, 2019, 3:07 p.m. UTC | #1
Hi Leonard,

Thanks for the patch!

On 1.11.19 г. 0:52 ч., Leonard Crestez wrote:
> This adds support for i.MX SoC family to interconnect framework.
> 
> Platform drivers can describe the interconnect graph and several
> adjustment knobs where icc node bandwidth is converted to a
> DEV_PM_QOS_MIN_FREQUENCY request.
> 
> The adjustable nodes are found based on an "interconnect-node-id"
> property by scanning the entire device tree.

Are the adjustable nodes SoC specific? Can we have them here in the driver
instead of scanning the entire device tree?

> The interconnect provider doesn't need an virtual OF node, instead those
> same adjustable nodes are registered as proxies which xlate to the
> platform-level provider.
> 
> The platform device for the interconnect needs to be registered from a
> SOC driver (similar to cpufreq).
> 
> Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> ---
>  drivers/interconnect/Kconfig      |   1 +
>  drivers/interconnect/Makefile     |   1 +
>  drivers/interconnect/imx/Kconfig  |   5 +
>  drivers/interconnect/imx/Makefile |   1 +
>  drivers/interconnect/imx/imx.c    | 273 ++++++++++++++++++++++++++++++
>  drivers/interconnect/imx/imx.h    |  60 +++++++
>  6 files changed, 341 insertions(+)
>  create mode 100644 drivers/interconnect/imx/Kconfig
>  create mode 100644 drivers/interconnect/imx/Makefile
>  create mode 100644 drivers/interconnect/imx/imx.c
>  create mode 100644 drivers/interconnect/imx/imx.h
> 
> diff --git a/drivers/interconnect/Kconfig b/drivers/interconnect/Kconfig
> index b6ea8f0a6122..f57e77b8731c 100644
> --- a/drivers/interconnect/Kconfig
> +++ b/drivers/interconnect/Kconfig
> @@ -10,7 +10,8 @@ menuconfig INTERCONNECT
>  	  If unsure, say no.
>  
>  if INTERCONNECT
>  
>  source "drivers/interconnect/qcom/Kconfig"
> +source "drivers/interconnect/imx/Kconfig"
>  
>  endif
> diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile
> index 28f2ab0824d5..20a13b7eb37f 100644
> --- a/drivers/interconnect/Makefile
> +++ b/drivers/interconnect/Makefile
> @@ -2,5 +2,6 @@
>  
>  icc-core-objs				:= core.o
>  
>  obj-$(CONFIG_INTERCONNECT)		+= icc-core.o
>  obj-$(CONFIG_INTERCONNECT_QCOM)		+= qcom/
> +obj-$(CONFIG_INTERCONNECT_IMX)		+= imx/
> diff --git a/drivers/interconnect/imx/Kconfig b/drivers/interconnect/imx/Kconfig
> new file mode 100644
> index 000000000000..7d81d3c83a61
> --- /dev/null
> +++ b/drivers/interconnect/imx/Kconfig
> @@ -0,0 +1,5 @@
> +config INTERCONNECT_IMX
> +	bool "i.MX interconnect drivers"
> +	depends on ARCH_MXC || COMPILE_TEST
> +	help
> +	  Generic interconnect driver for i.MX SOCs
> diff --git a/drivers/interconnect/imx/Makefile b/drivers/interconnect/imx/Makefile
> new file mode 100644
> index 000000000000..bb92fd9fe4a5
> --- /dev/null
> +++ b/drivers/interconnect/imx/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_INTERCONNECT_IMX) += imx.o
> diff --git a/drivers/interconnect/imx/imx.c b/drivers/interconnect/imx/imx.c
> new file mode 100644
> index 000000000000..7d248e01dcf0
> --- /dev/null
> +++ b/drivers/interconnect/imx/imx.c
> @@ -0,0 +1,273 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Interconnect framework driver for i.MX SoC
> + *
> + * Copyright (c) 2019, BayLibre
> + * Copyright (c) 2019, NXP
> + * Author: Alexandre Bailon <abailon@baylibre.com>
> + * Author: Leonard Crestez <leonard.crestez@nxp.com>
> + */
> +
> +#include <linux/devfreq.h>
> +#include <linux/device.h>
> +#include <linux/interconnect-provider.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_qos.h>
> +
> +#include "imx.h"
> +
> +/* private icc_provider data */
> +struct imx_icc_provider {
> +	struct device *dev;

What device is this? There is already a *dev in struct icc_provider.
Please add kernel-doc.

> +};
> +
> +/* private icc_node data */
> +struct imx_icc_node {
> +	const struct imx_icc_node_desc *desc;
> +	struct devfreq *devfreq;
> +	struct dev_pm_qos_request qos_req;
> +};
> +
> +static int imx_icc_aggregate(struct icc_node *node, u32 tag,
> +			     u32 avg_bw, u32 peak_bw,
> +			     u32 *agg_avg, u32 *agg_peak)
> +{
> +	*agg_avg += avg_bw;
> +	*agg_peak = max(*agg_peak, peak_bw);
> +
> +	return 0;
> +}
> +
> +static struct icc_node *imx_icc_xlate(struct of_phandle_args *spec, void *data)
> +{
> +	struct imx_icc_provider *desc = data;
> +	struct icc_provider *provider = dev_get_drvdata(desc->dev);
> +	unsigned int id = spec->args[0];
> +	struct icc_node *node;
> +
> +	list_for_each_entry(node, &provider->nodes, node_list)
> +		if (node->id == id)
> +			return node;
> +
> +	return ERR_PTR(-EINVAL);
> +}
> +
> +static int imx_icc_node_set(struct icc_node *node)
> +{
> +	struct device *dev = node->provider->dev;
> +	struct imx_icc_node *node_data = node->data;
> +	u64 freq;
> +
> +	if (!node_data->devfreq)
> +		return 0;
> +
> +	freq = (node->avg_bw + node->peak_bw) * node_data->desc->adj->bw_mul;

Why the sum of average and peak bandwidth?

> +	do_div(freq, node_data->desc->adj->bw_div);
> +	dev_dbg(dev, "node %s device %s avg_bw %ukBps peak_bw %ukBps min_freq %llukHz\n",
> +			node->name, dev_name(node_data->devfreq->dev.parent),
> +			node->avg_bw, node->peak_bw, freq);
> +
> +	if (freq > S32_MAX) {
> +		dev_err(dev, "%s can't request more than S32_MAX freq\n",
> +				node->name);
> +		return -ERANGE;
> +	}
> +
> +	dev_pm_qos_update_request(&node_data->qos_req, freq);
> +
> +	return 0;
> +}
> +
> +static int imx_icc_set(struct icc_node *src, struct icc_node *dst)
> +{
> +	return imx_icc_node_set(dst);
> +}
> +
> +static int imx_icc_node_init_devfreq(struct device *dev,
> +				     struct icc_node *node)
> +{
> +	struct imx_icc_node *node_data = node->data;
> +	struct device_node *dn;
> +	u32 node_id;
> +	int ret;
> +
> +	/* Find nodes based on interconnect-node-id property */
> +	for_each_node_with_property(dn, "interconnect-node-id") {
> +		ret = of_property_read_u32(dn, "interconnect-node-id",
> +					   &node_id);
> +		if (ret != 0)
> +			continue;
> +
> +		if (node_id == node->id) {
> +			of_node_get(dn);
> +			break;
> +		}
> +	}
> +
> +	if (!dn)
> +		return 0;
> +
> +	dev_info(dev, "node %s[%d] has device node %pOF\n",
> +			node->name, node->id, dn);
> +	node_data->devfreq = devfreq_get_devfreq_by_node(dn);

Ah, so you need to get the devfreq nodes? So looking at the next
patches it seems that noc and ddrc are the only adjustable nodes?

Maybe we should model them both as interconnect providers, as they
seem to be dealing with the bandwidth/frequency requirements and
changing the clock rates.

Thanks,
Georgi

> +	if (IS_ERR(node_data->devfreq)) {
> +		of_node_put(dn);
> +		ret = PTR_ERR(node_data->devfreq);
> +		dev_err(dev, "failed to fetch devfreq for %s: %d\n",
> +				node->name, ret);
> +		return ret;
> +	}
> +
> +	of_node_put(dn);
> +
> +	return dev_pm_qos_add_request(node_data->devfreq->dev.parent,
> +				      &node_data->qos_req,
> +				      DEV_PM_QOS_MIN_FREQUENCY, 0);
> +}
> +
Leonard Crestez Nov. 12, 2019, 5:25 p.m. UTC | #2
On 12.11.2019 17:08, Georgi Djakov wrote:
> Hi Leonard,
> 
> Thanks for the patch!
> 
> On 1.11.19 г. 0:52 ч., Leonard Crestez wrote:
>> This adds support for i.MX SoC family to interconnect framework.
>>
>> Platform drivers can describe the interconnect graph and several
>> adjustment knobs where icc node bandwidth is converted to a
>> DEV_PM_QOS_MIN_FREQUENCY request.
>>
>> The adjustable nodes are found based on an "interconnect-node-id"
>> property by scanning the entire device tree.
> 
> Are the adjustable nodes SoC specific? Can we have them here in the driver
> instead of scanning the entire device tree?
> 
>> The interconnect provider doesn't need an virtual OF node, instead those
>> same adjustable nodes are registered as proxies which xlate to the
>> platform-level provider.
>>
>> The platform device for the interconnect needs to be registered from a
>> SOC driver (similar to cpufreq).
>>
>> Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> ---
>>   drivers/interconnect/Kconfig      |   1 +
>>   drivers/interconnect/Makefile     |   1 +
>>   drivers/interconnect/imx/Kconfig  |   5 +
>>   drivers/interconnect/imx/Makefile |   1 +
>>   drivers/interconnect/imx/imx.c    | 273 ++++++++++++++++++++++++++++++
>>   drivers/interconnect/imx/imx.h    |  60 +++++++
>>   6 files changed, 341 insertions(+)
>>   create mode 100644 drivers/interconnect/imx/Kconfig
>>   create mode 100644 drivers/interconnect/imx/Makefile
>>   create mode 100644 drivers/interconnect/imx/imx.c
>>   create mode 100644 drivers/interconnect/imx/imx.h
>>
>> diff --git a/drivers/interconnect/Kconfig b/drivers/interconnect/Kconfig
>> index b6ea8f0a6122..f57e77b8731c 100644
>> --- a/drivers/interconnect/Kconfig
>> +++ b/drivers/interconnect/Kconfig
>> @@ -10,7 +10,8 @@ menuconfig INTERCONNECT
>>   	  If unsure, say no.
>>   
>>   if INTERCONNECT
>>   
>>   source "drivers/interconnect/qcom/Kconfig"
>> +source "drivers/interconnect/imx/Kconfig"
>>   
>>   endif
>> diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile
>> index 28f2ab0824d5..20a13b7eb37f 100644
>> --- a/drivers/interconnect/Makefile
>> +++ b/drivers/interconnect/Makefile
>> @@ -2,5 +2,6 @@
>>   
>>   icc-core-objs				:= core.o
>>   
>>   obj-$(CONFIG_INTERCONNECT)		+= icc-core.o
>>   obj-$(CONFIG_INTERCONNECT_QCOM)		+= qcom/
>> +obj-$(CONFIG_INTERCONNECT_IMX)		+= imx/
>> diff --git a/drivers/interconnect/imx/Kconfig b/drivers/interconnect/imx/Kconfig
>> new file mode 100644
>> index 000000000000..7d81d3c83a61
>> --- /dev/null
>> +++ b/drivers/interconnect/imx/Kconfig
>> @@ -0,0 +1,5 @@
>> +config INTERCONNECT_IMX
>> +	bool "i.MX interconnect drivers"
>> +	depends on ARCH_MXC || COMPILE_TEST
>> +	help
>> +	  Generic interconnect driver for i.MX SOCs
>> diff --git a/drivers/interconnect/imx/Makefile b/drivers/interconnect/imx/Makefile
>> new file mode 100644
>> index 000000000000..bb92fd9fe4a5
>> --- /dev/null
>> +++ b/drivers/interconnect/imx/Makefile
>> @@ -0,0 +1 @@
>> +obj-$(CONFIG_INTERCONNECT_IMX) += imx.o
>> diff --git a/drivers/interconnect/imx/imx.c b/drivers/interconnect/imx/imx.c
>> new file mode 100644
>> index 000000000000..7d248e01dcf0
>> --- /dev/null
>> +++ b/drivers/interconnect/imx/imx.c
>> @@ -0,0 +1,273 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Interconnect framework driver for i.MX SoC
>> + *
>> + * Copyright (c) 2019, BayLibre
>> + * Copyright (c) 2019, NXP
>> + * Author: Alexandre Bailon <abailon@baylibre.com>
>> + * Author: Leonard Crestez <leonard.crestez@nxp.com>
>> + */
>> +
>> +#include <linux/devfreq.h>
>> +#include <linux/device.h>
>> +#include <linux/interconnect-provider.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_qos.h>
>> +
>> +#include "imx.h"
>> +
>> +/* private icc_provider data */
>> +struct imx_icc_provider {
>> +	struct device *dev;
> 
> What device is this? There is already a *dev in struct icc_provider.
> Please add kernel-doc.

This is set as icc_provider->data and used for xlate but it can be 
removed entirely if driver switches to icc_xlate_onecell.

>> +/* private icc_node data */
>> +struct imx_icc_node {
>> +	const struct imx_icc_node_desc *desc;
>> +	struct devfreq *devfreq;
>> +	struct dev_pm_qos_request qos_req;
>> +};
>> +
>> +static int imx_icc_aggregate(struct icc_node *node, u32 tag,
>> +			     u32 avg_bw, u32 peak_bw,
>> +			     u32 *agg_avg, u32 *agg_peak)
>> +{
>> +	*agg_avg += avg_bw;
>> +	*agg_peak = max(*agg_peak, peak_bw);
>> +
>> +	return 0;
>> +}
>> +
>> +static struct icc_node *imx_icc_xlate(struct of_phandle_args *spec, void *data)
>> +{
>> +	struct imx_icc_provider *desc = data;
>> +	struct icc_provider *provider = dev_get_drvdata(desc->dev);
>> +	unsigned int id = spec->args[0];
>> +	struct icc_node *node;
>> +
>> +	list_for_each_entry(node, &provider->nodes, node_list)
>> +		if (node->id == id)
>> +			return node;
>> +
>> +	return ERR_PTR(-EINVAL);
>> +}
>> +
>> +static int imx_icc_node_set(struct icc_node *node)
>> +{
>> +	struct device *dev = node->provider->dev;
>> +	struct imx_icc_node *node_data = node->data;
>> +	u64 freq;
>> +
>> +	if (!node_data->devfreq)
>> +		return 0;
>> +
>> +	freq = (node->avg_bw + node->peak_bw) * node_data->desc->adj->bw_mul;
> 
> Why the sum of average and peak bandwidth?

What else?

Averages from all path requests are added and the max() of all per-path 
peak requests is added on top. The average BW is guaranteed to be 
consumed (for example a display controller) and the BW for occasional 
bursts (such as USB) should be reserved on top of that.

Using max(avg, peak) here would make requests for peak BW largely 
ineffective when there's also a large "avg" request.

>> +	do_div(freq, node_data->desc->adj->bw_div);
>> +	dev_dbg(dev, "node %s device %s avg_bw %ukBps peak_bw %ukBps min_freq %llukHz\n",
>> +			node->name, dev_name(node_data->devfreq->dev.parent),
>> +			node->avg_bw, node->peak_bw, freq);
>> +
>> +	if (freq > S32_MAX) {
>> +		dev_err(dev, "%s can't request more than S32_MAX freq\n",
>> +				node->name);
>> +		return -ERANGE;
>> +	}
>> +
>> +	dev_pm_qos_update_request(&node_data->qos_req, freq);
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx_icc_set(struct icc_node *src, struct icc_node *dst)
>> +{
>> +	return imx_icc_node_set(dst);
>> +}
>> +
>> +static int imx_icc_node_init_devfreq(struct device *dev,
>> +				     struct icc_node *node)
>> +{
>> +	struct imx_icc_node *node_data = node->data;
>> +	struct device_node *dn;
>> +	u32 node_id;
>> +	int ret;
>> +
>> +	/* Find nodes based on interconnect-node-id property */
>> +	for_each_node_with_property(dn, "interconnect-node-id") {
>> +		ret = of_property_read_u32(dn, "interconnect-node-id",
>> +					   &node_id);
>> +		if (ret != 0)
>> +			continue;
>> +
>> +		if (node_id == node->id) {
>> +			of_node_get(dn);
>> +			break;
>> +		}
>> +	}
>> +
>> +	if (!dn)
>> +		return 0;
>> +
>> +	dev_info(dev, "node %s[%d] has device node %pOF\n",
>> +			node->name, node->id, dn);
>> +	node_data->devfreq = devfreq_get_devfreq_by_node(dn);
> 
> Ah, so you need to get the devfreq nodes? So looking at the next
> patches it seems that noc and ddrc are the only adjustable nodes?

I want to add more scalable nodes in the future. imx8mq/8mm/8mn have 
additional NICs (AXI switches with a different implementation) and 
upcoming imx8mp will have additional NOCs and scalable NTTP links.

> Maybe we should model them both as interconnect providers, as they
> seem to be dealing with the bandwidth/frequency requirements and
> changing the clock rates.

The ddrc is not really an interconnect, it only has an AXI slave port. 
In theory it could register itself as a single-node icc provider solely 
so that it can handle the "set" call but that seems hacky.

Current icc driver implementations send aggregated BW requests to a 
separate controller and internally it changes some frequencies, correct? 
What I'm doing is implementing the frequency adjustment part inside the 
kernel as devfreq drivers. These devices have their own OF nodes with 
OPP tables and supply regulators and governors (based on bandwith 
measurement).

It makes a lot of sense to layer the "interconnect" on top of "scalable 
nodes".

--
Regards,
Leonard
diff mbox series

Patch

diff --git a/drivers/interconnect/Kconfig b/drivers/interconnect/Kconfig
index b6ea8f0a6122..f57e77b8731c 100644
--- a/drivers/interconnect/Kconfig
+++ b/drivers/interconnect/Kconfig
@@ -10,7 +10,8 @@  menuconfig INTERCONNECT
 	  If unsure, say no.
 
 if INTERCONNECT
 
 source "drivers/interconnect/qcom/Kconfig"
+source "drivers/interconnect/imx/Kconfig"
 
 endif
diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile
index 28f2ab0824d5..20a13b7eb37f 100644
--- a/drivers/interconnect/Makefile
+++ b/drivers/interconnect/Makefile
@@ -2,5 +2,6 @@ 
 
 icc-core-objs				:= core.o
 
 obj-$(CONFIG_INTERCONNECT)		+= icc-core.o
 obj-$(CONFIG_INTERCONNECT_QCOM)		+= qcom/
+obj-$(CONFIG_INTERCONNECT_IMX)		+= imx/
diff --git a/drivers/interconnect/imx/Kconfig b/drivers/interconnect/imx/Kconfig
new file mode 100644
index 000000000000..7d81d3c83a61
--- /dev/null
+++ b/drivers/interconnect/imx/Kconfig
@@ -0,0 +1,5 @@ 
+config INTERCONNECT_IMX
+	bool "i.MX interconnect drivers"
+	depends on ARCH_MXC || COMPILE_TEST
+	help
+	  Generic interconnect driver for i.MX SOCs
diff --git a/drivers/interconnect/imx/Makefile b/drivers/interconnect/imx/Makefile
new file mode 100644
index 000000000000..bb92fd9fe4a5
--- /dev/null
+++ b/drivers/interconnect/imx/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_INTERCONNECT_IMX) += imx.o
diff --git a/drivers/interconnect/imx/imx.c b/drivers/interconnect/imx/imx.c
new file mode 100644
index 000000000000..7d248e01dcf0
--- /dev/null
+++ b/drivers/interconnect/imx/imx.c
@@ -0,0 +1,273 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Interconnect framework driver for i.MX SoC
+ *
+ * Copyright (c) 2019, BayLibre
+ * Copyright (c) 2019, NXP
+ * Author: Alexandre Bailon <abailon@baylibre.com>
+ * Author: Leonard Crestez <leonard.crestez@nxp.com>
+ */
+
+#include <linux/devfreq.h>
+#include <linux/device.h>
+#include <linux/interconnect-provider.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_qos.h>
+
+#include "imx.h"
+
+/* private icc_provider data */
+struct imx_icc_provider {
+	struct device *dev;
+};
+
+/* private icc_node data */
+struct imx_icc_node {
+	const struct imx_icc_node_desc *desc;
+	struct devfreq *devfreq;
+	struct dev_pm_qos_request qos_req;
+};
+
+static int imx_icc_aggregate(struct icc_node *node, u32 tag,
+			     u32 avg_bw, u32 peak_bw,
+			     u32 *agg_avg, u32 *agg_peak)
+{
+	*agg_avg += avg_bw;
+	*agg_peak = max(*agg_peak, peak_bw);
+
+	return 0;
+}
+
+static struct icc_node *imx_icc_xlate(struct of_phandle_args *spec, void *data)
+{
+	struct imx_icc_provider *desc = data;
+	struct icc_provider *provider = dev_get_drvdata(desc->dev);
+	unsigned int id = spec->args[0];
+	struct icc_node *node;
+
+	list_for_each_entry(node, &provider->nodes, node_list)
+		if (node->id == id)
+			return node;
+
+	return ERR_PTR(-EINVAL);
+}
+
+static int imx_icc_node_set(struct icc_node *node)
+{
+	struct device *dev = node->provider->dev;
+	struct imx_icc_node *node_data = node->data;
+	u64 freq;
+
+	if (!node_data->devfreq)
+		return 0;
+
+	freq = (node->avg_bw + node->peak_bw) * node_data->desc->adj->bw_mul;
+	do_div(freq, node_data->desc->adj->bw_div);
+	dev_dbg(dev, "node %s device %s avg_bw %ukBps peak_bw %ukBps min_freq %llukHz\n",
+			node->name, dev_name(node_data->devfreq->dev.parent),
+			node->avg_bw, node->peak_bw, freq);
+
+	if (freq > S32_MAX) {
+		dev_err(dev, "%s can't request more than S32_MAX freq\n",
+				node->name);
+		return -ERANGE;
+	}
+
+	dev_pm_qos_update_request(&node_data->qos_req, freq);
+
+	return 0;
+}
+
+static int imx_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+	return imx_icc_node_set(dst);
+}
+
+static int imx_icc_node_init_devfreq(struct device *dev,
+				     struct icc_node *node)
+{
+	struct imx_icc_node *node_data = node->data;
+	struct device_node *dn;
+	u32 node_id;
+	int ret;
+
+	/* Find nodes based on interconnect-node-id property */
+	for_each_node_with_property(dn, "interconnect-node-id") {
+		ret = of_property_read_u32(dn, "interconnect-node-id",
+					   &node_id);
+		if (ret != 0)
+			continue;
+
+		if (node_id == node->id) {
+			of_node_get(dn);
+			break;
+		}
+	}
+
+	if (!dn)
+		return 0;
+
+	dev_info(dev, "node %s[%d] has device node %pOF\n",
+			node->name, node->id, dn);
+	node_data->devfreq = devfreq_get_devfreq_by_node(dn);
+	if (IS_ERR(node_data->devfreq)) {
+		of_node_put(dn);
+		ret = PTR_ERR(node_data->devfreq);
+		dev_err(dev, "failed to fetch devfreq for %s: %d\n",
+				node->name, ret);
+		return ret;
+	}
+
+	of_node_put(dn);
+
+	return dev_pm_qos_add_request(node_data->devfreq->dev.parent,
+				      &node_data->qos_req,
+				      DEV_PM_QOS_MIN_FREQUENCY, 0);
+}
+
+static struct icc_node *imx_icc_node_add(struct icc_provider *provider,
+		const struct imx_icc_node_desc *node_desc)
+{
+	struct imx_icc_provider *provider_data = provider->data;
+	struct device *dev = provider_data->dev;
+	struct imx_icc_node *node_data;
+	struct icc_node *node;
+	int ret;
+
+	node = icc_node_create(node_desc->id);
+	if (IS_ERR(node)) {
+		dev_err(dev, "failed to create node %d\n", node_desc->id);
+		return node;
+	}
+
+	if (node->data) {
+		dev_err(dev, "already created node %s id=%d\n",
+				node_desc->name, node_desc->id);
+		return ERR_PTR(-EEXIST);
+	}
+
+	node_data = devm_kzalloc(dev, sizeof(*node_data), GFP_KERNEL);
+	if (!node_data) {
+		icc_node_destroy(node->id);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	node->name = node_desc->name;
+	node->data = node_data;
+	node_data->desc = node_desc;
+	icc_node_add(node, provider);
+
+	if (node_desc->adj) {
+		ret = imx_icc_node_init_devfreq(dev, node);
+		if (ret < 0) {
+			icc_node_del(node);
+			icc_node_destroy(node->id);
+			return ERR_PTR(ret);
+		}
+	}
+
+	return node;
+}
+
+static void imx_icc_unregister_nodes(struct icc_provider *provider)
+{
+	struct icc_node *node, *tmp;
+
+	list_for_each_entry_safe(node, tmp, &provider->nodes, node_list) {
+		struct imx_icc_node *node_data = node->data;
+
+		icc_node_del(node);
+		icc_node_destroy(node->id);
+		if (dev_pm_qos_request_active(&node_data->qos_req))
+			dev_pm_qos_remove_request(&node_data->qos_req);
+	}
+}
+
+static int imx_icc_register_nodes(struct icc_provider *provider,
+				  const struct imx_icc_node_desc *descs,
+				  int count)
+{
+	int ret;
+	int i;
+
+	for (i = 0; i < count; i++) {
+		struct icc_node *node;
+		const struct imx_icc_node_desc *node_desc = &descs[i];
+		size_t j;
+
+		node = imx_icc_node_add(provider, node_desc);
+		if (IS_ERR(node)) {
+			ret = PTR_ERR(node);
+			if (ret != -EPROBE_DEFER)
+				dev_err(provider->dev, "failed to add %s: %d\n",
+						node_desc->name, ret);
+			goto err;
+		}
+
+		for (j = 0; j < node_desc->num_links; j++)
+			icc_link_create(node, node_desc->links[j]);
+	}
+
+	return 0;
+
+err:
+	imx_icc_unregister_nodes(provider);
+
+	return ret;
+}
+
+int imx_icc_register(struct platform_device *pdev,
+		     struct imx_icc_node_desc *nodes, int nodes_count)
+{
+	struct device *dev = &pdev->dev;
+	struct imx_icc_provider *provider_data;
+	struct icc_provider *provider;
+	int ret;
+
+	provider_data = devm_kzalloc(dev, sizeof(*provider_data), GFP_KERNEL);
+	if (!provider_data)
+		return -ENOMEM;
+	provider_data->dev = dev;
+
+	provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL);
+	if (!provider)
+		return -ENOMEM;
+	provider->set = imx_icc_set;
+	provider->aggregate = imx_icc_aggregate;
+	provider->xlate = imx_icc_xlate;
+	provider->data = provider_data;
+	provider->dev = dev->parent;
+	platform_set_drvdata(pdev, provider);
+
+	ret = icc_provider_add(provider);
+	if (ret) {
+		dev_err(dev, "error adding interconnect provider: %d\n", ret);
+		return ret;
+	}
+
+	ret = imx_icc_register_nodes(provider, nodes, nodes_count);
+	if (ret)
+		goto provider_del;
+
+	pr_info("registered %s\n", pdev->name);
+
+	return 0;
+
+provider_del:
+	icc_provider_del(provider);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(imx_icc_register);
+
+int imx_icc_unregister(struct platform_device *pdev)
+{
+	struct icc_provider *provider = platform_get_drvdata(pdev);
+
+	icc_provider_del(provider);
+	imx_icc_unregister_nodes(provider);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(imx_icc_unregister);
diff --git a/drivers/interconnect/imx/imx.h b/drivers/interconnect/imx/imx.h
new file mode 100644
index 000000000000..9299b8d941f0
--- /dev/null
+++ b/drivers/interconnect/imx/imx.h
@@ -0,0 +1,60 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Interconnect framework driver for i.MX SoC
+ *
+ * Copyright (c) 2019, BayLibre
+ * Copyright (c) 2019, NXP
+ * Author: Alexandre Bailon <abailon@baylibre.com>
+ * Author: Leonard Crestez <leonard.crestez@nxp.com>
+ */
+#ifndef __DRIVERS_INTERCONNECT_IMX_H
+#define __DRIVERS_INTERCONNECT_IMX_H
+
+#include <linux/kernel.h>
+
+#define IMX_ICC_MAX_LINKS	4
+
+/*
+ * struct imx_icc_node_adj - Describe a dynamic adjustment knob
+ */
+struct imx_icc_node_adj_desc {
+	unsigned int bw_mul, bw_div;
+};
+
+/*
+ * struct imx_icc_node - Describe an interconnect node
+ * @name: name of the node
+ * @id: an unique id to identify the node
+ * @links: an array of slaves' node id
+ * @num_links: number of id defined in links
+ */
+struct imx_icc_node_desc {
+	const char *name;
+	u16 id;
+	u16 links[IMX_ICC_MAX_LINKS];
+	u16 num_links;
+
+	const struct imx_icc_node_adj_desc *adj;
+};
+
+#define DEFINE_BUS_INTERCONNECT(_name, _id, _adj, ...)			\
+	{								\
+		.id = _id,						\
+		.name = _name,						\
+		.adj = _adj,						\
+		.num_links = ARRAY_SIZE(((int[]){ __VA_ARGS__ })),	\
+		.links = { __VA_ARGS__ },				\
+	}
+
+#define DEFINE_BUS_MASTER(_name, _id, _dest_id)				\
+	DEFINE_BUS_INTERCONNECT(_name, _id, NULL, 1, _dest_id)
+
+#define DEFINE_BUS_SLAVE(_name, _id, _adj)				\
+	DEFINE_BUS_INTERCONNECT(_name, _id, _adj, 0)
+
+int imx_icc_register(struct platform_device *pdev,
+		     struct imx_icc_node_desc *nodes,
+		     int nodes_count);
+int imx_icc_unregister(struct platform_device *pdev);
+
+#endif /* __DRIVERS_INTERCONNECT_IMX_H */