diff mbox series

[2/2] OPP/pmdomain: Fix the assignment of the required-devs

Message ID 20240902224815.78220-3-ulf.hansson@linaro.org
State New
Headers show
Series OPP/pmdomain: Assign the correct required-dev | expand

Commit Message

Ulf Hansson Sept. 2, 2024, 10:48 p.m. UTC
The recent attempt to make genpd first lookup an OPP table for a device
that has been attached to it and then let the OPP core validate whether the
OPP table is correct, fails for some configurations.

More precisely, if a device has multiple power-domains, which all has an
OPP table associated, doesn't necessarily mean that the device's OPP table
needs multiple phandles specified in the required-opps. Instead it's
perfectly possible to use a single phandle in the required-opps, which is
typically where the current code fails to assign the correct required-dev.

To fix this problem, let's instead start by letting the OPP core find the
device node for the required OPP table and then let genpd search for a
corresponding OPP table, allowing us the find the correct required-dev to
assign for it.

Reported-by: Dikshita Agarwal <quic_dikshita@quicinc.com>
Fixes: f0d2dcc9b087 ("OPP/pmdomain: Set the required_dev for a required OPP during genpd attach")
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
---
 drivers/opp/core.c      | 15 +++++++-----
 drivers/pmdomain/core.c | 52 +++++++++++++++++++++++------------------
 include/linux/pm_opp.h  |  7 ++++--
 3 files changed, 43 insertions(+), 31 deletions(-)

Comments

