diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/cpu.c | 21 | ||||
-rw-r--r-- | drivers/base/memory.c | 31 | ||||
-rw-r--r-- | drivers/base/node.c | 8 | ||||
-rw-r--r-- | drivers/base/power/power.h | 4 | ||||
-rw-r--r-- | drivers/base/power/qos.c | 61 | ||||
-rw-r--r-- | drivers/base/power/sysfs.c | 47 | ||||
-rw-r--r-- | drivers/base/power/wakeup.c | 85 | ||||
-rw-r--r-- | drivers/base/regmap/regcache.c | 4 |
8 files changed, 233 insertions, 28 deletions
diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index db87e78d7459..4dabf5077c48 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -208,6 +208,25 @@ static ssize_t print_cpus_offline(struct device *dev, } static DEVICE_ATTR(offline, 0444, print_cpus_offline, NULL); +static void cpu_device_release(struct device *dev) +{ + /* + * This is an empty function to prevent the driver core from spitting a + * warning at us. Yes, I know this is directly opposite of what the + * documentation for the driver core and kobjects say, and the author + * of this code has already been publically ridiculed for doing + * something as foolish as this. However, at this point in time, it is + * the only way to handle the issue of statically allocated cpu + * devices. The different architectures will have their cpu device + * code reworked to properly handle this in the near future, so this + * function will then be changed to correctly free up the memory held + * by the cpu device. + * + * Never copy this way of doing things, or you too will be made fun of + * on the linux-kerenl list, you have been warned. + */ +} + /* * register_cpu - Setup a sysfs device for a CPU. * @cpu - cpu->hotpluggable field set to 1 will generate a control file in @@ -221,8 +240,10 @@ int __cpuinit register_cpu(struct cpu *cpu, int num) int error; cpu->node_id = cpu_to_node(num); + memset(&cpu->dev, 0x00, sizeof(struct device)); cpu->dev.id = num; cpu->dev.bus = &cpu_subsys; + cpu->dev.release = cpu_device_release; error = device_register(&cpu->dev); if (!error && cpu->hotpluggable) register_cpu_control(cpu); diff --git a/drivers/base/memory.c b/drivers/base/memory.c index ed5de58c340f..9e60dbe9fd94 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -572,19 +572,36 @@ static int init_memory_block(struct memory_block **memory, } static int add_memory_section(int nid, struct mem_section *section, + struct memory_block **mem_p, unsigned long state, enum mem_add_context context) { - struct memory_block *mem; + struct memory_block *mem = NULL; + int scn_nr = __section_nr(section); int ret = 0; mutex_lock(&mem_sysfs_mutex); - mem = find_memory_block(section); + if (context == BOOT) { + /* same memory block ? */ + if (mem_p && *mem_p) + if (scn_nr >= (*mem_p)->start_section_nr && + scn_nr <= (*mem_p)->end_section_nr) { + mem = *mem_p; + kobject_get(&mem->dev.kobj); + } + } else + mem = find_memory_block(section); + if (mem) { mem->section_count++; kobject_put(&mem->dev.kobj); - } else + } else { ret = init_memory_block(&mem, section, state); + /* store memory_block pointer for next loop */ + if (!ret && context == BOOT) + if (mem_p) + *mem_p = mem; + } if (!ret) { if (context == HOTPLUG && @@ -627,7 +644,7 @@ int remove_memory_block(unsigned long node_id, struct mem_section *section, */ int register_new_memory(int nid, struct mem_section *section) { - return add_memory_section(nid, section, MEM_OFFLINE, HOTPLUG); + return add_memory_section(nid, section, NULL, MEM_OFFLINE, HOTPLUG); } int unregister_memory_section(struct mem_section *section) @@ -647,6 +664,7 @@ int __init memory_dev_init(void) int ret; int err; unsigned long block_sz; + struct memory_block *mem = NULL; ret = subsys_system_register(&memory_subsys, NULL); if (ret) @@ -662,7 +680,10 @@ int __init memory_dev_init(void) for (i = 0; i < NR_MEM_SECTIONS; i++) { if (!present_section_nr(i)) continue; - err = add_memory_section(0, __nr_to_section(i), MEM_ONLINE, + /* don't need to reuse memory_block if only one per block */ + err = add_memory_section(0, __nr_to_section(i), + (sections_per_block == 1) ? NULL : &mem, + MEM_ONLINE, BOOT); if (!ret) ret = err; diff --git a/drivers/base/node.c b/drivers/base/node.c index 44f427a66117..90aa2a11a933 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -456,7 +456,15 @@ static int link_mem_sections(int nid) if (!present_section_nr(section_nr)) continue; mem_sect = __nr_to_section(section_nr); + + /* same memblock ? */ + if (mem_blk) + if ((section_nr >= mem_blk->start_section_nr) && + (section_nr <= mem_blk->end_section_nr)) + continue; + mem_blk = find_memory_block_hinted(mem_sect, mem_blk); + ret = register_mem_sect_under_node(mem_blk, nid); if (!err) err = ret; diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 9bf62323aaf3..eeb4bff9505c 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -71,6 +71,8 @@ extern void dpm_sysfs_remove(struct device *dev); extern void rpm_sysfs_remove(struct device *dev); extern int wakeup_sysfs_add(struct device *dev); extern void wakeup_sysfs_remove(struct device *dev); +extern int pm_qos_sysfs_add(struct device *dev); +extern void pm_qos_sysfs_remove(struct device *dev); #else /* CONFIG_PM */ @@ -79,5 +81,7 @@ static inline void dpm_sysfs_remove(struct device *dev) {} static inline void rpm_sysfs_remove(struct device *dev) {} static inline int wakeup_sysfs_add(struct device *dev) { return 0; } static inline void wakeup_sysfs_remove(struct device *dev) {} +static inline int pm_qos_sysfs_add(struct device *dev) { return 0; } +static inline void pm_qos_sysfs_remove(struct device *dev) {} #endif diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index c5d358837461..71855570922d 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -41,6 +41,7 @@ #include <linux/mutex.h> #include <linux/export.h> +#include "power.h" static DEFINE_MUTEX(dev_pm_qos_mtx); @@ -166,6 +167,12 @@ void dev_pm_qos_constraints_destroy(struct device *dev) struct dev_pm_qos_request *req, *tmp; struct pm_qos_constraints *c; + /* + * If the device's PM QoS resume latency limit has been exposed to user + * space, it has to be hidden at this point. + */ + dev_pm_qos_hide_latency_limit(dev); + mutex_lock(&dev_pm_qos_mtx); dev->power.power_state = PMSG_INVALID; @@ -445,3 +452,57 @@ int dev_pm_qos_add_ancestor_request(struct device *dev, return error; } EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request); + +#ifdef CONFIG_PM_RUNTIME +static void __dev_pm_qos_drop_user_request(struct device *dev) +{ + dev_pm_qos_remove_request(dev->power.pq_req); + dev->power.pq_req = 0; +} + +/** + * dev_pm_qos_expose_latency_limit - Expose PM QoS latency limit to user space. + * @dev: Device whose PM QoS latency limit is to be exposed to user space. + * @value: Initial value of the latency limit. + */ +int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value) +{ + struct dev_pm_qos_request *req; + int ret; + + if (!device_is_registered(dev) || value < 0) + return -EINVAL; + + if (dev->power.pq_req) + return -EEXIST; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + ret = dev_pm_qos_add_request(dev, req, value); + if (ret < 0) + return ret; + + dev->power.pq_req = req; + ret = pm_qos_sysfs_add(dev); + if (ret) + __dev_pm_qos_drop_user_request(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit); + +/** + * dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space. + * @dev: Device whose PM QoS latency limit is to be hidden from user space. + */ +void dev_pm_qos_hide_latency_limit(struct device *dev) +{ + if (dev->power.pq_req) { + pm_qos_sysfs_remove(dev); + __dev_pm_qos_drop_user_request(dev); + } +} +EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit); +#endif /* CONFIG_PM_RUNTIME */ diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index adf41be0ea66..95c12f6cb5b9 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -5,6 +5,7 @@ #include <linux/device.h> #include <linux/string.h> #include <linux/export.h> +#include <linux/pm_qos.h> #include <linux/pm_runtime.h> #include <linux/atomic.h> #include <linux/jiffies.h> @@ -217,6 +218,31 @@ static ssize_t autosuspend_delay_ms_store(struct device *dev, static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show, autosuspend_delay_ms_store); +static ssize_t pm_qos_latency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dev->power.pq_req->node.prio); +} + +static ssize_t pm_qos_latency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t n) +{ + s32 value; + int ret; + + if (kstrtos32(buf, 0, &value)) + return -EINVAL; + + if (value < 0) + return -EINVAL; + + ret = dev_pm_qos_update_request(dev->power.pq_req, value); + return ret < 0 ? ret : n; +} + +static DEVICE_ATTR(pm_qos_resume_latency_us, 0644, + pm_qos_latency_show, pm_qos_latency_store); #endif /* CONFIG_PM_RUNTIME */ #ifdef CONFIG_PM_SLEEP @@ -490,6 +516,17 @@ static struct attribute_group pm_runtime_attr_group = { .attrs = runtime_attrs, }; +static struct attribute *pm_qos_attrs[] = { +#ifdef CONFIG_PM_RUNTIME + &dev_attr_pm_qos_resume_latency_us.attr, +#endif /* CONFIG_PM_RUNTIME */ + NULL, +}; +static struct attribute_group pm_qos_attr_group = { + .name = power_group_name, + .attrs = pm_qos_attrs, +}; + int dpm_sysfs_add(struct device *dev) { int rc; @@ -530,6 +567,16 @@ void wakeup_sysfs_remove(struct device *dev) sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); } +int pm_qos_sysfs_add(struct device *dev) +{ + return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group); +} + +void pm_qos_sysfs_remove(struct device *dev) +{ + sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group); +} + void rpm_sysfs_remove(struct device *dev) { sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group); diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index caf995fb774b..2a3e581b8dcd 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -53,6 +53,23 @@ static void pm_wakeup_timer_fn(unsigned long data); static LIST_HEAD(wakeup_sources); /** + * wakeup_source_prepare - Prepare a new wakeup source for initialization. + * @ws: Wakeup source to prepare. + * @name: Pointer to the name of the new wakeup source. + * + * Callers must ensure that the @name string won't be freed when @ws is still in + * use. + */ +void wakeup_source_prepare(struct wakeup_source *ws, const char *name) +{ + if (ws) { + memset(ws, 0, sizeof(*ws)); + ws->name = name; + } +} +EXPORT_SYMBOL_GPL(wakeup_source_prepare); + +/** * wakeup_source_create - Create a struct wakeup_source object. * @name: Name of the new wakeup source. */ @@ -60,37 +77,44 @@ struct wakeup_source *wakeup_source_create(const char *name) { struct wakeup_source *ws; - ws = kzalloc(sizeof(*ws), GFP_KERNEL); + ws = kmalloc(sizeof(*ws), GFP_KERNEL); if (!ws) return NULL; - spin_lock_init(&ws->lock); - if (name) - ws->name = kstrdup(name, GFP_KERNEL); - + wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL); return ws; } EXPORT_SYMBOL_GPL(wakeup_source_create); /** + * wakeup_source_drop - Prepare a struct wakeup_source object for destruction. + * @ws: Wakeup source to prepare for destruction. + * + * Callers must ensure that __pm_stay_awake() or __pm_wakeup_event() will never + * be run in parallel with this function for the same wakeup source object. + */ +void wakeup_source_drop(struct wakeup_source *ws) +{ + if (!ws) + return; + + del_timer_sync(&ws->timer); + __pm_relax(ws); +} +EXPORT_SYMBOL_GPL(wakeup_source_drop); + +/** * wakeup_source_destroy - Destroy a struct wakeup_source object. * @ws: Wakeup source to destroy. + * + * Use only for wakeup source objects created with wakeup_source_create(). */ void wakeup_source_destroy(struct wakeup_source *ws) { if (!ws) return; - spin_lock_irq(&ws->lock); - while (ws->active) { - spin_unlock_irq(&ws->lock); - - schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT)); - - spin_lock_irq(&ws->lock); - } - spin_unlock_irq(&ws->lock); - + wakeup_source_drop(ws); kfree(ws->name); kfree(ws); } @@ -105,6 +129,7 @@ void wakeup_source_add(struct wakeup_source *ws) if (WARN_ON(!ws)) return; + spin_lock_init(&ws->lock); setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws); ws->active = false; @@ -152,8 +177,10 @@ EXPORT_SYMBOL_GPL(wakeup_source_register); */ void wakeup_source_unregister(struct wakeup_source *ws) { - wakeup_source_remove(ws); - wakeup_source_destroy(ws); + if (ws) { + wakeup_source_remove(ws); + wakeup_source_destroy(ws); + } } EXPORT_SYMBOL_GPL(wakeup_source_unregister); @@ -349,7 +376,6 @@ static void wakeup_source_activate(struct wakeup_source *ws) { ws->active = true; ws->active_count++; - ws->timer_expires = jiffies; ws->last_time = ktime_get(); /* Increment the counter of events in progress. */ @@ -370,9 +396,14 @@ void __pm_stay_awake(struct wakeup_source *ws) return; spin_lock_irqsave(&ws->lock, flags); + ws->event_count++; if (!ws->active) wakeup_source_activate(ws); + + del_timer(&ws->timer); + ws->timer_expires = 0; + spin_unlock_irqrestore(&ws->lock, flags); } EXPORT_SYMBOL_GPL(__pm_stay_awake); @@ -438,6 +469,7 @@ static void wakeup_source_deactivate(struct wakeup_source *ws) ws->max_time = duration; del_timer(&ws->timer); + ws->timer_expires = 0; /* * Increment the counter of registered wakeup events and decrement the @@ -492,11 +524,22 @@ EXPORT_SYMBOL_GPL(pm_relax); * pm_wakeup_timer_fn - Delayed finalization of a wakeup event. * @data: Address of the wakeup source object associated with the event source. * - * Call __pm_relax() for the wakeup source whose address is stored in @data. + * Call wakeup_source_deactivate() for the wakeup source whose address is stored + * in @data if it is currently active and its timer has not been canceled and + * the expiration time of the timer is not in future. */ static void pm_wakeup_timer_fn(unsigned long data) { - __pm_relax((struct wakeup_source *)data); + struct wakeup_source *ws = (struct wakeup_source *)data; + unsigned long flags; + + spin_lock_irqsave(&ws->lock, flags); + + if (ws->active && ws->timer_expires + && time_after_eq(jiffies, ws->timer_expires)) + wakeup_source_deactivate(ws); + + spin_unlock_irqrestore(&ws->lock, flags); } /** @@ -534,7 +577,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) if (!expires) expires = 1; - if (time_after(expires, ws->timer_expires)) { + if (!ws->timer_expires || time_after(expires, ws->timer_expires)) { mod_timer(&ws->timer, expires); ws->timer_expires = expires; } diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 1ead66186b7c..d1daa5e9fadf 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -53,7 +53,7 @@ static int regcache_hw_init(struct regmap *map) for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) { val = regcache_get_val(map->reg_defaults_raw, i, map->cache_word_size); - if (!val) + if (regmap_volatile(map, i)) continue; count++; } @@ -70,7 +70,7 @@ static int regcache_hw_init(struct regmap *map) for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { val = regcache_get_val(map->reg_defaults_raw, i, map->cache_word_size); - if (!val) + if (regmap_volatile(map, i)) continue; map->reg_defaults[j].reg = i; map->reg_defaults[j].def = val; |