diff mbox

PM / Sleep: Fall back to subsystem level PM callbacks for PM domains

Message ID 1398422695-2295-1-git-send-email-ulf.hansson@linaro.org
State New
Headers show

Commit Message

Ulf Hansson April 25, 2014, 10:44 a.m. UTC
Previously once the PM core found a PM domain pointer for a device,
but which didn't have a valid PM callback, it falled back to try the
driver's PM callback.

In this scenario, change the behavior of the PM core to try out the
other subsystem level PM callbacks, before it moves on to the driver.

This gives provision for PM domains to easier re-use subsystem level
code to handle the needed operations.

Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
---
 Documentation/power/devices.txt |    6 +++--
 drivers/base/power/main.c       |   55 +++++++++++++++++++++++++++++++--------
 2 files changed, 48 insertions(+), 13 deletions(-)

Comments

Rafael J. Wysocki April 29, 2014, 10:47 p.m. UTC | #1
On Friday, April 25, 2014 12:44:55 PM Ulf Hansson wrote:
> Previously once the PM core found a PM domain pointer for a device,
> but which didn't have a valid PM callback, it falled back to try the
> driver's PM callback.
> 
> In this scenario, change the behavior of the PM core to try out the
> other subsystem level PM callbacks, before it moves on to the driver.
> 
> This gives provision for PM domains to easier re-use subsystem level
> code to handle the needed operations.
> 
> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>

Are you sure this is not going to break the existing PM domains?