Viresh Kumar Sept. 6, 2024, 6:14 a.m. UTC | #1
On 04-09-24, 14:57, Ulf Hansson wrote:
> > Yeah, I missed that, it doesn't happen via DT but by platform code. I
> > do see problems where situation would be a bit ambiguous. Your example
> > with a minor change to your code:
> >
> >         opp_table_devA: opp-table-devA {
> >                 compatible = "operating-points-v2";
> >
> >                 opp-devA-50 {
> >                         opp-hz = /bits/ 64 <2500>;
> >                         required-opps = <&opp_pd_50, &opp_pd_51>; //corresponds to pd_perf1 and pd_perf0 (in reverse order)
> >                 };
> >                ....
> >
> >         devA {
> >                 compatible = "foo,bar";
> >                 power-domains = <&pd_perf0>, <&pd_perf1>; //both
> > pd_perf0 and pd_perf1 has OPP tables.
> >                 power-domain-names = "perf0", "perf1";
> >                 operating-points-v2 = <&opp_table_devA>;
> >         };
> >
> > Here, I don't think there is a way for us to know which genpd does
> > opp_pd_50 belongs to and to which one opp_pd_51 does.
> >
> > We solve this by sending clock_names and regulator_names in OPP
> > config structure. That gives the ordering in which required_opps are
> > present. The same needs to be done for genpd, and then genpd core
> > would be able to attach the right genpd with right required opp.
> 
> No, we don't need this for gend as $subject patch is addressing this
> problem too. Let me elaborate.
> 
> The OPP core holds the information about the devA's required-opps and
> to what OPP table each required-opps belongs to
> (opp_table->required_opp_tables[n]).
> 
> The genpd core holds the information about the allocated virtual
> devices that it creates when it attached devA to its power-domains.
> The virtual device(s) gets a genpd attached to it and that genpd also
> has an OPP table associated with it (genpd->opp_table).
> 
> By asking the OPP core to walk through the array of allocated
> required-opps for devA and to match it against a *one* of the virtual
> devices' genpd->opp_table, we can figure out at what index we should
> assign the virtual device to in the opp_table->required_devs[index].

How do we differentiate between two cases where the required-opps can
be defined as either of these:

required-opps = <&opp_pd_50, &opp_pd_51>; //corresponds to pd_perf1 and pd_perf0 (in reverse order)

OR

required-opps = <&opp_pd_51, &opp_pd_50>; //corresponds to pd_perf0 and pd_perf1

I thought this can't be fixed without some platform code telling how
the DT is really configured, i.e. order of the power domains in the
required-opps.
Ulf Hansson Sept. 6, 2024, 8:49 a.m. UTC | #2
On Fri, 6 Sept 2024 at 08:14, Viresh Kumar <viresh.kumar@linaro.org> wrote:
>
> On 04-09-24, 14:57, Ulf Hansson wrote:
> > > Yeah, I missed that, it doesn't happen via DT but by platform code. I
> > > do see problems where situation would be a bit ambiguous. Your example
> > > with a minor change to your code:
> > >
> > >         opp_table_devA: opp-table-devA {
> > >                 compatible = "operating-points-v2";
> > >
> > >                 opp-devA-50 {
> > >                         opp-hz = /bits/ 64 <2500>;
> > >                         required-opps = <&opp_pd_50, &opp_pd_51>; //corresponds to pd_perf1 and pd_perf0 (in reverse order)
> > >                 };
> > >                ....
> > >
> > >         devA {
> > >                 compatible = "foo,bar";
> > >                 power-domains = <&pd_perf0>, <&pd_perf1>; //both
> > > pd_perf0 and pd_perf1 has OPP tables.
> > >                 power-domain-names = "perf0", "perf1";
> > >                 operating-points-v2 = <&opp_table_devA>;
> > >         };
> > >
> > > Here, I don't think there is a way for us to know which genpd does
> > > opp_pd_50 belongs to and to which one opp_pd_51 does.
> > >
> > > We solve this by sending clock_names and regulator_names in OPP
> > > config structure. That gives the ordering in which required_opps are
> > > present. The same needs to be done for genpd, and then genpd core
> > > would be able to attach the right genpd with right required opp.
> >
> > No, we don't need this for gend as $subject patch is addressing this
> > problem too. Let me elaborate.
> >
> > The OPP core holds the information about the devA's required-opps and
> > to what OPP table each required-opps belongs to
> > (opp_table->required_opp_tables[n]).
> >
> > The genpd core holds the information about the allocated virtual
> > devices that it creates when it attached devA to its power-domains.
> > The virtual device(s) gets a genpd attached to it and that genpd also
> > has an OPP table associated with it (genpd->opp_table).
> >
> > By asking the OPP core to walk through the array of allocated
> > required-opps for devA and to match it against a *one* of the virtual
> > devices' genpd->opp_table, we can figure out at what index we should
> > assign the virtual device to in the opp_table->required_devs[index].
>
> How do we differentiate between two cases where the required-opps can
> be defined as either of these:
>
> required-opps = <&opp_pd_50, &opp_pd_51>; //corresponds to pd_perf1 and pd_perf0 (in reverse order)
>
> OR
>
> required-opps = <&opp_pd_51, &opp_pd_50>; //corresponds to pd_perf0 and pd_perf1
>
> I thought this can't be fixed without some platform code telling how
> the DT is really configured, i.e. order of the power domains in the
> required-opps.

I don't think we need platform code for this.

When registering a genpd provider, an OPP table gets assigned to it.
When hooking up a device to one of its genpd providers, that virtual
device then also gets a handle to its genpd's OPP table.

Each of the phandles in the required-opps points to another OPP table,
which OPP table should be associated with a specific genpd.

In other words, the information is there, we should not need anything
additional in DT.

Kind regards
Uffe
Viresh Kumar Sept. 11, 2024, 6:02 a.m. UTC | #3
FYI, I am on holidays now :)

On Fri, 6 Sept 2024 at 14:19, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> > How do we differentiate between two cases where the required-opps can
> > be defined as either of these:
> >
> > required-opps = <&opp_pd_50, &opp_pd_51>; //corresponds to pd_perf1 and pd_perf0 (in reverse order)
> >
> > OR
> >
> > required-opps = <&opp_pd_51, &opp_pd_50>; //corresponds to pd_perf0 and pd_perf1
> >
> > I thought this can't be fixed without some platform code telling how
> > the DT is really configured, i.e. order of the power domains in the
> > required-opps.
>
> I don't think we need platform code for this.
>
> When registering a genpd provider, an OPP table gets assigned to it.

So we will create a real OPP table in code, which will point to the common
OPP table in DT. Fine.

> When hooking up a device to one of its genpd providers, that virtual
> device then also gets a handle to its genpd's OPP table.

Right.

If there are two genpds required for a device from the same genpd provider, the
picture isn't very clear at this point. i.e. which required OPP
belongs to which genpd,
as both have same table in DT.

> Each of the phandles in the required-opps points to another OPP table,
> which OPP table should be associated with a specific genpd.

Yes, but a simple order reversal in DT (which I sent in my last
email), will not be picked
by code at all. i.e. DT doesn't give the order in which required OPPs
are present.

> In other words, the information is there, we should not need anything
> additional in DT.
Ulf Hansson Sept. 12, 2024, 10:13 a.m. UTC | #4
On Wed, 11 Sept 2024 at 16:15, Ulf Hansson <ulf.hansson@linaro.org> wrote:
>
> On Wed, 11 Sept 2024 at 08:03, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> >
> > FYI, I am on holidays now :)
>
> Oh, nice! Enjoy!
>
> >
> > On Fri, 6 Sept 2024 at 14:19, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> > > > How do we differentiate between two cases where the required-opps can
> > > > be defined as either of these:
> > > >
> > > > required-opps = <&opp_pd_50, &opp_pd_51>; //corresponds to pd_perf1 and pd_perf0 (in reverse order)
> > > >
> > > > OR
> > > >
> > > > required-opps = <&opp_pd_51, &opp_pd_50>; //corresponds to pd_perf0 and pd_perf1
> > > >
> > > > I thought this can't be fixed without some platform code telling how
> > > > the DT is really configured, i.e. order of the power domains in the
> > > > required-opps.
> > >
> > > I don't think we need platform code for this.
> > >
> > > When registering a genpd provider, an OPP table gets assigned to it.
> >
> > So we will create a real OPP table in code, which will point to the common
> > OPP table in DT. Fine.
> >
> > > When hooking up a device to one of its genpd providers, that virtual
> > > device then also gets a handle to its genpd's OPP table.
> >
> > Right.
> >
> > If there are two genpds required for a device from the same genpd provider, the
> > picture isn't very clear at this point. i.e. which required OPP
> > belongs to which genpd,
> > as both have same table in DT.
>
> I agree that it's not very clear.
>
> But to me, this seems like an orthogonal problem that really should
> not be managed by platform specific code in consumer drivers.
> Moreover, unless I am mistaken, I believe this isn't really a problem
> for the currently supported use cases we have for required-opps. Or is
> it?

