From c4bb3160c8823d3a1e581d7e05fb8b343097e7c8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:04 +0200 Subject: PM / Domains: Implement subdomain counters as atomic fields Currently, pm_genpd_poweron() and pm_genpd_poweroff() need to take the parent PM domain's lock in order to modify the parent's counter of active subdomains in a nonracy way. This causes the locking to be considerably complex and in fact is not necessary, because the subdomain counters may be implemented as atomic fields and they won't have to be modified under a lock. Replace the unsigned in sd_count field in struct generic_pm_domain by an atomic_t one and modify the code in drivers/base/power/domain.c to take this change into account. This patch doesn't change the locking yet, that is going to be done in a separate subsequent patch. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 1c374579407c..20e2b52d9c9c 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -29,10 +29,20 @@ static struct generic_pm_domain *dev_to_genpd(struct device *dev) return pd_to_genpd(dev->pm_domain); } -static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) +static bool genpd_sd_counter_dec(struct generic_pm_domain *genpd) { - if (!WARN_ON(genpd->sd_count == 0)) - genpd->sd_count--; + bool ret = false; + + if (!WARN_ON(atomic_read(&genpd->sd_count) == 0)) + ret = !!atomic_dec_and_test(&genpd->sd_count); + + return ret; +} + +static void genpd_sd_counter_inc(struct generic_pm_domain *genpd) +{ + atomic_inc(&genpd->sd_count); + smp_mb__after_atomic_inc(); } static void genpd_acquire_lock(struct generic_pm_domain *genpd) @@ -118,7 +128,7 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) genpd_set_active(genpd); if (parent) - parent->sd_count++; + genpd_sd_counter_inc(parent); out: mutex_unlock(&genpd->lock); @@ -254,7 +264,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) || genpd->resume_count > 0) return 0; - if (genpd->sd_count > 0) + if (atomic_read(&genpd->sd_count) > 0) return -EBUSY; not_suspended = 0; @@ -325,8 +335,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd->status = GPD_STATE_POWER_OFF; if (parent) { - genpd_sd_counter_dec(parent); - if (parent->sd_count == 0) + if (genpd_sd_counter_dec(parent)) genpd_queue_power_off_work(parent); genpd_release_lock(parent); @@ -506,7 +515,8 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) if (genpd->status == GPD_STATE_POWER_OFF) return; - if (genpd->suspended_count != genpd->device_count || genpd->sd_count > 0) + if (genpd->suspended_count != genpd->device_count + || atomic_read(&genpd->sd_count) > 0) return; if (genpd->power_off) @@ -1167,7 +1177,7 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, list_add_tail(&new_subdomain->sd_node, &genpd->sd_list); new_subdomain->parent = genpd; if (subdomain->status != GPD_STATE_POWER_OFF) - genpd->sd_count++; + genpd_sd_counter_inc(genpd); out: mutex_unlock(&new_subdomain->lock); @@ -1242,7 +1252,7 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->gov = gov; INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); genpd->in_progress = 0; - genpd->sd_count = 0; + atomic_set(&genpd->sd_count, 0); genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE; init_waitqueue_head(&genpd->status_wait_queue); genpd->poweroff_task = NULL; -- cgit v1.2.3 From 3c07cbc488bfd1ad1abf64d09cc692339b5f8a83 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:14 +0200 Subject: PM / Domains: Do not take parent locks to modify subdomain counters After the subdomain counter in struct generic_pm_domain has been changed into an atomic_t field, it is possible to modify pm_genpd_poweron() and pm_genpd_poweroff() so that they don't take the parents locks. This requires pm_genpd_poweron() to increment the parent's subdomain counter before calling itself recursively for the parent and to decrement it if an error is to be returned. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 70 ++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 39 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 20e2b52d9c9c..ef25b6f99f99 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -93,12 +93,7 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) int ret = 0; start: - if (parent) { - genpd_acquire_lock(parent); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - } else { - mutex_lock(&genpd->lock); - } + mutex_lock(&genpd->lock); if (genpd->status == GPD_STATE_ACTIVE || (genpd->prepared_count > 0 && genpd->suspend_power_off)) @@ -109,31 +104,33 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) goto out; } - if (parent && parent->status != GPD_STATE_ACTIVE) { + if (parent) { + genpd_sd_counter_inc(parent); + mutex_unlock(&genpd->lock); - genpd_release_lock(parent); ret = pm_genpd_poweron(parent); - if (ret) + if (ret) { + genpd_sd_counter_dec(parent); return ret; + } + parent = NULL; goto start; } - if (genpd->power_on) { + if (genpd->power_on) ret = genpd->power_on(genpd); - if (ret) - goto out; - } - genpd_set_active(genpd); - if (parent) - genpd_sd_counter_inc(parent); + if (ret) { + if (genpd->parent) + genpd_sd_counter_dec(genpd->parent); + } else { + genpd_set_active(genpd); + } out: mutex_unlock(&genpd->lock); - if (parent) - genpd_release_lock(parent); return ret; } @@ -293,7 +290,8 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd->poweroff_task = current; list_for_each_entry_reverse(dle, &genpd->dev_list, node) { - ret = __pm_genpd_save_device(dle, genpd); + ret = atomic_read(&genpd->sd_count) == 0 ? + __pm_genpd_save_device(dle, genpd) : -EBUSY; if (ret) { genpd_set_active(genpd); goto out; @@ -308,38 +306,32 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } } - parent = genpd->parent; - if (parent) { - mutex_unlock(&genpd->lock); - - genpd_acquire_lock(parent); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - - if (genpd_abort_poweroff(genpd)) { - genpd_release_lock(parent); + if (genpd->power_off) { + if (atomic_read(&genpd->sd_count) > 0) { + ret = -EBUSY; goto out; } - } - if (genpd->power_off) { + /* + * If sd_count > 0 at this point, one of the children hasn't + * managed to call pm_genpd_poweron() for the parent yet after + * incrementing it. In that case pm_genpd_poweron() will wait + * for us to drop the lock, so we can call .power_off() and let + * the pm_genpd_poweron() restore power for us (this shouldn't + * happen very often). + */ ret = genpd->power_off(genpd); if (ret == -EBUSY) { genpd_set_active(genpd); - if (parent) - genpd_release_lock(parent); - goto out; } } genpd->status = GPD_STATE_POWER_OFF; - if (parent) { - if (genpd_sd_counter_dec(parent)) - genpd_queue_power_off_work(parent); - - genpd_release_lock(parent); - } + parent = genpd->parent; + if (parent && genpd_sd_counter_dec(parent)) + genpd_queue_power_off_work(parent); out: genpd->poweroff_task = NULL; -- cgit v1.2.3 From 9e08cf429697090d0fac57d493dc7b6de17a5eee Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:22 +0200 Subject: PM / Domains: Make pm_genpd_poweron() always survive parent removal If pm_genpd_remove_subdomain() is called to remove a PM domain's subdomain and pm_genpd_poweron() is called for that subdomain at the same time, and the pm_genpd_poweron() called by it recursively for the parent returns an error, the first pm_genpd_poweron()'s error code path will attempt to decrement the subdomain counter of a PM domain that it's not a subdomain of any more. Rearrange the code in pm_genpd_poweron() to prevent this from happening. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index ef25b6f99f99..dc423a99c67c 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -89,12 +89,14 @@ static void genpd_set_active(struct generic_pm_domain *genpd) */ int pm_genpd_poweron(struct generic_pm_domain *genpd) { - struct generic_pm_domain *parent = genpd->parent; + struct generic_pm_domain *parent; int ret = 0; - start: mutex_lock(&genpd->lock); + parent = genpd->parent; + + start: if (genpd->status == GPD_STATE_ACTIVE || (genpd->prepared_count > 0 && genpd->suspend_power_off)) goto out; @@ -110,29 +112,34 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) mutex_unlock(&genpd->lock); ret = pm_genpd_poweron(parent); - if (ret) { - genpd_sd_counter_dec(parent); - return ret; - } + + mutex_lock(&genpd->lock); + + if (ret) + goto err; parent = NULL; goto start; } - if (genpd->power_on) + if (genpd->power_on) { ret = genpd->power_on(genpd); - - if (ret) { - if (genpd->parent) - genpd_sd_counter_dec(genpd->parent); - } else { - genpd_set_active(genpd); + if (ret) + goto err; } + genpd_set_active(genpd); + out: mutex_unlock(&genpd->lock); return ret; + + err: + if (genpd->parent) + genpd_sd_counter_dec(genpd->parent); + + goto out; } #endif /* CONFIG_PM */ -- cgit v1.2.3 From 3f241775c30365c33a0d2f6d40f4cf12470f48c6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:29 +0200 Subject: PM / Domains: Add "wait for parent" status for generic PM domains The next patch will make it possible for a generic PM domain to have multiple parents (i.e. multiple PM domains it depends on). To prepare for that change it is necessary to change pm_genpd_poweron() so that it doesn't jump to the start label after running itself recursively for the parent domain. For this purpose, introduce a new PM domain status value GPD_STATE_WAIT_PARENT that will be set by pm_genpd_poweron() before calling itself recursively for the parent domain and modify the code in drivers/base/power/domain.c so that the GPD_STATE_WAIT_PARENT status is guaranteed to be preserved during the execution of pm_genpd_poweron() for the parent. This change also causes pm_genpd_add_subdomain() and pm_genpd_remove_subdomain() to wait for started pm_genpd_poweron() to complete and allows pm_genpd_runtime_resume() to avoid dropping the lock after powering on the PM domain. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 90 ++++++++++++++++++++++++++++++--------------- include/linux/pm_domain.h | 1 + 2 files changed, 61 insertions(+), 30 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index dc423a99c67c..1f4b1326c6a9 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -81,45 +81,59 @@ static void genpd_set_active(struct generic_pm_domain *genpd) } /** - * pm_genpd_poweron - Restore power to a given PM domain and its parents. + * __pm_genpd_poweron - Restore power to a given PM domain and its parents. * @genpd: PM domain to power up. * * Restore power to @genpd and all of its parents so that it is possible to * resume a device belonging to it. */ -int pm_genpd_poweron(struct generic_pm_domain *genpd) +int __pm_genpd_poweron(struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { - struct generic_pm_domain *parent; + DEFINE_WAIT(wait); int ret = 0; - mutex_lock(&genpd->lock); + /* If the domain's parent is being waited for, we have to wait too. */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status != GPD_STATE_WAIT_PARENT) + break; + mutex_unlock(&genpd->lock); - parent = genpd->parent; + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); - start: if (genpd->status == GPD_STATE_ACTIVE || (genpd->prepared_count > 0 && genpd->suspend_power_off)) - goto out; + return 0; if (genpd->status != GPD_STATE_POWER_OFF) { genpd_set_active(genpd); - goto out; + return 0; } - if (parent) { - genpd_sd_counter_inc(parent); + if (genpd->parent) { + genpd_sd_counter_inc(genpd->parent); + genpd->status = GPD_STATE_WAIT_PARENT; mutex_unlock(&genpd->lock); - ret = pm_genpd_poweron(parent); + ret = pm_genpd_poweron(genpd->parent); mutex_lock(&genpd->lock); + /* + * The "wait for parent" status is guaranteed not to change + * while the parent is powering on. + */ + genpd->status = GPD_STATE_POWER_OFF; + wake_up_all(&genpd->status_wait_queue); if (ret) goto err; - - parent = NULL; - goto start; } if (genpd->power_on) { @@ -130,16 +144,27 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) genpd_set_active(genpd); - out: - mutex_unlock(&genpd->lock); - - return ret; + return 0; err: if (genpd->parent) genpd_sd_counter_dec(genpd->parent); - goto out; + return ret; +} + +/** + * pm_genpd_poweron - Restore power to a given PM domain and its parents. + * @genpd: PM domain to power up. + */ +int pm_genpd_poweron(struct generic_pm_domain *genpd) +{ + int ret; + + mutex_lock(&genpd->lock); + ret = __pm_genpd_poweron(genpd); + mutex_unlock(&genpd->lock); + return ret; } #endif /* CONFIG_PM */ @@ -225,7 +250,8 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, */ static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) { - return genpd->status == GPD_STATE_ACTIVE || genpd->resume_count > 0; + return genpd->status == GPD_STATE_WAIT_PARENT + || genpd->status == GPD_STATE_ACTIVE || genpd->resume_count > 0; } /** @@ -261,11 +287,13 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) /* * Do not try to power off the domain in the following situations: * (1) The domain is already in the "power off" state. - * (2) System suspend is in progress. + * (2) The domain is waiting for its parent to power up. * (3) One of the domain's devices is being resumed right now. + * (4) System suspend is in progress. */ - if (genpd->status == GPD_STATE_POWER_OFF || genpd->prepared_count > 0 - || genpd->resume_count > 0) + if (genpd->status == GPD_STATE_POWER_OFF + || genpd->status == GPD_STATE_WAIT_PARENT + || genpd->resume_count > 0 || genpd->prepared_count > 0) return 0; if (atomic_read(&genpd->sd_count) > 0) @@ -299,14 +327,15 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) list_for_each_entry_reverse(dle, &genpd->dev_list, node) { ret = atomic_read(&genpd->sd_count) == 0 ? __pm_genpd_save_device(dle, genpd) : -EBUSY; + + if (genpd_abort_poweroff(genpd)) + goto out; + if (ret) { genpd_set_active(genpd); goto out; } - if (genpd_abort_poweroff(genpd)) - goto out; - if (genpd->status == GPD_STATE_REPEAT) { genpd->poweroff_task = NULL; goto start; @@ -432,11 +461,12 @@ static int pm_genpd_runtime_resume(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - ret = pm_genpd_poweron(genpd); - if (ret) - return ret; - mutex_lock(&genpd->lock); + ret = __pm_genpd_poweron(genpd); + if (ret) { + mutex_unlock(&genpd->lock); + return ret; + } genpd->status = GPD_STATE_BUSY; genpd->resume_count++; for (;;) { diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 81c5782d90a3..97e3f8eb4978 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -13,6 +13,7 @@ enum gpd_status { GPD_STATE_ACTIVE = 0, /* PM domain is active */ + GPD_STATE_WAIT_PARENT, /* PM domain's parent is being waited for */ GPD_STATE_BUSY, /* Something is happening to the PM domain */ GPD_STATE_REPEAT, /* Power off in progress, to be repeated */ GPD_STATE_POWER_OFF, /* PM domain is off */ -- cgit v1.2.3 From 5063ce1571b73865cbdcd92db002e85809750c97 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:40 +0200 Subject: PM / Domains: Allow generic PM domains to have multiple masters Currently, for a given generic PM domain there may be only one parent domain (i.e. a PM domain it depends on). However, there is at least one real-life case in which there should be two parents (masters) for one PM domain (the A3RV domain on SH7372 turns out to depend on the A4LC domain and it depends on the A4R domain and the same time). For this reason, allow a PM domain to have multiple parents (masters) by introducing objects representing links between PM domains. The (logical) links between PM domains represent relationships in which one domain is a master (i.e. it is depended on) and another domain is a slave (i.e. it depends on the master) with the rule that the slave cannot be powered on if the master is not powered on and the master cannot be powered off if the slave is not powered off. Each struct generic_pm_domain object representing a PM domain has two lists of links, a list of links in which it is a master and a list of links in which it is a slave. The first of these lists replaces the list of subdomains and the second one is used in place of the parent pointer. Each link is represented by struct gpd_link object containing pointers to the master and the slave and two struct list_head members allowing it to hook into two lists (the master's list of "master" links and the slave's list of "slave" links). This allows the code to get to the link from each side (either from the master or from the slave) and follow it in each direction. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 99 ++++++++++++++++++++++++++------------------- include/linux/pm_domain.h | 12 ++++-- 2 files changed, 67 insertions(+), 44 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 1f4b1326c6a9..8fc538da1de0 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -81,19 +81,20 @@ static void genpd_set_active(struct generic_pm_domain *genpd) } /** - * __pm_genpd_poweron - Restore power to a given PM domain and its parents. + * __pm_genpd_poweron - Restore power to a given PM domain and its masters. * @genpd: PM domain to power up. * - * Restore power to @genpd and all of its parents so that it is possible to + * Restore power to @genpd and all of its masters so that it is possible to * resume a device belonging to it. */ int __pm_genpd_poweron(struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { + struct gpd_link *link; DEFINE_WAIT(wait); int ret = 0; - /* If the domain's parent is being waited for, we have to wait too. */ + /* If the domain's master is being waited for, we have to wait too. */ for (;;) { prepare_to_wait(&genpd->status_wait_queue, &wait, TASK_UNINTERRUPTIBLE); @@ -116,24 +117,31 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) return 0; } - if (genpd->parent) { - genpd_sd_counter_inc(genpd->parent); + /* + * The list is guaranteed not to change while the loop below is being + * executed, unless one of the masters' .power_on() callbacks fiddles + * with it. + */ + list_for_each_entry(link, &genpd->slave_links, slave_node) { + genpd_sd_counter_inc(link->master); genpd->status = GPD_STATE_WAIT_PARENT; mutex_unlock(&genpd->lock); - ret = pm_genpd_poweron(genpd->parent); + ret = pm_genpd_poweron(link->master); mutex_lock(&genpd->lock); /* * The "wait for parent" status is guaranteed not to change - * while the parent is powering on. + * while the master is powering on. */ genpd->status = GPD_STATE_POWER_OFF; wake_up_all(&genpd->status_wait_queue); - if (ret) + if (ret) { + genpd_sd_counter_dec(link->master); goto err; + } } if (genpd->power_on) { @@ -147,14 +155,14 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) return 0; err: - if (genpd->parent) - genpd_sd_counter_dec(genpd->parent); + list_for_each_entry_continue_reverse(link, &genpd->slave_links, slave_node) + genpd_sd_counter_dec(link->master); return ret; } /** - * pm_genpd_poweron - Restore power to a given PM domain and its parents. + * pm_genpd_poweron - Restore power to a given PM domain and its masters. * @genpd: PM domain to power up. */ int pm_genpd_poweron(struct generic_pm_domain *genpd) @@ -278,8 +286,8 @@ void genpd_queue_power_off_work(struct generic_pm_domain *genpd) static int pm_genpd_poweroff(struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { - struct generic_pm_domain *parent; struct dev_list_entry *dle; + struct gpd_link *link; unsigned int not_suspended; int ret = 0; @@ -287,7 +295,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) /* * Do not try to power off the domain in the following situations: * (1) The domain is already in the "power off" state. - * (2) The domain is waiting for its parent to power up. + * (2) The domain is waiting for its master to power up. * (3) One of the domain's devices is being resumed right now. * (4) System suspend is in progress. */ @@ -349,8 +357,8 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } /* - * If sd_count > 0 at this point, one of the children hasn't - * managed to call pm_genpd_poweron() for the parent yet after + * If sd_count > 0 at this point, one of the subdomains hasn't + * managed to call pm_genpd_poweron() for the master yet after * incrementing it. In that case pm_genpd_poweron() will wait * for us to drop the lock, so we can call .power_off() and let * the pm_genpd_poweron() restore power for us (this shouldn't @@ -365,9 +373,10 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd->status = GPD_STATE_POWER_OFF; - parent = genpd->parent; - if (parent && genpd_sd_counter_dec(parent)) - genpd_queue_power_off_work(parent); + list_for_each_entry(link, &genpd->slave_links, slave_node) { + genpd_sd_counter_dec(link->master); + genpd_queue_power_off_work(link->master); + } out: genpd->poweroff_task = NULL; @@ -527,11 +536,11 @@ static inline void __pm_genpd_runtime_resume(struct device *dev, #ifdef CONFIG_PM_SLEEP /** - * pm_genpd_sync_poweroff - Synchronously power off a PM domain and its parents. + * pm_genpd_sync_poweroff - Synchronously power off a PM domain and its masters. * @genpd: PM domain to power off, if possible. * * Check if the given PM domain can be powered off (during system suspend or - * hibernation) and do that if so. Also, in that case propagate to its parent. + * hibernation) and do that if so. Also, in that case propagate to its masters. * * This function is only called in "noirq" stages of system power transitions, * so it need not acquire locks (all of the "noirq" callbacks are executed @@ -539,7 +548,7 @@ static inline void __pm_genpd_runtime_resume(struct device *dev, */ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) { - struct generic_pm_domain *parent = genpd->parent; + struct gpd_link *link; if (genpd->status == GPD_STATE_POWER_OFF) return; @@ -552,9 +561,10 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) genpd->power_off(genpd); genpd->status = GPD_STATE_POWER_OFF; - if (parent) { - genpd_sd_counter_dec(parent); - pm_genpd_sync_poweroff(parent); + + list_for_each_entry(link, &genpd->slave_links, slave_node) { + genpd_sd_counter_dec(link->master); + pm_genpd_sync_poweroff(link->master); } } @@ -1173,7 +1183,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain) { - struct generic_pm_domain *subdomain; + struct gpd_link *link; int ret = 0; if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain)) @@ -1196,16 +1206,23 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, goto out; } - list_for_each_entry(subdomain, &genpd->sd_list, sd_node) { - if (subdomain == new_subdomain) { + list_for_each_entry(link, &genpd->slave_links, slave_node) { + if (link->slave == new_subdomain && link->master == genpd) { ret = -EINVAL; goto out; } } - list_add_tail(&new_subdomain->sd_node, &genpd->sd_list); - new_subdomain->parent = genpd; - if (subdomain->status != GPD_STATE_POWER_OFF) + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto out; + } + link->master = genpd; + list_add_tail(&link->master_node, &genpd->master_links); + link->slave = new_subdomain; + list_add_tail(&link->slave_node, &new_subdomain->slave_links); + if (new_subdomain->status != GPD_STATE_POWER_OFF) genpd_sd_counter_inc(genpd); out: @@ -1218,22 +1235,22 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, /** * pm_genpd_remove_subdomain - Remove a subdomain from an I/O PM domain. * @genpd: Master PM domain to remove the subdomain from. - * @target: Subdomain to be removed. + * @subdomain: Subdomain to be removed. */ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, - struct generic_pm_domain *target) + struct generic_pm_domain *subdomain) { - struct generic_pm_domain *subdomain; + struct gpd_link *link; int ret = -EINVAL; - if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(target)) + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain)) return -EINVAL; start: genpd_acquire_lock(genpd); - list_for_each_entry(subdomain, &genpd->sd_list, sd_node) { - if (subdomain != target) + list_for_each_entry(link, &genpd->master_links, master_node) { + if (link->slave != subdomain) continue; mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); @@ -1245,8 +1262,9 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, goto start; } - list_del(&subdomain->sd_node); - subdomain->parent = NULL; + list_del(&link->master_node); + list_del(&link->slave_node); + kfree(link); if (subdomain->status != GPD_STATE_POWER_OFF) genpd_sd_counter_dec(genpd); @@ -1273,10 +1291,9 @@ void pm_genpd_init(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd)) return; - INIT_LIST_HEAD(&genpd->sd_node); - genpd->parent = NULL; + INIT_LIST_HEAD(&genpd->master_links); + INIT_LIST_HEAD(&genpd->slave_links); INIT_LIST_HEAD(&genpd->dev_list); - INIT_LIST_HEAD(&genpd->sd_list); mutex_init(&genpd->lock); genpd->gov = gov; INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 97e3f8eb4978..5f5154d79253 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -26,9 +26,8 @@ struct dev_power_governor { struct generic_pm_domain { struct dev_pm_domain domain; /* PM domain operations */ struct list_head gpd_list_node; /* Node in the global PM domains list */ - struct list_head sd_node; /* Node in the parent's subdomain list */ - struct generic_pm_domain *parent; /* Parent PM domain */ - struct list_head sd_list; /* List of dubdomains */ + struct list_head master_links; /* Links with PM domain as a master */ + struct list_head slave_links; /* Links with PM domain as a slave */ struct list_head dev_list; /* List of devices */ struct mutex lock; struct dev_power_governor *gov; @@ -55,6 +54,13 @@ static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) return container_of(pd, struct generic_pm_domain, domain); } +struct gpd_link { + struct generic_pm_domain *master; + struct list_head master_node; + struct generic_pm_domain *slave; + struct list_head slave_node; +}; + struct dev_list_entry { struct list_head node; struct device *dev; -- cgit v1.2.3 From 17877eb5a900f32bb5827a7b2109b6c9adff5fc3 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:50 +0200 Subject: PM / Domains: Rename GPD_STATE_WAIT_PARENT to GPD_STATE_WAIT_MASTER Since it is now possible for a PM domain to have multiple masters instead of one parent, rename the "wait for parent" status to reflect the new situation. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 8 ++++---- include/linux/pm_domain.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 8fc538da1de0..c06f8f8717b9 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -98,7 +98,7 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) for (;;) { prepare_to_wait(&genpd->status_wait_queue, &wait, TASK_UNINTERRUPTIBLE); - if (genpd->status != GPD_STATE_WAIT_PARENT) + if (genpd->status != GPD_STATE_WAIT_MASTER) break; mutex_unlock(&genpd->lock); @@ -124,7 +124,7 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) */ list_for_each_entry(link, &genpd->slave_links, slave_node) { genpd_sd_counter_inc(link->master); - genpd->status = GPD_STATE_WAIT_PARENT; + genpd->status = GPD_STATE_WAIT_MASTER; mutex_unlock(&genpd->lock); @@ -258,7 +258,7 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, */ static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) { - return genpd->status == GPD_STATE_WAIT_PARENT + return genpd->status == GPD_STATE_WAIT_MASTER || genpd->status == GPD_STATE_ACTIVE || genpd->resume_count > 0; } @@ -300,7 +300,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) * (4) System suspend is in progress. */ if (genpd->status == GPD_STATE_POWER_OFF - || genpd->status == GPD_STATE_WAIT_PARENT + || genpd->status == GPD_STATE_WAIT_MASTER || genpd->resume_count > 0 || genpd->prepared_count > 0) return 0; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 5f5154d79253..bf679f59f9a8 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -13,7 +13,7 @@ enum gpd_status { GPD_STATE_ACTIVE = 0, /* PM domain is active */ - GPD_STATE_WAIT_PARENT, /* PM domain's parent is being waited for */ + GPD_STATE_WAIT_MASTER, /* PM domain's master is being waited for */ GPD_STATE_BUSY, /* Something is happening to the PM domain */ GPD_STATE_REPEAT, /* Power off in progress, to be repeated */ GPD_STATE_POWER_OFF, /* PM domain is off */ -- cgit v1.2.3 From bc0403ff16e5305c3a14c2b0826616ceaabbf058 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Aug 2011 23:43:59 +0200 Subject: PM / Domains: Rename argument of pm_genpd_add_subdomain() Change the name of the second argument of pm_genpd_add_subdomain() so that it is (a) shorter and (b) in agreement with the name of the second argument of pm_genpd_add_subdomain(). Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index c06f8f8717b9..1fc6cc9835ad 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1178,36 +1178,36 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, /** * pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain. * @genpd: Master PM domain to add the subdomain to. - * @new_subdomain: Subdomain to be added. + * @subdomain: Subdomain to be added. */ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, - struct generic_pm_domain *new_subdomain) + struct generic_pm_domain *subdomain) { struct gpd_link *link; int ret = 0; - if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain)) + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain)) return -EINVAL; start: genpd_acquire_lock(genpd); - mutex_lock_nested(&new_subdomain->lock, SINGLE_DEPTH_NESTING); + mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); - if (new_subdomain->status != GPD_STATE_POWER_OFF - && new_subdomain->status != GPD_STATE_ACTIVE) { - mutex_unlock(&new_subdomain->lock); + if (subdomain->status != GPD_STATE_POWER_OFF + && subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&subdomain->lock); genpd_release_lock(genpd); goto start; } if (genpd->status == GPD_STATE_POWER_OFF - && new_subdomain->status != GPD_STATE_POWER_OFF) { + && subdomain->status != GPD_STATE_POWER_OFF) { ret = -EINVAL; goto out; } list_for_each_entry(link, &genpd->slave_links, slave_node) { - if (link->slave == new_subdomain && link->master == genpd) { + if (link->slave == subdomain && link->master == genpd) { ret = -EINVAL; goto out; } @@ -1220,13 +1220,13 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, } link->master = genpd; list_add_tail(&link->master_node, &genpd->master_links); - link->slave = new_subdomain; - list_add_tail(&link->slave_node, &new_subdomain->slave_links); - if (new_subdomain->status != GPD_STATE_POWER_OFF) + link->slave = subdomain; + list_add_tail(&link->slave_node, &subdomain->slave_links); + if (subdomain->status != GPD_STATE_POWER_OFF) genpd_sd_counter_inc(genpd); out: - mutex_unlock(&new_subdomain->lock); + mutex_unlock(&subdomain->lock); genpd_release_lock(genpd); return ret; -- cgit v1.2.3 From 5c095a0e0d600d5a5a4207eaadabd18db46395ce Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Aug 2011 15:33:50 +0200 Subject: PM: Introduce struct pm_subsys_data Introduce struct pm_subsys_data that may be subclassed by subsystems to store subsystem-specific information related to the device. Move the clock management fields accessed through the power.subsys_data pointer in struct device to the new strucutre. Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-shmobile/pm-sh7372.c | 2 +- drivers/base/power/clock_ops.c | 122 +++++++++++++++++++------------------ include/linux/device.h | 5 ++ include/linux/pm.h | 9 ++- include/linux/pm_runtime.h | 8 ++- 5 files changed, 84 insertions(+), 62 deletions(-) (limited to 'drivers/base') diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index b471b795dfc3..54d4d86883c9 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -115,7 +115,7 @@ void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, struct device *dev = &pdev->dev; if (!dev->power.subsys_data) { - pm_clk_init(dev); + pm_clk_create(dev); pm_clk_add(dev, NULL); } pm_genpd_add_device(&sh7372_pd->genpd, dev); diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 2c18d584066d..b7f1db4f5945 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -17,11 +17,6 @@ #ifdef CONFIG_PM -struct pm_clk_data { - struct list_head clock_list; - spinlock_t lock; -}; - enum pce_status { PCE_STATUS_NONE = 0, PCE_STATUS_ACQUIRED, @@ -36,11 +31,6 @@ struct pm_clock_entry { enum pce_status status; }; -static struct pm_clk_data *__to_pcd(struct device *dev) -{ - return dev ? dev->power.subsys_data : NULL; -} - /** * pm_clk_add - Start using a device clock for power management. * @dev: Device whose clock is going to be used for power management. @@ -51,10 +41,10 @@ static struct pm_clk_data *__to_pcd(struct device *dev) */ int pm_clk_add(struct device *dev, const char *con_id) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; - if (!pcd) + if (!psd) return -EINVAL; ce = kzalloc(sizeof(*ce), GFP_KERNEL); @@ -73,9 +63,9 @@ int pm_clk_add(struct device *dev, const char *con_id) } } - spin_lock_irq(&pcd->lock); - list_add_tail(&ce->node, &pcd->clock_list); - spin_unlock_irq(&pcd->lock); + spin_lock_irq(&psd->lock); + list_add_tail(&ce->node, &psd->clock_list); + spin_unlock_irq(&psd->lock); return 0; } @@ -117,15 +107,15 @@ static void __pm_clk_remove(struct pm_clock_entry *ce) */ void pm_clk_remove(struct device *dev, const char *con_id) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; - if (!pcd) + if (!psd) return; - spin_lock_irq(&pcd->lock); + spin_lock_irq(&psd->lock); - list_for_each_entry(ce, &pcd->clock_list, node) { + list_for_each_entry(ce, &psd->clock_list, node) { if (!con_id && !ce->con_id) { __pm_clk_remove(ce); break; @@ -137,29 +127,45 @@ void pm_clk_remove(struct device *dev, const char *con_id) } } - spin_unlock_irq(&pcd->lock); + spin_unlock_irq(&psd->lock); } /** * pm_clk_init - Initialize a device's list of power management clocks. * @dev: Device to initialize the list of PM clocks for. * - * Allocate a struct pm_clk_data object, initialize its lock member and - * make the @dev's power.subsys_data field point to it. + * Initialize the lock and clock_list members of the device's pm_subsys_data + * object. */ -int pm_clk_init(struct device *dev) +void pm_clk_init(struct device *dev) { - struct pm_clk_data *pcd; + struct pm_subsys_data *psd = dev_to_psd(dev); + + if (!psd) + return; - pcd = kzalloc(sizeof(*pcd), GFP_KERNEL); - if (!pcd) { + INIT_LIST_HEAD(&psd->clock_list); + spin_lock_init(&psd->lock); +} + +/** + * pm_clk_create - Create and initialize a device's list of PM clocks. + * @dev: Device to create and initialize the list of PM clocks for. + * + * Allocate a struct pm_subsys_data object, initialize its lock and clock_list + * members and make the @dev's power.subsys_data field point to it. + */ +int pm_clk_create(struct device *dev) +{ + struct pm_subsys_data *psd; + + psd = kzalloc(sizeof(*psd), GFP_KERNEL); + if (!psd) { dev_err(dev, "Not enough memory for PM clock data.\n"); return -ENOMEM; } - - INIT_LIST_HEAD(&pcd->clock_list); - spin_lock_init(&pcd->lock); - dev->power.subsys_data = pcd; + dev->power.subsys_data = psd; + pm_clk_init(dev); return 0; } @@ -168,27 +174,27 @@ int pm_clk_init(struct device *dev) * @dev: Device to destroy the list of PM clocks for. * * Clear the @dev's power.subsys_data field, remove the list of clock entries - * from the struct pm_clk_data object pointed to by it before and free + * from the struct pm_subsys_data object pointed to by it before and free * that object. */ void pm_clk_destroy(struct device *dev) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce, *c; - if (!pcd) + if (!psd) return; dev->power.subsys_data = NULL; - spin_lock_irq(&pcd->lock); + spin_lock_irq(&psd->lock); - list_for_each_entry_safe_reverse(ce, c, &pcd->clock_list, node) + list_for_each_entry_safe_reverse(ce, c, &psd->clock_list, node) __pm_clk_remove(ce); - spin_unlock_irq(&pcd->lock); + spin_unlock_irq(&psd->lock); - kfree(pcd); + kfree(psd); } #endif /* CONFIG_PM */ @@ -218,18 +224,18 @@ static void pm_clk_acquire(struct device *dev, */ int pm_clk_suspend(struct device *dev) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; unsigned long flags; dev_dbg(dev, "%s()\n", __func__); - if (!pcd) + if (!psd) return 0; - spin_lock_irqsave(&pcd->lock, flags); + spin_lock_irqsave(&psd->lock, flags); - list_for_each_entry_reverse(ce, &pcd->clock_list, node) { + list_for_each_entry_reverse(ce, &psd->clock_list, node) { if (ce->status == PCE_STATUS_NONE) pm_clk_acquire(dev, ce); @@ -239,7 +245,7 @@ int pm_clk_suspend(struct device *dev) } } - spin_unlock_irqrestore(&pcd->lock, flags); + spin_unlock_irqrestore(&psd->lock, flags); return 0; } @@ -250,18 +256,18 @@ int pm_clk_suspend(struct device *dev) */ int pm_clk_resume(struct device *dev) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; unsigned long flags; dev_dbg(dev, "%s()\n", __func__); - if (!pcd) + if (!psd) return 0; - spin_lock_irqsave(&pcd->lock, flags); + spin_lock_irqsave(&psd->lock, flags); - list_for_each_entry(ce, &pcd->clock_list, node) { + list_for_each_entry(ce, &psd->clock_list, node) { if (ce->status == PCE_STATUS_NONE) pm_clk_acquire(dev, ce); @@ -271,7 +277,7 @@ int pm_clk_resume(struct device *dev) } } - spin_unlock_irqrestore(&pcd->lock, flags); + spin_unlock_irqrestore(&psd->lock, flags); return 0; } @@ -309,7 +315,7 @@ static int pm_clk_notify(struct notifier_block *nb, if (dev->pm_domain) break; - error = pm_clk_init(dev); + error = pm_clk_create(dev); if (error) break; @@ -344,22 +350,22 @@ static int pm_clk_notify(struct notifier_block *nb, */ int pm_clk_suspend(struct device *dev) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; unsigned long flags; dev_dbg(dev, "%s()\n", __func__); /* If there is no driver, the clocks are already disabled. */ - if (!pcd || !dev->driver) + if (!psd || !dev->driver) return 0; - spin_lock_irqsave(&pcd->lock, flags); + spin_lock_irqsave(&psd->lock, flags); - list_for_each_entry_reverse(ce, &pcd->clock_list, node) + list_for_each_entry_reverse(ce, &psd->clock_list, node) clk_disable(ce->clk); - spin_unlock_irqrestore(&pcd->lock, flags); + spin_unlock_irqrestore(&psd->lock, flags); return 0; } @@ -370,22 +376,22 @@ int pm_clk_suspend(struct device *dev) */ int pm_clk_resume(struct device *dev) { - struct pm_clk_data *pcd = __to_pcd(dev); + struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; unsigned long flags; dev_dbg(dev, "%s()\n", __func__); /* If there is no driver, the clocks should remain disabled. */ - if (!pcd || !dev->driver) + if (!psd || !dev->driver) return 0; - spin_lock_irqsave(&pcd->lock, flags); + spin_lock_irqsave(&psd->lock, flags); - list_for_each_entry(ce, &pcd->clock_list, node) + list_for_each_entry(ce, &psd->clock_list, node) clk_enable(ce->clk); - spin_unlock_irqrestore(&pcd->lock, flags); + spin_unlock_irqrestore(&psd->lock, flags); return 0; } diff --git a/include/linux/device.h b/include/linux/device.h index c20dfbfc49b4..5d200ed0071a 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -636,6 +636,11 @@ static inline void set_dev_node(struct device *dev, int node) } #endif +static inline struct pm_subsys_data *dev_to_psd(struct device *dev) +{ + return dev ? dev->power.subsys_data : NULL; +} + static inline unsigned int dev_get_uevent_suppress(const struct device *dev) { return dev->kobj.uevent_suppress; diff --git a/include/linux/pm.h b/include/linux/pm.h index f7c84c9abd30..bf5ee37388d4 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -421,6 +421,13 @@ enum rpm_request { struct wakeup_source; +struct pm_subsys_data { + spinlock_t lock; +#ifdef CONFIG_PM_CLK + struct list_head clock_list; +#endif +}; + struct dev_pm_info { pm_message_t power_state; unsigned int can_wakeup:1; @@ -462,7 +469,7 @@ struct dev_pm_info { unsigned long suspended_jiffies; unsigned long accounting_timestamp; #endif - void *subsys_data; /* Owned by the subsystem. */ + struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */ }; extern void update_pm_runtime_accounting(struct device *dev); diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index daac05d751b2..6b90630e3c98 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -258,14 +258,18 @@ struct pm_clk_notifier_block { }; #ifdef CONFIG_PM_CLK -extern int pm_clk_init(struct device *dev); +extern void pm_clk_init(struct device *dev); +extern int pm_clk_create(struct device *dev); extern void pm_clk_destroy(struct device *dev); extern int pm_clk_add(struct device *dev, const char *con_id); extern void pm_clk_remove(struct device *dev, const char *con_id); extern int pm_clk_suspend(struct device *dev); extern int pm_clk_resume(struct device *dev); #else -static inline int pm_clk_init(struct device *dev) +static inline void pm_clk_init(struct device *dev) +{ +} +static inline int pm_clk_create(struct device *dev) { return -EINVAL; } -- cgit v1.2.3 From ef27bed1870dbd5fd363ff5ec51eebd5a695e277 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Aug 2011 15:34:01 +0200 Subject: PM: Reference counting of power.subsys_data Since the power.subsys_data device field will be used by multiple filesystems, introduce a reference counting mechanism for it to avoid freeing it prematurely or changing its value at a wrong time. Make the PM clocks management code that currently is the only user of power.subsys_data use the new reference counting. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/Makefile | 2 +- drivers/base/power/clock_ops.c | 24 +++--------- drivers/base/power/common.c | 87 ++++++++++++++++++++++++++++++++++++++++++ include/linux/pm.h | 3 ++ 4 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 drivers/base/power/common.c (limited to 'drivers/base') diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 2639ae79a372..6488ce12f586 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_PM) += sysfs.o generic_ops.o +obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_RUNTIME) += runtime.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index b7f1db4f5945..8383e2488d79 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -140,12 +140,8 @@ void pm_clk_remove(struct device *dev, const char *con_id) void pm_clk_init(struct device *dev) { struct pm_subsys_data *psd = dev_to_psd(dev); - - if (!psd) - return; - - INIT_LIST_HEAD(&psd->clock_list); - spin_lock_init(&psd->lock); + if (psd) + INIT_LIST_HEAD(&psd->clock_list); } /** @@ -157,16 +153,8 @@ void pm_clk_init(struct device *dev) */ int pm_clk_create(struct device *dev) { - struct pm_subsys_data *psd; - - psd = kzalloc(sizeof(*psd), GFP_KERNEL); - if (!psd) { - dev_err(dev, "Not enough memory for PM clock data.\n"); - return -ENOMEM; - } - dev->power.subsys_data = psd; - pm_clk_init(dev); - return 0; + int ret = dev_pm_get_subsys_data(dev); + return ret < 0 ? ret : 0; } /** @@ -185,8 +173,6 @@ void pm_clk_destroy(struct device *dev) if (!psd) return; - dev->power.subsys_data = NULL; - spin_lock_irq(&psd->lock); list_for_each_entry_safe_reverse(ce, c, &psd->clock_list, node) @@ -194,7 +180,7 @@ void pm_clk_destroy(struct device *dev) spin_unlock_irq(&psd->lock); - kfree(psd); + dev_pm_put_subsys_data(dev); } #endif /* CONFIG_PM */ diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c new file mode 100644 index 000000000000..d398cf029b32 --- /dev/null +++ b/drivers/base/power/common.c @@ -0,0 +1,87 @@ +/* + * drivers/base/power/common.c - Common device power management code. + * + * Copyright (C) 2011 Rafael J. Wysocki , Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include + +/** + * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device. + * @dev: Device to handle. + * + * If power.subsys_data is NULL, point it to a new object, otherwise increment + * its reference counter. Return 1 if a new object has been created, otherwise + * return 0 or error code. + */ +int dev_pm_get_subsys_data(struct device *dev) +{ + struct pm_subsys_data *psd; + int ret = 0; + + psd = kzalloc(sizeof(*psd), GFP_KERNEL); + if (!psd) + return -ENOMEM; + + spin_lock_irq(&dev->power.lock); + + if (dev->power.subsys_data) { + dev->power.subsys_data->refcount++; + } else { + spin_lock_init(&psd->lock); + psd->refcount = 1; + dev->power.subsys_data = psd; + pm_clk_init(dev); + psd = NULL; + ret = 1; + } + + spin_unlock_irq(&dev->power.lock); + + /* kfree() verifies that its argument is nonzero. */ + kfree(psd); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_get_subsys_data); + +/** + * dev_pm_put_subsys_data - Drop reference to power.subsys_data. + * @dev: Device to handle. + * + * If the reference counter of power.subsys_data is zero after dropping the + * reference, power.subsys_data is removed. Return 1 if that happens or 0 + * otherwise. + */ +int dev_pm_put_subsys_data(struct device *dev) +{ + struct pm_subsys_data *psd; + int ret = 0; + + spin_lock_irq(&dev->power.lock); + + psd = dev_to_psd(dev); + if (!psd) { + ret = -EINVAL; + goto out; + } + + if (--psd->refcount == 0) { + dev->power.subsys_data = NULL; + kfree(psd); + ret = 1; + } + + out: + spin_unlock_irq(&dev->power.lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_put_subsys_data); diff --git a/include/linux/pm.h b/include/linux/pm.h index bf5ee37388d4..c6b5a0a41ab3 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -423,6 +423,7 @@ struct wakeup_source; struct pm_subsys_data { spinlock_t lock; + unsigned int refcount; #ifdef CONFIG_PM_CLK struct list_head clock_list; #endif @@ -473,6 +474,8 @@ struct dev_pm_info { }; extern void update_pm_runtime_accounting(struct device *dev); +extern int dev_pm_get_subsys_data(struct device *dev); +extern int dev_pm_put_subsys_data(struct device *dev); /* * Power domains provide callbacks that are executed during system suspend, -- cgit v1.2.3 From 4605ab653c1f9d7cc2dda8033de215c9cee325f4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Aug 2011 15:34:12 +0200 Subject: PM / Domains: Use power.sybsys_data to reduce overhead Currently pm_genpd_runtime_resume() has to walk the list of devices from the device's PM domain to find the corresponding device list object containing the need_restore field to check if the driver's .runtime_resume() callback should be executed for the device. This is suboptimal and can be simplified by using power.sybsys_data to store device information used by the generic PM domains code. Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-shmobile/pm-sh7372.c | 6 +-- drivers/base/power/domain.c | 87 +++++++++++++------------------------- include/linux/pm.h | 9 ++++ include/linux/pm_domain.h | 6 --- include/linux/pm_runtime.h | 10 +++++ 5 files changed, 51 insertions(+), 67 deletions(-) (limited to 'drivers/base') diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 54d4d86883c9..4aeca0adae56 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -114,11 +114,9 @@ void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, { struct device *dev = &pdev->dev; - if (!dev->power.subsys_data) { - pm_clk_create(dev); - pm_clk_add(dev, NULL); - } pm_genpd_add_device(&sh7372_pd->genpd, dev); + if (pm_clk_no_clocks(dev)) + pm_clk_add(dev, NULL); } void sh7372_pm_add_subdomain(struct sh7372_pm_domain *sh7372_pd, diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 1fc6cc9835ad..339eb2d9bdda 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -181,18 +181,18 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) /** * __pm_genpd_save_device - Save the pre-suspend state of a device. - * @dle: Device list entry of the device to save the state of. + * @pdd: Domain data of the device to save the state of. * @genpd: PM domain the device belongs to. */ -static int __pm_genpd_save_device(struct dev_list_entry *dle, +static int __pm_genpd_save_device(struct pm_domain_data *pdd, struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { - struct device *dev = dle->dev; + struct device *dev = pdd->dev; struct device_driver *drv = dev->driver; int ret = 0; - if (dle->need_restore) + if (pdd->need_restore) return 0; mutex_unlock(&genpd->lock); @@ -210,24 +210,24 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, mutex_lock(&genpd->lock); if (!ret) - dle->need_restore = true; + pdd->need_restore = true; return ret; } /** * __pm_genpd_restore_device - Restore the pre-suspend state of a device. - * @dle: Device list entry of the device to restore the state of. + * @pdd: Domain data of the device to restore the state of. * @genpd: PM domain the device belongs to. */ -static void __pm_genpd_restore_device(struct dev_list_entry *dle, +static void __pm_genpd_restore_device(struct pm_domain_data *pdd, struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { - struct device *dev = dle->dev; + struct device *dev = pdd->dev; struct device_driver *drv = dev->driver; - if (!dle->need_restore) + if (!pdd->need_restore) return; mutex_unlock(&genpd->lock); @@ -244,7 +244,7 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, mutex_lock(&genpd->lock); - dle->need_restore = false; + pdd->need_restore = false; } /** @@ -286,7 +286,7 @@ void genpd_queue_power_off_work(struct generic_pm_domain *genpd) static int pm_genpd_poweroff(struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { - struct dev_list_entry *dle; + struct pm_domain_data *pdd; struct gpd_link *link; unsigned int not_suspended; int ret = 0; @@ -308,8 +308,8 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) return -EBUSY; not_suspended = 0; - list_for_each_entry(dle, &genpd->dev_list, node) - if (dle->dev->driver && !pm_runtime_suspended(dle->dev)) + list_for_each_entry(pdd, &genpd->dev_list, list_node) + if (pdd->dev->driver && !pm_runtime_suspended(pdd->dev)) not_suspended++; if (not_suspended > genpd->in_progress) @@ -332,9 +332,9 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd->status = GPD_STATE_BUSY; genpd->poweroff_task = current; - list_for_each_entry_reverse(dle, &genpd->dev_list, node) { + list_for_each_entry_reverse(pdd, &genpd->dev_list, list_node) { ret = atomic_read(&genpd->sd_count) == 0 ? - __pm_genpd_save_device(dle, genpd) : -EBUSY; + __pm_genpd_save_device(pdd, genpd) : -EBUSY; if (genpd_abort_poweroff(genpd)) goto out; @@ -432,24 +432,6 @@ static int pm_genpd_runtime_suspend(struct device *dev) return 0; } -/** - * __pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. - * @dev: Device to resume. - * @genpd: PM domain the device belongs to. - */ -static void __pm_genpd_runtime_resume(struct device *dev, - struct generic_pm_domain *genpd) -{ - struct dev_list_entry *dle; - - list_for_each_entry(dle, &genpd->dev_list, node) { - if (dle->dev == dev) { - __pm_genpd_restore_device(dle, genpd); - break; - } - } -} - /** * pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. * @dev: Device to resume. @@ -495,7 +477,7 @@ static int pm_genpd_runtime_resume(struct device *dev) mutex_lock(&genpd->lock); } finish_wait(&genpd->status_wait_queue, &wait); - __pm_genpd_runtime_resume(dev, genpd); + __pm_genpd_restore_device(&dev->power.subsys_data->domain_data, genpd); genpd->resume_count--; genpd_set_active(genpd); wake_up_all(&genpd->status_wait_queue); @@ -525,8 +507,6 @@ void pm_genpd_poweroff_unused(void) #else static inline void genpd_power_off_work_fn(struct work_struct *work) {} -static inline void __pm_genpd_runtime_resume(struct device *dev, - struct generic_pm_domain *genpd) {} #define pm_genpd_runtime_suspend NULL #define pm_genpd_runtime_resume NULL @@ -1083,7 +1063,7 @@ static void pm_genpd_complete(struct device *dev) */ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) { - struct dev_list_entry *dle; + struct pm_domain_data *pdd; int ret = 0; dev_dbg(dev, "%s()\n", __func__); @@ -1103,26 +1083,20 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) goto out; } - list_for_each_entry(dle, &genpd->dev_list, node) - if (dle->dev == dev) { + list_for_each_entry(pdd, &genpd->dev_list, list_node) + if (pdd->dev == dev) { ret = -EINVAL; goto out; } - dle = kzalloc(sizeof(*dle), GFP_KERNEL); - if (!dle) { - ret = -ENOMEM; - goto out; - } - - dle->dev = dev; - dle->need_restore = false; - list_add_tail(&dle->node, &genpd->dev_list); genpd->device_count++; - spin_lock_irq(&dev->power.lock); dev->pm_domain = &genpd->domain; - spin_unlock_irq(&dev->power.lock); + dev_pm_get_subsys_data(dev); + pdd = &dev->power.subsys_data->domain_data; + pdd->dev = dev; + pdd->need_restore = false; + list_add_tail(&pdd->list_node, &genpd->dev_list); out: genpd_release_lock(genpd); @@ -1138,7 +1112,7 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev) { - struct dev_list_entry *dle; + struct pm_domain_data *pdd; int ret = -EINVAL; dev_dbg(dev, "%s()\n", __func__); @@ -1153,17 +1127,16 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, goto out; } - list_for_each_entry(dle, &genpd->dev_list, node) { - if (dle->dev != dev) + list_for_each_entry(pdd, &genpd->dev_list, list_node) { + if (pdd->dev != dev) continue; - spin_lock_irq(&dev->power.lock); + list_del_init(&pdd->list_node); + pdd->dev = NULL; + dev_pm_put_subsys_data(dev); dev->pm_domain = NULL; - spin_unlock_irq(&dev->power.lock); genpd->device_count--; - list_del(&dle->node); - kfree(dle); ret = 0; break; diff --git a/include/linux/pm.h b/include/linux/pm.h index c6b5a0a41ab3..ed10f24d5259 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -421,12 +421,21 @@ enum rpm_request { struct wakeup_source; +struct pm_domain_data { + struct list_head list_node; + struct device *dev; + bool need_restore; +}; + struct pm_subsys_data { spinlock_t lock; unsigned int refcount; #ifdef CONFIG_PM_CLK struct list_head clock_list; #endif +#ifdef CONFIG_PM_GENERIC_DOMAINS + struct pm_domain_data domain_data; +#endif }; struct dev_pm_info { diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index bf679f59f9a8..5cce46c2d926 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -61,12 +61,6 @@ struct gpd_link { struct list_head slave_node; }; -struct dev_list_entry { - struct list_head node; - struct device *dev; - bool need_restore; -}; - #ifdef CONFIG_PM_GENERIC_DOMAINS extern int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev); diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 6b90630e3c98..a5a41a850efb 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -258,6 +258,12 @@ struct pm_clk_notifier_block { }; #ifdef CONFIG_PM_CLK +static inline bool pm_clk_no_clocks(struct device *dev) +{ + return dev && dev->power.subsys_data + && list_empty(&dev->power.subsys_data->clock_list); +} + extern void pm_clk_init(struct device *dev); extern int pm_clk_create(struct device *dev); extern void pm_clk_destroy(struct device *dev); @@ -266,6 +272,10 @@ extern void pm_clk_remove(struct device *dev, const char *con_id); extern int pm_clk_suspend(struct device *dev); extern int pm_clk_resume(struct device *dev); #else +static inline bool pm_clk_no_clocks(struct device *dev) +{ + return true; +} static inline void pm_clk_init(struct device *dev) { } -- cgit v1.2.3 From b5e8d269d814763d597ccc0108d1fa6639ad35a1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Aug 2011 15:34:19 +0200 Subject: PM: Move clock-related definitions and headers to separate file Since the PM clock management code in drivers/base/power/clock_ops.c is used for both runtime PM and system suspend/hibernation, the definitions of data structures and headers related to it should not be located in include/linux/pm_rumtime.h. Move them to a separate header file. Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-omap1/pm_bus.c | 1 + arch/arm/mach-shmobile/board-ap4evb.c | 1 + arch/arm/mach-shmobile/board-mackerel.c | 2 +- arch/arm/mach-shmobile/pm-sh7372.c | 2 +- arch/arm/mach-shmobile/pm_runtime.c | 1 + drivers/base/power/clock_ops.c | 2 +- drivers/base/power/common.c | 3 +- include/linux/pm_clock.h | 71 +++++++++++++++++++++++++++++++++ include/linux/pm_runtime.h | 56 -------------------------- 9 files changed, 78 insertions(+), 61 deletions(-) create mode 100644 include/linux/pm_clock.h (limited to 'drivers/base') diff --git a/arch/arm/mach-omap1/pm_bus.c b/arch/arm/mach-omap1/pm_bus.c index 943072d5a1d5..7868e75ad077 100644 --- a/arch/arm/mach-omap1/pm_bus.c +++ b/arch/arm/mach-omap1/pm_bus.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/arm/mach-shmobile/board-ap4evb.c b/arch/arm/mach-shmobile/board-ap4evb.c index fadbe5b3005d..074884c646db 100644 --- a/arch/arm/mach-shmobile/board-ap4evb.c +++ b/arch/arm/mach-shmobile/board-ap4evb.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include diff --git a/arch/arm/mach-shmobile/board-mackerel.c b/arch/arm/mach-shmobile/board-mackerel.c index 0ea71f8d4b89..4978e8186127 100644 --- a/arch/arm/mach-shmobile/board-mackerel.c +++ b/arch/arm/mach-shmobile/board-mackerel.c @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 4aeca0adae56..5d35831e4fb1 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/arm/mach-shmobile/pm_runtime.c b/arch/arm/mach-shmobile/pm_runtime.c index 6ec454e1e063..bd5c6a3b8c55 100644 --- a/arch/arm/mach-shmobile/pm_runtime.c +++ b/arch/arm/mach-shmobile/pm_runtime.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 8383e2488d79..cb44b58d6813 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index d398cf029b32..29820c396182 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -10,8 +10,7 @@ #include #include #include -#include -#include +#include /** * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device. diff --git a/include/linux/pm_clock.h b/include/linux/pm_clock.h new file mode 100644 index 000000000000..8348866e7b05 --- /dev/null +++ b/include/linux/pm_clock.h @@ -0,0 +1,71 @@ +/* + * pm_clock.h - Definitions and headers related to device clocks. + * + * Copyright (C) 2011 Rafael J. Wysocki , Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#ifndef _LINUX_PM_CLOCK_H +#define _LINUX_PM_CLOCK_H + +#include +#include + +struct pm_clk_notifier_block { + struct notifier_block nb; + struct dev_pm_domain *pm_domain; + char *con_ids[]; +}; + +#ifdef CONFIG_PM_CLK +static inline bool pm_clk_no_clocks(struct device *dev) +{ + return dev && dev->power.subsys_data + && list_empty(&dev->power.subsys_data->clock_list); +} + +extern void pm_clk_init(struct device *dev); +extern int pm_clk_create(struct device *dev); +extern void pm_clk_destroy(struct device *dev); +extern int pm_clk_add(struct device *dev, const char *con_id); +extern void pm_clk_remove(struct device *dev, const char *con_id); +extern int pm_clk_suspend(struct device *dev); +extern int pm_clk_resume(struct device *dev); +#else +static inline bool pm_clk_no_clocks(struct device *dev) +{ + return true; +} +static inline void pm_clk_init(struct device *dev) +{ +} +static inline int pm_clk_create(struct device *dev) +{ + return -EINVAL; +} +static inline void pm_clk_destroy(struct device *dev) +{ +} +static inline int pm_clk_add(struct device *dev, const char *con_id) +{ + return -EINVAL; +} +static inline void pm_clk_remove(struct device *dev, const char *con_id) +{ +} +#define pm_clk_suspend NULL +#define pm_clk_resume NULL +#endif + +#ifdef CONFIG_HAVE_CLK +extern void pm_clk_add_notifier(struct bus_type *bus, + struct pm_clk_notifier_block *clknb); +#else +static inline void pm_clk_add_notifier(struct bus_type *bus, + struct pm_clk_notifier_block *clknb) +{ +} +#endif + +#endif diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index a5a41a850efb..70b284024d9e 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -251,60 +251,4 @@ static inline void pm_runtime_dont_use_autosuspend(struct device *dev) __pm_runtime_use_autosuspend(dev, false); } -struct pm_clk_notifier_block { - struct notifier_block nb; - struct dev_pm_domain *pm_domain; - char *con_ids[]; -}; - -#ifdef CONFIG_PM_CLK -static inline bool pm_clk_no_clocks(struct device *dev) -{ - return dev && dev->power.subsys_data - && list_empty(&dev->power.subsys_data->clock_list); -} - -extern void pm_clk_init(struct device *dev); -extern int pm_clk_create(struct device *dev); -extern void pm_clk_destroy(struct device *dev); -extern int pm_clk_add(struct device *dev, const char *con_id); -extern void pm_clk_remove(struct device *dev, const char *con_id); -extern int pm_clk_suspend(struct device *dev); -extern int pm_clk_resume(struct device *dev); -#else -static inline bool pm_clk_no_clocks(struct device *dev) -{ - return true; -} -static inline void pm_clk_init(struct device *dev) -{ -} -static inline int pm_clk_create(struct device *dev) -{ - return -EINVAL; -} -static inline void pm_clk_destroy(struct device *dev) -{ -} -static inline int pm_clk_add(struct device *dev, const char *con_id) -{ - return -EINVAL; -} -static inline void pm_clk_remove(struct device *dev, const char *con_id) -{ -} -#define pm_clk_suspend NULL -#define pm_clk_resume NULL -#endif - -#ifdef CONFIG_HAVE_CLK -extern void pm_clk_add_notifier(struct bus_type *bus, - struct pm_clk_notifier_block *clknb); -#else -static inline void pm_clk_add_notifier(struct bus_type *bus, - struct pm_clk_notifier_block *clknb) -{ -} -#endif - #endif -- cgit v1.2.3 From 0aa2a221696cc8ea20a4cdca01315d3b6b4ecc4d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Aug 2011 15:37:04 +0200 Subject: PM / Domains: Preliminary support for devices with power.irq_safe set The generic PM domains framework currently doesn't work with devices whose power.irq_safe flag is set, because runtime PM callbacks for such devices are run with interrupts disabled and the callbacks provided by the generic PM domains framework use domain mutexes and may sleep. However, such devices very well may belong to power domains on some systems, so the generic PM domains framework should take them into account. For this reason, modify the generic PM domains framework so that the domain .power_off() and .power_on() callbacks are never executed for a domain containing devices with power.irq_safe set, although the .stop_device() and .start_device() callbacks are still run for them. Additionally, introduce a flag allowing the creator of a struct generic_pm_domain object to indicate that its .stop_device() and .start_device() callbacks may be run in interrupt context (might_sleep_if() triggers if that flag is not set and one of those callbacks is run in interrupt context). Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-shmobile/pm-sh7372.c | 1 + drivers/base/power/domain.c | 19 ++++++++++++++++++- include/linux/pm_domain.h | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 5d35831e4fb1..ac47bfcd287e 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -103,6 +103,7 @@ void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) pm_genpd_init(genpd, NULL, false); genpd->stop_device = pm_clk_suspend; genpd->start_device = pm_clk_resume; + genpd->dev_irq_safe = true; genpd->active_wakeup = pd_active_wakeup; genpd->power_off = pd_power_down; genpd->power_on = pd_power_up; diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 339eb2d9bdda..c2468a7e5b21 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -309,7 +309,8 @@ 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)) + if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev) + || pdd->dev->power.irq_safe)) not_suspended++; if (not_suspended > genpd->in_progress) @@ -417,12 +418,21 @@ static int pm_genpd_runtime_suspend(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; + might_sleep_if(!genpd->dev_irq_safe); + if (genpd->stop_device) { int ret = genpd->stop_device(dev); if (ret) return ret; } + /* + * If power.irq_safe is set, this routine will be run with interrupts + * off, so it can't use mutexes. + */ + if (dev->power.irq_safe) + return 0; + mutex_lock(&genpd->lock); genpd->in_progress++; pm_genpd_poweroff(genpd); @@ -452,6 +462,12 @@ static int pm_genpd_runtime_resume(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; + might_sleep_if(!genpd->dev_irq_safe); + + /* If power.irq_safe, the PM domain is never powered off. */ + if (dev->power.irq_safe) + goto out; + mutex_lock(&genpd->lock); ret = __pm_genpd_poweron(genpd); if (ret) { @@ -483,6 +499,7 @@ static int pm_genpd_runtime_resume(struct device *dev) wake_up_all(&genpd->status_wait_queue); mutex_unlock(&genpd->lock); + out: if (genpd->start_device) genpd->start_device(dev); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 5cce46c2d926..2538d906bcd1 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -42,6 +42,7 @@ struct generic_pm_domain { unsigned int suspended_count; /* System suspend device counter */ unsigned int prepared_count; /* Suspend counter of prepared devices */ bool suspend_power_off; /* Power status before system suspend */ + bool dev_irq_safe; /* Device callbacks are IRQ-safe */ int (*power_off)(struct generic_pm_domain *domain); int (*power_on)(struct generic_pm_domain *domain); int (*start_device)(struct device *dev); -- cgit v1.2.3 From cd0ea672f58d5cfdea271c45cec0c897f2b792aa Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 26 Sep 2011 20:22:02 +0200 Subject: PM / Domains: Split device PM domain data into base and need_restore The struct pm_domain_data data type is defined in such a way that adding new fields specific to the generic PM domains code will require include/linux/pm.h to be modified. As a result, data types used only by the generic PM domains code will be defined in two headers, although they all should be defined in pm_domain.h and pm.h will need to include more headers, which won't be very nice. For this reason change the definition of struct pm_subsys_data so that its domain_data member is a pointer, which will allow struct pm_domain_data to be subclassed by various PM domains implementations. Remove the need_restore member from struct pm_domain_data and make the generic PM domains code subclass it by adding the need_restore member to the new data type. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 28 +++++++++++++++++++--------- include/linux/pm.h | 3 +-- include/linux/pm_domain.h | 10 ++++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index c2468a7e5b21..22fe029ca212 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -188,11 +188,12 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd, struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { + struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); struct device *dev = pdd->dev; struct device_driver *drv = dev->driver; int ret = 0; - if (pdd->need_restore) + if (gpd_data->need_restore) return 0; mutex_unlock(&genpd->lock); @@ -210,7 +211,7 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd, mutex_lock(&genpd->lock); if (!ret) - pdd->need_restore = true; + gpd_data->need_restore = true; return ret; } @@ -224,10 +225,11 @@ static void __pm_genpd_restore_device(struct pm_domain_data *pdd, struct generic_pm_domain *genpd) __releases(&genpd->lock) __acquires(&genpd->lock) { + struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); struct device *dev = pdd->dev; struct device_driver *drv = dev->driver; - if (!pdd->need_restore) + if (!gpd_data->need_restore) return; mutex_unlock(&genpd->lock); @@ -244,7 +246,7 @@ static void __pm_genpd_restore_device(struct pm_domain_data *pdd, mutex_lock(&genpd->lock); - pdd->need_restore = false; + gpd_data->need_restore = false; } /** @@ -493,7 +495,7 @@ static int pm_genpd_runtime_resume(struct device *dev) mutex_lock(&genpd->lock); } finish_wait(&genpd->status_wait_queue, &wait); - __pm_genpd_restore_device(&dev->power.subsys_data->domain_data, genpd); + __pm_genpd_restore_device(dev->power.subsys_data->domain_data, genpd); genpd->resume_count--; genpd_set_active(genpd); wake_up_all(&genpd->status_wait_queue); @@ -1080,6 +1082,7 @@ static void pm_genpd_complete(struct device *dev) */ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) { + struct generic_pm_domain_data *gpd_data; struct pm_domain_data *pdd; int ret = 0; @@ -1106,14 +1109,20 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) goto out; } + gpd_data = kzalloc(sizeof(*gpd_data), GFP_KERNEL); + if (!gpd_data) { + ret = -ENOMEM; + goto out; + } + genpd->device_count++; dev->pm_domain = &genpd->domain; dev_pm_get_subsys_data(dev); - pdd = &dev->power.subsys_data->domain_data; - pdd->dev = dev; - pdd->need_restore = false; - list_add_tail(&pdd->list_node, &genpd->dev_list); + dev->power.subsys_data->domain_data = &gpd_data->base; + gpd_data->base.dev = dev; + gpd_data->need_restore = false; + list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); out: genpd_release_lock(genpd); @@ -1152,6 +1161,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, pdd->dev = NULL; dev_pm_put_subsys_data(dev); dev->pm_domain = NULL; + kfree(to_gpd_data(pdd)); genpd->device_count--; diff --git a/include/linux/pm.h b/include/linux/pm.h index ed10f24d5259..f25682477f08 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -424,7 +424,6 @@ struct wakeup_source; struct pm_domain_data { struct list_head list_node; struct device *dev; - bool need_restore; }; struct pm_subsys_data { @@ -434,7 +433,7 @@ struct pm_subsys_data { struct list_head clock_list; #endif #ifdef CONFIG_PM_GENERIC_DOMAINS - struct pm_domain_data domain_data; + struct pm_domain_data *domain_data; #endif }; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 2538d906bcd1..65633e5a2bc0 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -62,6 +62,16 @@ struct gpd_link { struct list_head slave_node; }; +struct generic_pm_domain_data { + struct pm_domain_data base; + bool need_restore; +}; + +static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data *pdd) +{ + return container_of(pdd, struct generic_pm_domain_data, base); +} + #ifdef CONFIG_PM_GENERIC_DOMAINS extern int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev); -- cgit v1.2.3