> ---
>  Documentation/power/devices.txt |    6 +++--
>  drivers/base/power/main.c       |   55 +++++++++++++++++++++++++++++++--------
>  2 files changed, 48 insertions(+), 13 deletions(-)
> 
> diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt
> index 47d46df..c875056 100644
> --- a/Documentation/power/devices.txt
> +++ b/Documentation/power/devices.txt
> @@ -303,8 +303,10 @@ The PM domain, type, class and bus callbacks may in turn invoke device- or
>  driver-specific methods stored in dev->driver->pm, but they don't have to do
>  that.
>  
> -If the subsystem callback chosen for execution is not present, the PM core will
> -execute the corresponding method from dev->driver->pm instead if there is one.
> +If a PM domain exist, but the callback chosen for execution isn't present, the
> +PM core will fall back to try the other subsystem level callbacks.  Finally, if
> +no subsystem level callback is found, it executes the corresponding method from
> +dev->driver->pm instead if there is one.
>  
>  
>  Entering System Suspend
> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> index 86d5e4f..e604db9 100644
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -490,7 +490,11 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
>  	if (dev->pm_domain) {
>  		info = "noirq power domain ";
>  		callback = pm_noirq_op(&dev->pm_domain->ops, state);
> -	} else if (dev->type && dev->type->pm) {
> +		if (callback)
> +			goto End;
> +	}
> +
> +	if (dev->type && dev->type->pm) {
>  		info = "noirq type ";
>  		callback = pm_noirq_op(dev->type->pm, state);
>  	} else if (dev->class && dev->class->pm) {
> @@ -506,6 +510,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
>  		callback = pm_noirq_op(dev->driver->pm, state);
>  	}
>  
> + End:
>  	error = dpm_run_callback(callback, dev, state, info);
>  	dev->power.is_noirq_suspended = false;
>  
> @@ -616,7 +621,11 @@ static int device_resume_early(struct device *dev, pm_message_t state, bool asyn
>  	if (dev->pm_domain) {
>  		info = "early power domain ";
>  		callback = pm_late_early_op(&dev->pm_domain->ops, state);
> -	} else if (dev->type && dev->type->pm) {
> +		if (callback)
> +			goto End;
> +	}
> +
> +	if (dev->type && dev->type->pm) {
>  		info = "early type ";
>  		callback = pm_late_early_op(dev->type->pm, state);
>  	} else if (dev->class && dev->class->pm) {
> @@ -632,6 +641,7 @@ static int device_resume_early(struct device *dev, pm_message_t state, bool asyn
>  		callback = pm_late_early_op(dev->driver->pm, state);
>  	}
>  
> + End:
>  	error = dpm_run_callback(callback, dev, state, info);
>  	dev->power.is_late_suspended = false;
>  
> @@ -751,7 +761,8 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
>  	if (dev->pm_domain) {
>  		info = "power domain ";
>  		callback = pm_op(&dev->pm_domain->ops, state);
> -		goto Driver;
> +		if (callback)
> +			goto End;
>  	}
>  
>  	if (dev->type && dev->type->pm) {
> @@ -889,7 +900,11 @@ static void device_complete(struct device *dev, pm_message_t state)
>  	if (dev->pm_domain) {
>  		info = "completing power domain ";
>  		callback = dev->pm_domain->ops.complete;
> -	} else if (dev->type && dev->type->pm) {
> +		if (callback)
> +			goto End;
> +	}
> +
> +	if (dev->type && dev->type->pm) {
>  		info = "completing type ";
>  		callback = dev->type->pm->complete;
>  	} else if (dev->class && dev->class->pm) {
> @@ -905,6 +920,7 @@ static void device_complete(struct device *dev, pm_message_t state)
>  		callback = dev->driver->pm->complete;
>  	}
>  
> + End:
>  	if (callback) {
>  		pm_dev_dbg(dev, state, info);
>  		callback(dev);
> @@ -1015,7 +1031,11 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
>  	if (dev->pm_domain) {
>  		info = "noirq power domain ";
>  		callback = pm_noirq_op(&dev->pm_domain->ops, state);
> -	} else if (dev->type && dev->type->pm) {
> +		if (callback)
> +			goto End;
> +	}
> +
> +	if (dev->type && dev->type->pm) {
>  		info = "noirq type ";
>  		callback = pm_noirq_op(dev->type->pm, state);
>  	} else if (dev->class && dev->class->pm) {
> @@ -1031,6 +1051,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
>  		callback = pm_noirq_op(dev->driver->pm, state);
>  	}
>  
> + End:
>  	error = dpm_run_callback(callback, dev, state, info);
>  	if (!error)
>  		dev->power.is_noirq_suspended = true;
> @@ -1154,7 +1175,11 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
>  	if (dev->pm_domain) {
>  		info = "late power domain ";
>  		callback = pm_late_early_op(&dev->pm_domain->ops, state);
> -	} else if (dev->type && dev->type->pm) {
> +		if (callback)
> +			goto End;
> +	}
> +
> +	if (dev->type && dev->type->pm) {
>  		info = "late type ";
>  		callback = pm_late_early_op(dev->type->pm, state);
>  	} else if (dev->class && dev->class->pm) {
> @@ -1170,6 +1195,7 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
>  		callback = pm_late_early_op(dev->driver->pm, state);
>  	}
>  
> + End:
>  	error = dpm_run_callback(callback, dev, state, info);
>  	if (!error)
>  		dev->power.is_late_suspended = true;
> @@ -1338,20 +1364,21 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
>  	if (dev->pm_domain) {
>  		info = "power domain ";
>  		callback = pm_op(&dev->pm_domain->ops, state);
> -		goto Run;
> +		if (callback)
> +			goto Run;
>  	}
>  
>  	if (dev->type && dev->type->pm) {
>  		info = "type ";
>  		callback = pm_op(dev->type->pm, state);
> -		goto Run;
> +		goto Driver;
>  	}
>  
>  	if (dev->class) {
>  		if (dev->class->pm) {
>  			info = "class ";
>  			callback = pm_op(dev->class->pm, state);
> -			goto Run;
> +			goto Driver;
>  		} else if (dev->class->suspend) {
>  			pm_dev_dbg(dev, state, "legacy class ");
>  			error = legacy_suspend(dev, state, dev->class->suspend,
> @@ -1372,12 +1399,13 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
>  		}
>  	}
>  
> - Run:
> + Driver:
>  	if (!callback && dev->driver && dev->driver->pm) {
>  		info = "driver ";
>  		callback = pm_op(dev->driver->pm, state);
>  	}
>  
> + Run:
>  	error = dpm_run_callback(callback, dev, state, info);
>  
>   End:
> @@ -1507,7 +1535,11 @@ static int device_prepare(struct device *dev, pm_message_t state)
>  	if (dev->pm_domain) {
>  		info = "preparing power domain ";
>  		callback = dev->pm_domain->ops.prepare;
> -	} else if (dev->type && dev->type->pm) {
> +		if (callback)
> +			goto End;
> +	}
> +
> +	if (dev->type && dev->type->pm) {
>  		info = "preparing type ";
>  		callback = dev->type->pm->prepare;
>  	} else if (dev->class && dev->class->pm) {
> @@ -1523,6 +1555,7 @@ static int device_prepare(struct device *dev, pm_message_t state)
>  		callback = dev->driver->pm->prepare;
>  	}
>  
> + End:
>  	if (callback) {
>  		error = callback(dev);
>  		suspend_report_result(callback, error);
>
Rafael J. Wysocki April 29, 2014, 10:52 p.m. UTC | #2
On Wednesday, April 30, 2014 12:47:28 AM Rafael J. Wysocki wrote:
> On Friday, April 25, 2014 12:44:55 PM Ulf Hansson wrote:
> > Previously once the PM core found a PM domain pointer for a device,
> > but which didn't have a valid PM callback, it falled back to try the
> > driver's PM callback.
> > 
> > In this scenario, change the behavior of the PM core to try out the
> > other subsystem level PM callbacks, before it moves on to the driver.
> > 
> > This gives provision for PM domains to easier re-use subsystem level
> > code to handle the needed operations.
> > 
> > Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
> 
> Are you sure this is not going to break the existing PM domains?

And even if it isn't, the idea behind PM domains was to provide a mechanism to
bypass the bus types' (etc) PM callbacks, so I'm not liking this patch at all.

Thanks!
Ulf Hansson April 30, 2014, 12:15 p.m. UTC | #3
On 30 April 2014 00:52, Rafael J. Wysocki <rjw@rjwysocki.net> wrote:
> On Wednesday, April 30, 2014 12:47:28 AM Rafael J. Wysocki wrote:
>> On Friday, April 25, 2014 12:44:55 PM Ulf Hansson wrote:
>> > Previously once the PM core found a PM domain pointer for a device,
>> > but which didn't have a valid PM callback, it falled back to try the
>> > driver's PM callback.
>> >
>> > In this scenario, change the behavior of the PM core to try out the
>> > other subsystem level PM callbacks, before it moves on to the driver.
>> >
>> > This gives provision for PM domains to easier re-use subsystem level
>> > code to handle the needed operations.
>> >
>> > Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
>>
>> Are you sure this is not going to break the existing PM domains?

I have checked the current implementations of the PM domains - all are
providing the PM callbacks. So this should be safe.

>
> And even if it isn't, the idea behind PM domains was to provide a mechanism to
> bypass the bus types' (etc) PM callbacks, so I'm not liking this patch at all.

I understand your view, it's the current mindset we have of the
hierarchy of handling the callbacks. I am wondering if it's time to
reconsider. :-)

To be clear, I don't want to prevent the PM domain from bypassing bus,
types etc, that's should be up to each implementation to decide, and
this patch won't affect that behaviour.

I intend to only simplify for those PM domains that want to re-use the
callbacks from bus, types. etc. Currently that's the most of them.

a)
Those ARM SOCs that implements PM domains, which don't use the generic
power domain, cares only about platform devices attached to the
platform_bus_type. These are copying the callbacks from the
platform_bus_type (using the USE_PLATFORM_PM_SLEEP_OPS macro)  - I
assume this is because they need to handle legacy suspend/resume.

arch/arm/mach-davinci/pm_domain.c
arch/arm/mach-keystone/pm_domain.c
arch/arm/mach-omap1/pm_bus.c
arch/arm/mach-omap2/omap_device.c
drivers/sh/pm_runtime.c

b)
An actual copy of the bus' PM callbacks to the PM domain's callbacks also exist.

drivers/gpu/vga/vga_switcheroo.c:
vga_switcheroo_init_domain_pm_ops()
vga_switcheroo_init_domain_pm_optimus_hdmi_audio()

c)
That leaves the generic power domain and acpi power domain, which
don't re-use callbacks, but have their own set.


The more complicated scenario have not yet been implemented - but I
can see it coming. :-)
That's when the PM domain wants to re-use callbacks for whatever bus,
types etc, the device is attached to.

To handle this, the PM domain will have to re-implement the code for
walking the hierarchy of callbacks, which is a bit messy. This patch
will make it possible for the PM domain to rely on the PM core to
handle this instead.

Kind regards
Ulf Hansson

>
> Thanks!
>
>
> --
> I speak only for myself.
> Rafael J. Wysocki, Intel Open Source Technology Center.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ulf Hansson May 21, 2014, 9:36 a.m. UTC | #4
On 30 April 2014 14:15, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> On 30 April 2014 00:52, Rafael J. Wysocki <rjw@rjwysocki.net> wrote:
>> On Wednesday, April 30, 2014 12:47:28 AM Rafael J. Wysocki wrote:
>>> On Friday, April 25, 2014 12:44:55 PM Ulf Hansson wrote:
>>> > Previously once the PM core found a PM domain pointer for a device,
>>> > but which didn't have a valid PM callback, it falled back to try the
>>> > driver's PM callback.
>>> >
>>> > In this scenario, change the behavior of the PM core to try out the
>>> > other subsystem level PM callbacks, before it moves on to the driver.
>>> >
>>> > This gives provision for PM domains to easier re-use subsystem level
>>> > code to handle the needed operations.
>>> >
>>> > Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
>>>
>>> Are you sure this is not going to break the existing PM domains?
>
> I have checked the current implementations of the PM domains - all are
> providing the PM callbacks. So this should be safe.
>
>>
>> And even if it isn't, the idea behind PM domains was to provide a mechanism to
>> bypass the bus types' (etc) PM callbacks, so I'm not liking this patch at all.
>
> I understand your view, it's the current mindset we have of the
> hierarchy of handling the callbacks. I am wondering if it's time to
> reconsider. :-)
>
> To be clear, I don't want to prevent the PM domain from bypassing bus,
> types etc, that's should be up to each implementation to decide, and
> this patch won't affect that behaviour.
>
> I intend to only simplify for those PM domains that want to re-use the
> callbacks from bus, types. etc. Currently that's the most of them.
>
> a)
> Those ARM SOCs that implements PM domains, which don't use the generic
> power domain, cares only about platform devices attached to the
> platform_bus_type. These are copying the callbacks from the
> platform_bus_type (using the USE_PLATFORM_PM_SLEEP_OPS macro)  - I
> assume this is because they need to handle legacy suspend/resume.
>
> arch/arm/mach-davinci/pm_domain.c
> arch/arm/mach-keystone/pm_domain.c
> arch/arm/mach-omap1/pm_bus.c
> arch/arm/mach-omap2/omap_device.c
> drivers/sh/pm_runtime.c
>
> b)
> An actual copy of the bus' PM callbacks to the PM domain's callbacks also exist.
>
> drivers/gpu/vga/vga_switcheroo.c:
> vga_switcheroo_init_domain_pm_ops()
> vga_switcheroo_init_domain_pm_optimus_hdmi_audio()
>
> c)
> That leaves the generic power domain and acpi power domain, which
> don't re-use callbacks, but have their own set.
>
>
> The more complicated scenario have not yet been implemented - but I
> can see it coming. :-)
> That's when the PM domain wants to re-use callbacks for whatever bus,
> types etc, the device is attached to.
>
> To handle this, the PM domain will have to re-implement the code for
> walking the hierarchy of callbacks, which is a bit messy. This patch
> will make it possible for the PM domain to rely on the PM core to
> handle this instead.
>