Answering my own question. After some further investigation, I am
afraid that your concern was correct.

One sm8250, venus is using three power-domains,"venus", "vcodec0",
"mx", but there is only one phandle in the required-opp.
"venus" and "vcodec0" correspond to the "videocc" power-domain, which
has a parent-domain that is the "rpmhpd".
"mx" corresponds to the "rpmhpd".
The rpmhpd power-domain has one shared OPP table used for all the
power-domains it provides. :-(

Because we also need to look for a matching OPP table for the
required-opp by walking the power-domain parents (needed on Tegra), we
simply can't tell what power-domain the required-opp belongs to.

>
> That said, we already have two methods that helps us to deal with this issue:
>
> 1)
> For a genpd OF provider that provides multiple genpds, the genpd/OPP
> core tries to assign an OPP table for each genpd, based on the
> power-domain index. In other words, if corresponding OPP-tables are
> specified in the operating-points-v2 list, those would get assigned
> accordingly.
>
> 2)
> The genpd OF provider can control on a per genpd basis, whether there
> should be an OPP table assigned to it. This is managed by assigning
> the ->set_performance_state() callback for the genpd or leaving it
> unassigned. Typically this works well, when there is one OPP-table
> specified in the operating-points-v2 list for the provider - and only
> one of the genpds that should use it.
>
> If it turns out that we need something more flexible, I think we need
> to look at extending the OPP/power-domain DT bindings. We would
> probably need a "by-names" DT property, allowing us to specify the
> mapping between the OPP-tables and the power-domains.
>
> >
> > > Each of the phandles in the required-opps points to another OPP table,
> > > which OPP table should be associated with a specific genpd.
> >
> > Yes, but a simple order reversal in DT (which I sent in my last
> > email), will not be picked
> > by code at all. i.e. DT doesn't give the order in which required OPPs
> > are present.
>
> Assuming genpd OF providers are following 1) or 2), I don't think this
> should be an issue.

