|
@@ -366,7 +366,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd)
|
|
|
not_suspended = 0;
|
|
|
list_for_each_entry(pdd, &genpd->dev_list, list_node)
|
|
|
if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev)
|
|
|
- || pdd->dev->power.irq_safe))
|
|
|
+ || pdd->dev->power.irq_safe || to_gpd_data(pdd)->always_on))
|
|
|
not_suspended++;
|
|
|
|
|
|
if (not_suspended > genpd->in_progress)
|
|
@@ -503,6 +503,9 @@ static int pm_genpd_runtime_suspend(struct device *dev)
|
|
|
|
|
|
might_sleep_if(!genpd->dev_irq_safe);
|
|
|
|
|
|
+ if (dev_gpd_data(dev)->always_on)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL;
|
|
|
if (stop_ok && !stop_ok(dev))
|
|
|
return -EBUSY;
|
|
@@ -764,8 +767,10 @@ static int pm_genpd_prepare(struct device *dev)
|
|
|
|
|
|
genpd_acquire_lock(genpd);
|
|
|
|
|
|
- if (genpd->prepared_count++ == 0)
|
|
|
+ if (genpd->prepared_count++ == 0) {
|
|
|
+ genpd->suspended_count = 0;
|
|
|
genpd->suspend_power_off = genpd->status == GPD_STATE_POWER_OFF;
|
|
|
+ }
|
|
|
|
|
|
genpd_release_lock(genpd);
|
|
|
|
|
@@ -857,7 +862,7 @@ static int pm_genpd_suspend_noirq(struct device *dev)
|
|
|
if (IS_ERR(genpd))
|
|
|
return -EINVAL;
|
|
|
|
|
|
- if (genpd->suspend_power_off
|
|
|
+ if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on
|
|
|
|| (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)))
|
|
|
return 0;
|
|
|
|
|
@@ -890,7 +895,8 @@ static int pm_genpd_resume_noirq(struct device *dev)
|
|
|
if (IS_ERR(genpd))
|
|
|
return -EINVAL;
|
|
|
|
|
|
- if (genpd->suspend_power_off)
|
|
|
+ if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on
|
|
|
+ || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)))
|
|
|
return 0;
|
|
|
|
|
|
/*
|
|
@@ -1009,7 +1015,8 @@ static int pm_genpd_freeze_noirq(struct device *dev)
|
|
|
if (IS_ERR(genpd))
|
|
|
return -EINVAL;
|
|
|
|
|
|
- return genpd->suspend_power_off ? 0 : genpd_stop_dev(genpd, dev);
|
|
|
+ return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ?
|
|
|
+ 0 : genpd_stop_dev(genpd, dev);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1029,7 +1036,8 @@ static int pm_genpd_thaw_noirq(struct device *dev)
|
|
|
if (IS_ERR(genpd))
|
|
|
return -EINVAL;
|
|
|
|
|
|
- return genpd->suspend_power_off ? 0 : genpd_start_dev(genpd, dev);
|
|
|
+ return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ?
|
|
|
+ 0 : genpd_start_dev(genpd, dev);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1096,22 +1104,32 @@ static int pm_genpd_restore_noirq(struct device *dev)
|
|
|
* Since all of the "noirq" callbacks are executed sequentially, it is
|
|
|
* guaranteed that this function will never run twice in parallel for
|
|
|
* the same PM domain, so it is not necessary to use locking here.
|
|
|
+ *
|
|
|
+ * At this point suspended_count == 0 means we are being run for the
|
|
|
+ * first time for the given domain in the present cycle.
|
|
|
*/
|
|
|
- genpd->status = GPD_STATE_POWER_OFF;
|
|
|
- if (genpd->suspend_power_off) {
|
|
|
+ if (genpd->suspended_count++ == 0) {
|
|
|
/*
|
|
|
- * The boot kernel might put the domain into the power on state,
|
|
|
- * so make sure it really is powered off.
|
|
|
+ * The boot kernel might put the domain into arbitrary state,
|
|
|
+ * so make it appear as powered off to pm_genpd_poweron(), so
|
|
|
+ * that it tries to power it on in case it was really off.
|
|
|
*/
|
|
|
- if (genpd->power_off)
|
|
|
- genpd->power_off(genpd);
|
|
|
- return 0;
|
|
|
+ genpd->status = GPD_STATE_POWER_OFF;
|
|
|
+ if (genpd->suspend_power_off) {
|
|
|
+ /*
|
|
|
+ * If the domain was off before the hibernation, make
|
|
|
+ * sure it will be off going forward.
|
|
|
+ */
|
|
|
+ if (genpd->power_off)
|
|
|
+ genpd->power_off(genpd);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pm_genpd_poweron(genpd);
|
|
|
- genpd->suspended_count--;
|
|
|
|
|
|
- return genpd_start_dev(genpd, dev);
|
|
|
+ return dev_gpd_data(dev)->always_on ? 0 : genpd_start_dev(genpd, dev);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1306,6 +1324,26 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd,
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * pm_genpd_dev_always_on - Set/unset the "always on" flag for a given device.
|
|
|
+ * @dev: Device to set/unset the flag for.
|
|
|
+ * @val: The new value of the device's "always on" flag.
|
|
|
+ */
|
|
|
+void pm_genpd_dev_always_on(struct device *dev, bool val)
|
|
|
+{
|
|
|
+ struct pm_subsys_data *psd;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&dev->power.lock, flags);
|
|
|
+
|
|
|
+ psd = dev_to_psd(dev);
|
|
|
+ if (psd && psd->domain_data)
|
|
|
+ to_gpd_data(psd->domain_data)->always_on = val;
|
|
|
+
|
|
|
+ spin_unlock_irqrestore(&dev->power.lock, flags);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(pm_genpd_dev_always_on);
|
|
|
+
|
|
|
/**
|
|
|
* pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain.
|
|
|
* @genpd: Master PM domain to add the subdomain to.
|
|
@@ -1648,7 +1686,6 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
|
|
|
genpd->poweroff_task = NULL;
|
|
|
genpd->resume_count = 0;
|
|
|
genpd->device_count = 0;
|
|
|
- genpd->suspended_count = 0;
|
|
|
genpd->max_off_time_ns = -1;
|
|
|
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
|
|
|
genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume;
|