Hi Rafael,

Just wanted to understand if you are still considering this patch or
if think it's the wrong approach?

Kind regards
Ulf Hansson
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rafael J. Wysocki May 21, 2014, 11:05 a.m. UTC | #5
On Wednesday, May 21, 2014 11:36:20 AM Ulf Hansson wrote:
> On 30 April 2014 14:15, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> > On 30 April 2014 00:52, Rafael J. Wysocki <rjw@rjwysocki.net> wrote:
> >> On Wednesday, April 30, 2014 12:47:28 AM Rafael J. Wysocki wrote:
> >>> On Friday, April 25, 2014 12:44:55 PM Ulf Hansson wrote:
> >>> > Previously once the PM core found a PM domain pointer for a device,
> >>> > but which didn't have a valid PM callback, it falled back to try the
> >>> > driver's PM callback.
> >>> >
> >>> > In this scenario, change the behavior of the PM core to try out the
> >>> > other subsystem level PM callbacks, before it moves on to the driver.
> >>> >
> >>> > This gives provision for PM domains to easier re-use subsystem level
> >>> > code to handle the needed operations.
> >>> >
> >>> > Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
> >>>
> >>> Are you sure this is not going to break the existing PM domains?
> >
> > I have checked the current implementations of the PM domains - all are
> > providing the PM callbacks. So this should be safe.
> >
> >>
> >> And even if it isn't, the idea behind PM domains was to provide a mechanism to
> >> bypass the bus types' (etc) PM callbacks, so I'm not liking this patch at all.
> >
> > I understand your view, it's the current mindset we have of the
> > hierarchy of handling the callbacks. I am wondering if it's time to
> > reconsider. :-)
> >
> > To be clear, I don't want to prevent the PM domain from bypassing bus,
> > types etc, that's should be up to each implementation to decide, and
> > this patch won't affect that behaviour.
> >
> > I intend to only simplify for those PM domains that want to re-use the
> > callbacks from bus, types. etc. Currently that's the most of them.
> >
> > a)
> > Those ARM SOCs that implements PM domains, which don't use the generic
> > power domain, cares only about platform devices attached to the
> > platform_bus_type. These are copying the callbacks from the
> > platform_bus_type (using the USE_PLATFORM_PM_SLEEP_OPS macro)  - I
> > assume this is because they need to handle legacy suspend/resume.
> >
> > arch/arm/mach-davinci/pm_domain.c
> > arch/arm/mach-keystone/pm_domain.c
> > arch/arm/mach-omap1/pm_bus.c
> > arch/arm/mach-omap2/omap_device.c
> > drivers/sh/pm_runtime.c
> >
> > b)
> > An actual copy of the bus' PM callbacks to the PM domain's callbacks also exist.
> >
> > drivers/gpu/vga/vga_switcheroo.c:
> > vga_switcheroo_init_domain_pm_ops()
> > vga_switcheroo_init_domain_pm_optimus_hdmi_audio()
> >
> > c)
> > That leaves the generic power domain and acpi power domain, which
> > don't re-use callbacks, but have their own set.
> >
> >
> > The more complicated scenario have not yet been implemented - but I
> > can see it coming. :-)
> > That's when the PM domain wants to re-use callbacks for whatever bus,
> > types etc, the device is attached to.
> >
> > To handle this, the PM domain will have to re-implement the code for
> > walking the hierarchy of callbacks, which is a bit messy. This patch
> > will make it possible for the PM domain to rely on the PM core to
> > handle this instead.
> >
> 
> Hi Rafael,
> 
> Just wanted to understand if you are still considering this patch or
> if think it's the wrong approach?