It looks like I was wrong.

This whole problem boils down to that we are allowing the OPP table to
be shared for a genpd OF provider for multiple power-domains. We could
consider adding some new DT property to point out at what power-domain
the required-opps belongs to, but it doesn't really matter as we need
to keep supporting the current DTS.

Oh well, back to the drawing table to re-work this again. It looks
like we need to make it possible for consumer drivers to provide
additional information to dev_pm_domain_attach_list(), allowing it to
understand how the required-devs should be assigned.

Unless you have some better ideas?

[...]

Kind regards
Uffe
diff mbox series

Patch

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 66cac7a1d9db..538612886446 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -2363,7 +2363,7 @@  static void _opp_put_config_regulators_helper(struct opp_table *opp_table)
 static int _opp_set_required_dev(struct opp_table *opp_table,
 				 struct device *dev,
 				 struct device *required_dev,
-				 struct opp_table *required_opp_table)
+				 config_required_dev_t config_required_dev)
 {
 	int i;
 
@@ -2380,6 +2380,7 @@  static int _opp_set_required_dev(struct opp_table *opp_table,
 
 	for (i = 0; i < opp_table->required_opp_count; i++) {
 		struct opp_table *table = opp_table->required_opp_tables[i];
+		struct opp_table *required_opp_table;
 
 		/*
 		 * The OPP table should be available at this point. If not, it's
@@ -2396,7 +2397,9 @@  static int _opp_set_required_dev(struct opp_table *opp_table,
 		 * We need to compare the nodes for the OPP tables, rather than
 		 * the OPP tables themselves, as we may have separate instances.
 		 */
-		if (required_opp_table->np == table->np) {
+		required_opp_table = config_required_dev(required_dev,
+							 table->np);
+		if (required_opp_table) {
 			/*
 			 * The required_opp_tables parsing is not perfect, as
 			 * the OPP core does the parsing solely based on the DT
@@ -2422,8 +2425,8 @@  static int _opp_set_required_dev(struct opp_table *opp_table,
 		}
 	}
 
-	dev_err(dev, "Missing OPP table, unable to set the required dev\n");
-	return -ENODEV;
+	/* No matching OPP table found for the required_dev. */
+	return -ERANGE;
 }
 
 static void _opp_put_required_dev(struct opp_table *opp_table,
@@ -2547,10 +2550,10 @@  int dev_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config)
 		data->flags |= OPP_CONFIG_REGULATOR;
 	}
 
-	if (config->required_dev && config->required_opp_table) {
+	if (config->required_dev && config->config_required_dev) {
 		ret = _opp_set_required_dev(opp_table, dev,
 					    config->required_dev,
-					    config->required_opp_table);
+					    config->config_required_dev);
 		if (ret < 0)
 			goto err;
 
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index edef1a520110..0ff0b513b2a1 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -2874,20 +2874,21 @@  static void genpd_dev_pm_sync(struct device *dev)
 	genpd_queue_power_off_work(pd);
 }
 
-static struct opp_table *genpd_find_opp_table(struct generic_pm_domain *genpd,
-					      unsigned int depth)
+static struct opp_table *_genpd_find_opp_table(struct generic_pm_domain *genpd,
+					       struct device_node *np,
+					       unsigned int depth)
 {
-	struct opp_table *opp_table;
+	struct opp_table *opp_table = genpd->opp_table;
 	struct gpd_link *link;
 
-	if (genpd->opp_table)
-		return genpd->opp_table;
+	if (opp_table && (dev_pm_opp_table_to_of_node(opp_table) == np))
+		return opp_table;
 
 	list_for_each_entry(link, &genpd->child_links, child_node) {
 		struct generic_pm_domain *parent = link->parent;
 
 		genpd_lock_nested(parent, depth + 1);
-		opp_table = genpd_find_opp_table(parent, depth + 1);
+		opp_table = _genpd_find_opp_table(parent, np, depth + 1);
 		genpd_unlock(parent);
 
 		if (opp_table)
@@ -2897,12 +2898,27 @@  static struct opp_table *genpd_find_opp_table(struct generic_pm_domain *genpd,
 	return NULL;
 }
 
-static int genpd_set_required_opp_dev(struct device *dev,
-				      struct device *base_dev)
+static struct opp_table *genpd_find_opp_table(struct device *dev,
+					      struct device_node *np)
 {
 	struct generic_pm_domain *genpd = dev_to_genpd(dev);
 	struct opp_table *opp_table;
-	int ret = 0;
+
+	genpd_lock(genpd);
+	opp_table = _genpd_find_opp_table(genpd, np, 0);
+	genpd_unlock(genpd);
+
+	return opp_table;
+}
+
+static int genpd_set_required_opp_dev(struct device *dev,
+				      struct device *base_dev)
+{
+	struct dev_pm_opp_config config = {
+		.required_dev = dev,
+		.config_required_dev = genpd_find_opp_table,
+	};
+	int ret;
 
 	/* Limit support to non-providers for now. */
 	if (of_property_present(base_dev->of_node, "#power-domain-cells"))
@@ -2911,20 +2927,10 @@  static int genpd_set_required_opp_dev(struct device *dev,
 	if (!dev_pm_opp_of_has_required_opp(base_dev))
 		return 0;
 
-	genpd_lock(genpd);
-	opp_table = genpd_find_opp_table(genpd, 0);
-	genpd_unlock(genpd);
-
-	if (opp_table) {
-		struct dev_pm_opp_config config = {
-			.required_dev = dev,
-			.required_opp_table = opp_table,
-		};
-
-		ret = devm_pm_opp_set_config(base_dev, &config);
-		if (ret < 0)
-			dev_err(dev, "failed to set opp config %d\n", ret);
-	}
+	ret = devm_pm_opp_set_config(base_dev, &config);
+	ret = ret == -ERANGE ? 0 : ret;
+	if (ret < 0)
+		dev_err(dev, "failed to set opp config %d\n", ret);
 
 	return ret;
 }
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 7894e631cded..0ada4a5057c8 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -53,6 +53,9 @@  typedef int (*config_regulators_t)(struct device *dev,
 typedef int (*config_clks_t)(struct device *dev, struct opp_table *opp_table,
 			struct dev_pm_opp *opp, void *data, bool scaling_down);
 
+typedef struct opp_table *(*config_required_dev_t)(struct device *dev,
+			struct device_node *opp_table_np);
+
 /**
  * struct dev_pm_opp_config - Device OPP configuration values
  * @clk_names: Clk names, NULL terminated array.
@@ -63,7 +66,7 @@  typedef int (*config_clks_t)(struct device *dev, struct opp_table *opp_table,
  * @supported_hw_count: Number of elements in the array.
  * @regulator_names: Array of pointers to the names of the regulator, NULL terminated.
  * @required_dev: Required OPP device.
- * @required_opp_table: The corresponding required OPP table for @required_dev.
+ * @config_required_dev: Custom helper to find the required OPP table for $required_dev.
  *
  * This structure contains platform specific OPP configurations for the device.
  */
@@ -77,7 +80,7 @@  struct dev_pm_opp_config {
 	unsigned int supported_hw_count;
 	const char * const *regulator_names;
 	struct device *required_dev;
-	struct opp_table *required_opp_table;
+	config_required_dev_t config_required_dev;
 };
 
 #define OPP_LEVEL_UNSET			U32_MAX