@@ -228,3 +228,96 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd)
device_pm_check_callbacks(dev);
}
EXPORT_SYMBOL_GPL(dev_pm_domain_set);
+
+/**
+ * dev_multi_pm_attach - power up device associated power domains
+ * @dev: The device used to lookup the PM domains
+ *
+ * Parse device's OF node to find all PM domains specifiers. For each power
+ * domain found, create a virtual device and associate it with the
+ * current power domain.
+ *
+ * This function should typically be invoked by a driver during the
+ * probe phase, in the case its device requires power management through
+ * multiple PM domains.
+ *
+ * Returns a pointer to @dev_multi_pm_domain_data if successfully attached PM
+ * domains, NULL when the device doesn't need a PM domain or when single
+ * power-domains exists for it, else an ERR_PTR() in case of
+ * failures.
+ */
+struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev)
+{
+ struct dev_multi_pm_domain_data *mpd, *retp;
+ int num_domains;
+ int i;
+
+ num_domains = of_count_phandle_with_args(dev->of_node, "power-domains",
+ "#power-domain-cells");
+ if (num_domains < 2)
+ return NULL;
+
+ mpd = devm_kzalloc(dev, GFP_KERNEL, sizeof(*mpd));
+ if (!mpd)
+ return ERR_PTR(-ENOMEM);
+
+ mpd->dev = dev;
+ mpd->num_domains = num_domains;
+
+ mpd->virt_devs = devm_kmalloc_array(dev, mpd->num_domains,
+ sizeof(*mpd->virt_devs),
+ GFP_KERNEL);
+ if (!mpd->virt_devs)
+ return ERR_PTR(-ENOMEM);
+
+ mpd->links = devm_kmalloc_array(dev, mpd->num_domains,
+ sizeof(*mpd->links), GFP_KERNEL);
+ if (!mpd->links)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < mpd->num_domains; i++) {
+ mpd->virt_devs[i] = dev_pm_domain_attach_by_id(dev, i);
+ if (IS_ERR(mpd->virt_devs[i])) {
+ retp = (struct dev_multi_pm_domain_data *)
+ mpd->virt_devs[i];
+ goto exit_unroll_pm;
+ }
+ mpd->links[i] = device_link_add(dev, mpd->virt_devs[i],
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (!mpd->links[i]) {
+ retp = ERR_PTR(-ENOMEM);
+ dev_pm_domain_detach(mpd->virt_devs[i], false);
+ goto exit_unroll_pm;
+ }
+ }
+ return mpd;
+
+exit_unroll_pm:
+ while (--i >= 0) {
+ device_link_del(mpd->links[i]);
+ dev_pm_domain_detach(mpd->virt_devs[i], false);
+ }
+
+ return retp;
+}
+EXPORT_SYMBOL(dev_multi_pm_attach);
+
+/**
+ * dev_multi_pm_detach - Detach a device from its PM domains.
+ * Each multi power domain is attached to a virtual children device
+ *
+ * @mpd: multi power domains data, contains the association between
+ * virtul device and PM domain
+ */
+void dev_multi_pm_detach(struct dev_multi_pm_domain_data *mpd)
+{
+ int i;
+
+ for (i = 0; i < mpd->num_domains; i++) {
+ device_link_del(mpd->links[i]);
+ dev_pm_domain_detach(mpd->virt_devs[i], false);
+ }
+}
+EXPORT_SYMBOL(dev_multi_pm_detach);
@@ -183,6 +183,13 @@ struct generic_pm_domain_data {
void *data;
};
+struct dev_multi_pm_domain_data {
+ struct device *dev; /* parent device */
+ struct device **virt_devs; /* virtual children links */
+ struct device_link **links; /* links parent <-> virtual children */
+ int num_domains;
+};
+
#ifdef CONFIG_PM_GENERIC_DOMAINS
static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data *pdd)
{
@@ -369,18 +376,27 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np)
#ifdef CONFIG_PM
int dev_pm_domain_attach(struct device *dev, bool power_on);
+struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev);
struct device *dev_pm_domain_attach_by_id(struct device *dev,
unsigned int index);
struct device *dev_pm_domain_attach_by_name(struct device *dev,
const char *name);
void dev_pm_domain_detach(struct device *dev, bool power_off);
int dev_pm_domain_start(struct device *dev);
+void dev_multi_pm_detach(struct dev_multi_pm_domain_data *mpd);
void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd);
+
#else
static inline int dev_pm_domain_attach(struct device *dev, bool power_on)
{
return 0;
}
+
+struct dev_multi_pm_domain_data *dev_multi_pm_attach(struct device *dev)
+{
+ return NULL;
+}
+
static inline struct device *dev_pm_domain_attach_by_id(struct device *dev,
unsigned int index)
{
@@ -396,6 +412,9 @@ static inline int dev_pm_domain_start(struct device *dev)
{
return 0;
}
+
+void dev_multi_pm_detach(struct dev_multi_pm_domain_data *mpd) {}
+
static inline void dev_pm_domain_set(struct device *dev,
struct dev_pm_domain *pd) {}
#endif