I said I didn't like the approach IIRC.  I'm not considering it anyway.

Rafael

--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt
index 47d46df..c875056 100644
--- a/Documentation/power/devices.txt
+++ b/Documentation/power/devices.txt
@@ -303,8 +303,10 @@  The PM domain, type, class and bus callbacks may in turn invoke device- or
 driver-specific methods stored in dev->driver->pm, but they don't have to do
 that.
 
-If the subsystem callback chosen for execution is not present, the PM core will
-execute the corresponding method from dev->driver->pm instead if there is one.
+If a PM domain exist, but the callback chosen for execution isn't present, the
+PM core will fall back to try the other subsystem level callbacks.  Finally, if
+no subsystem level callback is found, it executes the corresponding method from
+dev->driver->pm instead if there is one.
 
 
 Entering System Suspend
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 86d5e4f..e604db9 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -490,7 +490,11 @@  static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
 	if (dev->pm_domain) {
 		info = "noirq power domain ";
 		callback = pm_noirq_op(&dev->pm_domain->ops, state);
-	} else if (dev->type && dev->type->pm) {
+		if (callback)
+			goto End;
+	}
+
+	if (dev->type && dev->type->pm) {
 		info = "noirq type ";
 		callback = pm_noirq_op(dev->type->pm, state);
 	} else if (dev->class && dev->class->pm) {
@@ -506,6 +510,7 @@  static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
 		callback = pm_noirq_op(dev->driver->pm, state);
 	}
 
+ End:
 	error = dpm_run_callback(callback, dev, state, info);
 	dev->power.is_noirq_suspended = false;
 
@@ -616,7 +621,11 @@  static int device_resume_early(struct device *dev, pm_message_t state, bool asyn
 	if (dev->pm_domain) {
 		info = "early power domain ";
 		callback = pm_late_early_op(&dev->pm_domain->ops, state);
-	} else if (dev->type && dev->type->pm) {
+		if (callback)
+			goto End;
+	}
+
+	if (dev->type && dev->type->pm) {
 		info = "early type ";
 		callback = pm_late_early_op(dev->type->pm, state);
 	} else if (dev->class && dev->class->pm) {
@@ -632,6 +641,7 @@  static int device_resume_early(struct device *dev, pm_message_t state, bool asyn
 		callback = pm_late_early_op(dev->driver->pm, state);
 	}
 
+ End:
 	error = dpm_run_callback(callback, dev, state, info);
 	dev->power.is_late_suspended = false;
 
@@ -751,7 +761,8 @@  static int device_resume(struct device *dev, pm_message_t state, bool async)
 	if (dev->pm_domain) {
 		info = "power domain ";
 		callback = pm_op(&dev->pm_domain->ops, state);
-		goto Driver;
+		if (callback)
+			goto End;
 	}
 
 	if (dev->type && dev->type->pm) {
@@ -889,7 +900,11 @@  static void device_complete(struct device *dev, pm_message_t state)
 	if (dev->pm_domain) {
 		info = "completing power domain ";
 		callback = dev->pm_domain->ops.complete;
-	} else if (dev->type && dev->type->pm) {
+		if (callback)
+			goto End;
+	}
+
+	if (dev->type && dev->type->pm) {
 		info = "completing type ";
 		callback = dev->type->pm->complete;
 	} else if (dev->class && dev->class->pm) {
@@ -905,6 +920,7 @@  static void device_complete(struct device *dev, pm_message_t state)
 		callback = dev->driver->pm->complete;
 	}
 
+ End:
 	if (callback) {
 		pm_dev_dbg(dev, state, info);
 		callback(dev);
@@ -1015,7 +1031,11 @@  static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
 	if (dev->pm_domain) {
 		info = "noirq power domain ";
 		callback = pm_noirq_op(&dev->pm_domain->ops, state);
-	} else if (dev->type && dev->type->pm) {
+		if (callback)
+			goto End;
+	}
+
+	if (dev->type && dev->type->pm) {
 		info = "noirq type ";
 		callback = pm_noirq_op(dev->type->pm, state);
 	} else if (dev->class && dev->class->pm) {
@@ -1031,6 +1051,7 @@  static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
 		callback = pm_noirq_op(dev->driver->pm, state);
 	}
 
+ End:
 	error = dpm_run_callback(callback, dev, state, info);
 	if (!error)
 		dev->power.is_noirq_suspended = true;
@@ -1154,7 +1175,11 @@  static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
 	if (dev->pm_domain) {
 		info = "late power domain ";
 		callback = pm_late_early_op(&dev->pm_domain->ops, state);
-	} else if (dev->type && dev->type->pm) {
+		if (callback)
+			goto End;
+	}
+
+	if (dev->type && dev->type->pm) {
 		info = "late type ";
 		callback = pm_late_early_op(dev->type->pm, state);
 	} else if (dev->class && dev->class->pm) {
@@ -1170,6 +1195,7 @@  static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
 		callback = pm_late_early_op(dev->driver->pm, state);
 	}
 
+ End:
 	error = dpm_run_callback(callback, dev, state, info);
 	if (!error)
 		dev->power.is_late_suspended = true;
@@ -1338,20 +1364,21 @@  static int __device_suspend(struct device *dev, pm_message_t state, bool async)
 	if (dev->pm_domain) {
 		info = "power domain ";
 		callback = pm_op(&dev->pm_domain->ops, state);
-		goto Run;
+		if (callback)
+			goto Run;
 	}
 
 	if (dev->type && dev->type->pm) {
 		info = "type ";
 		callback = pm_op(dev->type->pm, state);
-		goto Run;
+		goto Driver;
 	}
 
 	if (dev->class) {
 		if (dev->class->pm) {
 			info = "class ";
 			callback = pm_op(dev->class->pm, state);
-			goto Run;
+			goto Driver;
 		} else if (dev->class->suspend) {
 			pm_dev_dbg(dev, state, "legacy class ");
 			error = legacy_suspend(dev, state, dev->class->suspend,
@@ -1372,12 +1399,13 @@  static int __device_suspend(struct device *dev, pm_message_t state, bool async)
 		}
 	}
 
- Run:
+ Driver:
 	if (!callback && dev->driver && dev->driver->pm) {
 		info = "driver ";
 		callback = pm_op(dev->driver->pm, state);
 	}
 
+ Run:
 	error = dpm_run_callback(callback, dev, state, info);
 
  End:
@@ -1507,7 +1535,11 @@  static int device_prepare(struct device *dev, pm_message_t state)
 	if (dev->pm_domain) {
 		info = "preparing power domain ";
 		callback = dev->pm_domain->ops.prepare;
-	} else if (dev->type && dev->type->pm) {
+		if (callback)
+			goto End;
+	}
+
+	if (dev->type && dev->type->pm) {
 		info = "preparing type ";
 		callback = dev->type->pm->prepare;
 	} else if (dev->class && dev->class->pm) {
@@ -1523,6 +1555,7 @@  static int device_prepare(struct device *dev, pm_message_t state)
 		callback = dev->driver->pm->prepare;
 	}
 
+ End:
 	if (callback) {
 		error = callback(dev);
 		suspend_report_result(callback, error);