diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Kconfig | 8 | ||||
-rw-r--r-- | drivers/base/Makefile | 2 | ||||
-rw-r--r-- | drivers/base/platform.c | 1 | ||||
-rw-r--r-- | drivers/base/power/clock_ops.c | 150 | ||||
-rw-r--r-- | drivers/base/power/domain.c | 147 | ||||
-rw-r--r-- | drivers/base/power/domain_governor.c | 11 | ||||
-rw-r--r-- | drivers/base/power/opp.c | 196 | ||||
-rw-r--r-- | drivers/base/power/power.h | 56 | ||||
-rw-r--r-- | drivers/base/power/qos.c | 5 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 76 | ||||
-rw-r--r-- | drivers/base/power/sysfs.c | 19 | ||||
-rw-r--r-- | drivers/base/property.c | 431 | ||||
-rw-r--r-- | drivers/base/regmap/Kconfig | 5 | ||||
-rw-r--r-- | drivers/base/regmap/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-flat.c | 2 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-lzo.c | 2 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-rbtree.c | 4 | ||||
-rw-r--r-- | drivers/base/regmap/regcache.c | 63 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-ac97.c | 114 |
19 files changed, 936 insertions, 357 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index df04227d00cf..98504ec99c7d 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -267,18 +267,24 @@ comment "Default contiguous memory area size:" config CMA_SIZE_MBYTES int "Size in Mega Bytes" depends on !CMA_SIZE_SEL_PERCENTAGE + default 0 if X86 default 16 help Defines the size (in MiB) of the default memory area for Contiguous - Memory Allocator. + Memory Allocator. If the size of 0 is selected, CMA is disabled by + default, but it can be enabled by passing cma=size[MG] to the kernel. + config CMA_SIZE_PERCENTAGE int "Percentage of total memory" depends on !CMA_SIZE_SEL_MBYTES + default 0 if X86 default 10 help Defines the size of the default memory area for Contiguous Memory Allocator as a percentage of the total memory in the system. + If 0 percent is selected, CMA is disabled by default, but it can be + enabled by passing cma=size[MG] to the kernel. choice prompt "Selected region size" diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 6922cd6850a2..53c3fe1aeb29 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -4,7 +4,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o + topology.o container.o property.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/platform.c b/drivers/base/platform.c index b2afc29403f9..233ececd15a3 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -1006,6 +1006,7 @@ int __init platform_bus_init(void) error = bus_register(&platform_bus_type); if (error) device_unregister(&platform_bus); + of_platform_register_reconfig_notifier(); return error; } diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 78369305e069..d626576a4f75 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -12,6 +12,7 @@ #include <linux/pm.h> #include <linux/pm_clock.h> #include <linux/clk.h> +#include <linux/clkdev.h> #include <linux/slab.h> #include <linux/err.h> @@ -34,14 +35,20 @@ struct pm_clock_entry { /** * pm_clk_enable - Enable a clock, reporting any errors * @dev: The device for the given clock - * @clk: The clock being enabled. + * @ce: PM clock entry corresponding to the clock. */ -static inline int __pm_clk_enable(struct device *dev, struct clk *clk) +static inline int __pm_clk_enable(struct device *dev, struct pm_clock_entry *ce) { - int ret = clk_enable(clk); - if (ret) - dev_err(dev, "%s: failed to enable clk %p, error %d\n", - __func__, clk, ret); + int ret; + + if (ce->status < PCE_STATUS_ERROR) { + ret = clk_enable(ce->clk); + if (!ret) + ce->status = PCE_STATUS_ENABLED; + else + dev_err(dev, "%s: failed to enable clk %p, error %d\n", + __func__, ce->clk, ret); + } return ret; } @@ -53,7 +60,8 @@ static inline int __pm_clk_enable(struct device *dev, struct clk *clk) */ static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce) { - ce->clk = clk_get(dev, ce->con_id); + if (!ce->clk) + ce->clk = clk_get(dev, ce->con_id); if (IS_ERR(ce->clk)) { ce->status = PCE_STATUS_ERROR; } else { @@ -63,15 +71,8 @@ static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce) } } -/** - * pm_clk_add - Start using a device clock for power management. - * @dev: Device whose clock is going to be used for power management. - * @con_id: Connection ID of the clock. - * - * Add the clock represented by @con_id to the list of clocks used for - * the power management of @dev. - */ -int pm_clk_add(struct device *dev, const char *con_id) +static int __pm_clk_add(struct device *dev, const char *con_id, + struct clk *clk) { struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; @@ -93,6 +94,12 @@ int pm_clk_add(struct device *dev, const char *con_id) kfree(ce); return -ENOMEM; } + } else { + if (IS_ERR(ce->clk) || !__clk_get(clk)) { + kfree(ce); + return -ENOENT; + } + ce->clk = clk; } pm_clk_acquire(dev, ce); @@ -104,6 +111,32 @@ int pm_clk_add(struct device *dev, const char *con_id) } /** + * pm_clk_add - Start using a device clock for power management. + * @dev: Device whose clock is going to be used for power management. + * @con_id: Connection ID of the clock. + * + * Add the clock represented by @con_id to the list of clocks used for + * the power management of @dev. + */ +int pm_clk_add(struct device *dev, const char *con_id) +{ + return __pm_clk_add(dev, con_id, NULL); +} + +/** + * pm_clk_add_clk - Start using a device clock for power management. + * @dev: Device whose clock is going to be used for power management. + * @clk: Clock pointer + * + * Add the clock to the list of clocks used for the power management of @dev. + * It will increment refcount on clock pointer, use clk_put() on it when done. + */ +int pm_clk_add_clk(struct device *dev, struct clk *clk) +{ + return __pm_clk_add(dev, NULL, clk); +} + +/** * __pm_clk_remove - Destroy PM clock entry. * @ce: PM clock entry to destroy. */ @@ -223,10 +256,6 @@ void pm_clk_destroy(struct device *dev) } } -#endif /* CONFIG_PM */ - -#ifdef CONFIG_PM_RUNTIME - /** * pm_clk_suspend - Disable clocks in a device's PM clock list. * @dev: Device to disable the clocks for. @@ -266,7 +295,6 @@ int pm_clk_resume(struct device *dev) struct pm_subsys_data *psd = dev_to_psd(dev); struct pm_clock_entry *ce; unsigned long flags; - int ret; dev_dbg(dev, "%s()\n", __func__); @@ -275,13 +303,8 @@ int pm_clk_resume(struct device *dev) spin_lock_irqsave(&psd->lock, flags); - list_for_each_entry(ce, &psd->clock_list, node) { - if (ce->status < PCE_STATUS_ERROR) { - ret = __pm_clk_enable(dev, ce->clk); - if (!ret) - ce->status = PCE_STATUS_ENABLED; - } - } + list_for_each_entry(ce, &psd->clock_list, node) + __pm_clk_enable(dev, ce); spin_unlock_irqrestore(&psd->lock, flags); @@ -346,74 +369,7 @@ static int pm_clk_notify(struct notifier_block *nb, return 0; } -#else /* !CONFIG_PM_RUNTIME */ - -#ifdef CONFIG_PM - -/** - * pm_clk_suspend - Disable clocks in a device's PM clock list. - * @dev: Device to disable the clocks for. - */ -int pm_clk_suspend(struct device *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 (!psd || !dev->driver) - return 0; - - spin_lock_irqsave(&psd->lock, flags); - - list_for_each_entry_reverse(ce, &psd->clock_list, node) { - if (ce->status < PCE_STATUS_ERROR) { - if (ce->status == PCE_STATUS_ENABLED) - clk_disable(ce->clk); - ce->status = PCE_STATUS_ACQUIRED; - } - } - - spin_unlock_irqrestore(&psd->lock, flags); - - return 0; -} - -/** - * pm_clk_resume - Enable clocks in a device's PM clock list. - * @dev: Device to enable the clocks for. - */ -int pm_clk_resume(struct device *dev) -{ - struct pm_subsys_data *psd = dev_to_psd(dev); - struct pm_clock_entry *ce; - unsigned long flags; - int ret; - - dev_dbg(dev, "%s()\n", __func__); - - /* If there is no driver, the clocks should remain disabled. */ - if (!psd || !dev->driver) - return 0; - - spin_lock_irqsave(&psd->lock, flags); - - list_for_each_entry(ce, &psd->clock_list, node) { - if (ce->status < PCE_STATUS_ERROR) { - ret = __pm_clk_enable(dev, ce->clk); - if (!ret) - ce->status = PCE_STATUS_ENABLED; - } - } - - spin_unlock_irqrestore(&psd->lock, flags); - - return 0; -} - -#endif /* CONFIG_PM */ +#else /* !CONFIG_PM */ /** * enable_clock - Enable a device clock. @@ -493,7 +449,7 @@ static int pm_clk_notify(struct notifier_block *nb, return 0; } -#endif /* !CONFIG_PM_RUNTIME */ +#endif /* !CONFIG_PM */ /** * pm_clk_add_notifier - Add bus type notifier for power management clocks. diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index fb83d4acd400..6a103a35ea9b 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -12,6 +12,7 @@ #include <linux/pm_runtime.h> #include <linux/pm_domain.h> #include <linux/pm_qos.h> +#include <linux/pm_clock.h> #include <linux/slab.h> #include <linux/err.h> #include <linux/sched.h> @@ -151,6 +152,59 @@ static void genpd_recalc_cpu_exit_latency(struct generic_pm_domain *genpd) genpd->cpuidle_data->idle_state->exit_latency = usecs64; } +static int genpd_power_on(struct generic_pm_domain *genpd) +{ + ktime_t time_start; + s64 elapsed_ns; + int ret; + + if (!genpd->power_on) + return 0; + + time_start = ktime_get(); + ret = genpd->power_on(genpd); + if (ret) + return ret; + + elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); + if (elapsed_ns <= genpd->power_on_latency_ns) + return ret; + + genpd->power_on_latency_ns = elapsed_ns; + genpd->max_off_time_changed = true; + genpd_recalc_cpu_exit_latency(genpd); + pr_warn("%s: Power-%s latency exceeded, new value %lld ns\n", + genpd->name, "on", elapsed_ns); + + return ret; +} + +static int genpd_power_off(struct generic_pm_domain *genpd) +{ + ktime_t time_start; + s64 elapsed_ns; + int ret; + + if (!genpd->power_off) + return 0; + + time_start = ktime_get(); + ret = genpd->power_off(genpd); + if (ret == -EBUSY) + return ret; + + elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); + if (elapsed_ns <= genpd->power_off_latency_ns) + return ret; + + genpd->power_off_latency_ns = elapsed_ns; + genpd->max_off_time_changed = true; + pr_warn("%s: Power-%s latency exceeded, new value %lld ns\n", + genpd->name, "off", elapsed_ns); + + return ret; +} + /** * __pm_genpd_poweron - Restore power to a given PM domain and its masters. * @genpd: PM domain to power up. @@ -222,25 +276,9 @@ static int __pm_genpd_poweron(struct generic_pm_domain *genpd) } } - if (genpd->power_on) { - ktime_t time_start = ktime_get(); - s64 elapsed_ns; - - ret = genpd->power_on(genpd); - if (ret) - goto err; - - elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); - if (elapsed_ns > genpd->power_on_latency_ns) { - genpd->power_on_latency_ns = elapsed_ns; - genpd->max_off_time_changed = true; - genpd_recalc_cpu_exit_latency(genpd); - if (genpd->name) - pr_warning("%s: Power-on latency exceeded, " - "new value %lld ns\n", genpd->name, - elapsed_ns); - } - } + ret = genpd_power_on(genpd); + if (ret) + goto err; out: genpd_set_active(genpd); @@ -280,8 +318,6 @@ int pm_genpd_name_poweron(const char *domain_name) return genpd ? pm_genpd_poweron(genpd) : -EINVAL; } -#ifdef CONFIG_PM_RUNTIME - static int genpd_start_dev_no_timing(struct generic_pm_domain *genpd, struct device *dev) { @@ -544,16 +580,11 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } if (genpd->power_off) { - ktime_t time_start; - s64 elapsed_ns; - if (atomic_read(&genpd->sd_count) > 0) { ret = -EBUSY; goto out; } - time_start = ktime_get(); - /* * If sd_count > 0 at this point, one of the subdomains hasn't * managed to call pm_genpd_poweron() for the master yet after @@ -562,21 +593,11 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) * the pm_genpd_poweron() restore power for us (this shouldn't * happen very often). */ - ret = genpd->power_off(genpd); + ret = genpd_power_off(genpd); if (ret == -EBUSY) { genpd_set_active(genpd); goto out; } - - elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); - if (elapsed_ns > genpd->power_off_latency_ns) { - genpd->power_off_latency_ns = elapsed_ns; - genpd->max_off_time_changed = true; - if (genpd->name) - pr_warning("%s: Power-off latency exceeded, " - "new value %lld ns\n", genpd->name, - elapsed_ns); - } } genpd->status = GPD_STATE_POWER_OFF; @@ -755,33 +776,15 @@ static int __init genpd_poweroff_unused(void) } late_initcall(genpd_poweroff_unused); -#else - -static inline int genpd_dev_pm_qos_notifier(struct notifier_block *nb, - unsigned long val, void *ptr) -{ - return NOTIFY_DONE; -} - -static inline void -genpd_queue_power_off_work(struct generic_pm_domain *genpd) {} - -static inline void genpd_power_off_work_fn(struct work_struct *work) {} - -#define pm_genpd_runtime_suspend NULL -#define pm_genpd_runtime_resume NULL - -#endif /* CONFIG_PM_RUNTIME */ - #ifdef CONFIG_PM_SLEEP /** * pm_genpd_present - Check if the given PM domain has been initialized. * @genpd: PM domain to check. */ -static bool pm_genpd_present(struct generic_pm_domain *genpd) +static bool pm_genpd_present(const struct generic_pm_domain *genpd) { - struct generic_pm_domain *gpd; + const struct generic_pm_domain *gpd; if (IS_ERR_OR_NULL(genpd)) return false; @@ -822,8 +825,7 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) || atomic_read(&genpd->sd_count) > 0) return; - if (genpd->power_off) - genpd->power_off(genpd); + genpd_power_off(genpd); genpd->status = GPD_STATE_POWER_OFF; @@ -854,8 +856,7 @@ static void pm_genpd_sync_poweron(struct generic_pm_domain *genpd) genpd_sd_counter_inc(link->master); } - if (genpd->power_on) - genpd->power_on(genpd); + genpd_power_on(genpd); genpd->status = GPD_STATE_ACTIVE; } @@ -1277,8 +1278,7 @@ static int pm_genpd_restore_noirq(struct device *dev) * If the domain was off before the hibernation, make * sure it will be off going forward. */ - if (genpd->power_off) - genpd->power_off(genpd); + genpd_power_off(genpd); return 0; } @@ -1364,7 +1364,7 @@ void pm_genpd_syscore_poweron(struct device *dev) } EXPORT_SYMBOL_GPL(pm_genpd_syscore_poweron); -#else +#else /* !CONFIG_PM_SLEEP */ #define pm_genpd_prepare NULL #define pm_genpd_suspend NULL @@ -1929,6 +1929,12 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->domain.ops.complete = pm_genpd_complete; genpd->dev_ops.save_state = pm_genpd_default_save_state; genpd->dev_ops.restore_state = pm_genpd_default_restore_state; + + if (genpd->flags & GENPD_FLAG_PM_CLK) { + genpd->dev_ops.stop = pm_clk_suspend; + genpd->dev_ops.start = pm_clk_resume; + } + mutex_lock(&gpd_list_lock); list_add(&genpd->gpd_list_node, &gpd_list); mutex_unlock(&gpd_list_lock); @@ -2216,11 +2222,12 @@ int genpd_dev_pm_attach(struct device *dev) } dev->pm_domain->detach = genpd_dev_pm_detach; + pm_genpd_poweron(pd); return 0; } EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); -#endif +#endif /* CONFIG_PM_GENERIC_DOMAINS_OF */ /*** debugfs support ***/ @@ -2236,10 +2243,8 @@ static struct dentry *pm_genpd_debugfs_dir; /* * TODO: This function is a slightly modified version of rtpm_status_show - * from sysfs.c, but dependencies between PM_GENERIC_DOMAINS and PM_RUNTIME - * are too loose to generalize it. + * from sysfs.c, so generalize it. */ -#ifdef CONFIG_PM_RUNTIME static void rtpm_status_str(struct seq_file *s, struct device *dev) { static const char * const status_lookup[] = { @@ -2261,12 +2266,6 @@ static void rtpm_status_str(struct seq_file *s, struct device *dev) seq_puts(s, p); } -#else -static void rtpm_status_str(struct seq_file *s, struct device *dev) -{ - seq_puts(s, "active"); -} -#endif static int pm_genpd_summary_one(struct seq_file *s, struct generic_pm_domain *gpd) diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index d88a62e104d4..2a4154a09e4d 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c @@ -11,8 +11,6 @@ #include <linux/pm_qos.h> #include <linux/hrtimer.h> -#ifdef CONFIG_PM_RUNTIME - static int dev_update_qos_constraint(struct device *dev, void *data) { s64 *constraint_ns_p = data; @@ -227,15 +225,6 @@ static bool always_on_power_down_ok(struct dev_pm_domain *domain) return false; } -#else /* !CONFIG_PM_RUNTIME */ - -static inline bool default_stop_ok(struct device *dev) { return false; } - -#define default_power_down_ok NULL -#define always_on_power_down_ok NULL - -#endif /* !CONFIG_PM_RUNTIME */ - struct dev_power_governor simple_qos_governor = { .stop_ok = default_stop_ok, .power_down_ok = default_power_down_ok, diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 89ced955fafa..2d195f3a1998 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -49,11 +49,12 @@ * are protected by the dev_opp_list_lock for integrity. * IMPORTANT: the opp nodes should be maintained in increasing * order. + * @dynamic: not-created from static DT entries. * @available: true/false - marks if this OPP as available or not * @rate: Frequency in hertz * @u_volt: Nominal voltage in microvolts corresponding to this OPP * @dev_opp: points back to the device_opp struct this opp belongs to - * @head: RCU callback head used for deferred freeing + * @rcu_head: RCU callback head used for deferred freeing * * This structure stores the OPP information for a given device. */ @@ -61,11 +62,12 @@ struct dev_pm_opp { struct list_head node; bool available; + bool dynamic; unsigned long rate; unsigned long u_volt; struct device_opp *dev_opp; - struct rcu_head head; + struct rcu_head rcu_head; }; /** @@ -76,7 +78,8 @@ struct dev_pm_opp { * RCU usage: nodes are not modified in the list of device_opp, * however addition is possible and is secured by dev_opp_list_lock * @dev: device pointer - * @head: notifier head to notify the OPP availability changes. + * @srcu_head: notifier head to notify the OPP availability changes. + * @rcu_head: RCU callback head used for deferred freeing * @opp_list: list of opps * * This is an internal data structure maintaining the link to opps attached to @@ -87,7 +90,8 @@ struct device_opp { struct list_head node; struct device *dev; - struct srcu_notifier_head head; + struct srcu_notifier_head srcu_head; + struct rcu_head rcu_head; struct list_head opp_list; }; @@ -378,30 +382,8 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, } EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor); -/** - * dev_pm_opp_add() - Add an OPP table from a table definitions - * @dev: device for which we do this operation - * @freq: Frequency in Hz for this OPP - * @u_volt: Voltage in uVolts for this OPP - * - * This function adds an opp definition to the opp list and returns status. - * The opp is made available by default and it can be controlled using - * dev_pm_opp_enable/disable functions. - * - * Locking: The internal device_opp and opp structures are RCU protected. - * Hence this function internally uses RCU updater strategy with mutex locks - * to keep the integrity of the internal data structures. Callers should ensure - * that this function is *NOT* called under RCU protection or in contexts where - * mutex cannot be locked. - * - * Return: - * 0: On success OR - * Duplicate OPPs (both freq and volt are same) and opp->available - * -EEXIST: Freq are same and volt are different OR - * Duplicate OPPs (both freq and volt are same) and !opp->available - * -ENOMEM: Memory allocation failure - */ -int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) +static int dev_pm_opp_add_dynamic(struct device *dev, unsigned long freq, + unsigned long u_volt, bool dynamic) { struct device_opp *dev_opp = NULL; struct dev_pm_opp *opp, *new_opp; @@ -417,6 +399,13 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) /* Hold our list modification lock here */ mutex_lock(&dev_opp_list_lock); + /* populate the opp table */ + new_opp->dev_opp = dev_opp; + new_opp->rate = freq; + new_opp->u_volt = u_volt; + new_opp->available = true; + new_opp->dynamic = dynamic; + /* Check for existing list for 'dev' */ dev_opp = find_device_opp(dev); if (IS_ERR(dev_opp)) { @@ -436,19 +425,15 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) } dev_opp->dev = dev; - srcu_init_notifier_head(&dev_opp->head); + srcu_init_notifier_head(&dev_opp->srcu_head); INIT_LIST_HEAD(&dev_opp->opp_list); /* Secure the device list modification */ list_add_rcu(&dev_opp->node, &dev_opp_list); + head = &dev_opp->opp_list; + goto list_add; } - /* populate the opp table */ - new_opp->dev_opp = dev_opp; - new_opp->rate = freq; - new_opp->u_volt = u_volt; - new_opp->available = true; - /* * Insert new OPP in order of increasing frequency * and discard if already present @@ -474,6 +459,7 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) return ret; } +list_add: list_add_rcu(&new_opp->node, head); mutex_unlock(&dev_opp_list_lock); @@ -481,11 +467,109 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) * Notify the changes in the availability of the operable * frequency/voltage list. */ - srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ADD, new_opp); + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADD, new_opp); return 0; } + +/** + * dev_pm_opp_add() - Add an OPP table from a table definitions + * @dev: device for which we do this operation + * @freq: Frequency in Hz for this OPP + * @u_volt: Voltage in uVolts for this OPP + * + * This function adds an opp definition to the opp list and returns status. + * The opp is made available by default and it can be controlled using + * dev_pm_opp_enable/disable functions. + * + * Locking: The internal device_opp and opp structures are RCU protected. + * Hence this function internally uses RCU updater strategy with mutex locks + * to keep the integrity of the internal data structures. Callers should ensure + * that this function is *NOT* called under RCU protection or in contexts where + * mutex cannot be locked. + * + * Return: + * 0: On success OR + * Duplicate OPPs (both freq and volt are same) and opp->available + * -EEXIST: Freq are same and volt are different OR + * Duplicate OPPs (both freq and volt are same) and !opp->available + * -ENOMEM: Memory allocation failure + */ +int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) +{ + return dev_pm_opp_add_dynamic(dev, freq, u_volt, true); +} EXPORT_SYMBOL_GPL(dev_pm_opp_add); +static void kfree_opp_rcu(struct rcu_head *head) +{ + struct dev_pm_opp *opp = container_of(head, struct dev_pm_opp, rcu_head); + + kfree_rcu(opp, rcu_head); +} + +static void kfree_device_rcu(struct rcu_head *head) +{ + struct device_opp *device_opp = container_of(head, struct device_opp, rcu_head); + + kfree(device_opp); +} + +void __dev_pm_opp_remove(struct device_opp *dev_opp, struct dev_pm_opp *opp) +{ + /* + * Notify the changes in the availability of the operable + * frequency/voltage list. + */ + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp); + list_del_rcu(&opp->node); + call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, kfree_opp_rcu); + + if (list_empty(&dev_opp->opp_list)) { + list_del_rcu(&dev_opp->node); + call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head, + kfree_device_rcu); + } +} + +/** + * dev_pm_opp_remove() - Remove an OPP from OPP list + * @dev: device for which we do this operation + * @freq: OPP to remove with matching 'freq' + * + * This function removes an opp from the opp list. + */ +void dev_pm_opp_remove(struct device *dev, unsigned long freq) +{ + struct dev_pm_opp *opp; + struct device_opp *dev_opp; + bool found = false; + + /* Hold our list modification lock here */ + mutex_lock(&dev_opp_list_lock); + + dev_opp = find_device_opp(dev); + if (IS_ERR(dev_opp)) + goto unlock; + + list_for_each_entry(opp, &dev_opp->opp_list, node) { + if (opp->rate == freq) { + found = true; + break; + } + } + + if (!found) { + dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n", + __func__, freq); + goto unlock; + } + + __dev_pm_opp_remove(dev_opp, opp); +unlock: + mutex_unlock(&dev_opp_list_lock); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_remove); + /** * opp_set_availability() - helper to set the availability of an opp * @dev: device for which we do this operation @@ -557,14 +641,14 @@ static int opp_set_availability(struct device *dev, unsigned long freq, list_replace_rcu(&opp->node, &new_opp->node); mutex_unlock(&dev_opp_list_lock); - kfree_rcu(opp, head); + call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, kfree_opp_rcu); /* Notify the change of the OPP availability */ if (availability_req) - srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ENABLE, + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ENABLE, new_opp); else - srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_DISABLE, + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_DISABLE, new_opp); return 0; @@ -629,7 +713,7 @@ struct srcu_notifier_head *dev_pm_opp_get_notifier(struct device *dev) if (IS_ERR(dev_opp)) return ERR_CAST(dev_opp); /* matching type */ - return &dev_opp->head; + return &dev_opp->srcu_head; } #ifdef CONFIG_OF @@ -666,7 +750,7 @@ int of_init_opp_table(struct device *dev) unsigned long freq = be32_to_cpup(val++) * 1000; unsigned long volt = be32_to_cpup(val++); - if (dev_pm_opp_add(dev, freq, volt)) + if (dev_pm_opp_add_dynamic(dev, freq, volt, false)) dev_warn(dev, "%s: Failed to add OPP %ld\n", __func__, freq); nr -= 2; @@ -675,4 +759,34 @@ int of_init_opp_table(struct device *dev) return 0; } EXPORT_SYMBOL_GPL(of_init_opp_table); + +/** + * of_free_opp_table() - Free OPP table entries created from static DT entries + * @dev: device pointer used to lookup device OPPs. + * + * Free OPPs created using static entries present in DT. + */ +void of_free_opp_table(struct device *dev) +{ + struct device_opp *dev_opp = find_device_opp(dev); + struct dev_pm_opp *opp, *tmp; + + /* Check for existing list for 'dev' */ + dev_opp = find_device_opp(dev); + if (WARN(IS_ERR(dev_opp), "%s: dev_opp: %ld\n", dev_name(dev), + PTR_ERR(dev_opp))) + return; + + /* Hold our list modification lock here */ + mutex_lock(&dev_opp_list_lock); + + /* Free static OPPs */ + list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) { + if (!opp->dynamic) + __dev_pm_opp_remove(dev_opp, opp); + } + + mutex_unlock(&dev_opp_list_lock); +} +EXPORT_SYMBOL_GPL(of_free_opp_table); #endif diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index a21223d95926..b6b8a273c5da 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -9,7 +9,7 @@ static inline void device_pm_init_common(struct device *dev) } } -#ifdef CONFIG_PM_RUNTIME +#ifdef CONFIG_PM static inline void pm_runtime_early_init(struct device *dev) { @@ -20,7 +20,21 @@ static inline void pm_runtime_early_init(struct device *dev) extern void pm_runtime_init(struct device *dev); extern void pm_runtime_remove(struct device *dev); -#else /* !CONFIG_PM_RUNTIME */ +/* + * sysfs.c + */ + +extern int dpm_sysfs_add(struct device *dev); +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_resume_latency(struct device *dev); +extern void pm_qos_sysfs_remove_resume_latency(struct device *dev); +extern int pm_qos_sysfs_add_flags(struct device *dev); +extern void pm_qos_sysfs_remove_flags(struct device *dev); + +#else /* CONFIG_PM */ static inline void pm_runtime_early_init(struct device *dev) { @@ -30,7 +44,15 @@ static inline void pm_runtime_early_init(struct device *dev) static inline void pm_runtime_init(struct device *dev) {} static inline void pm_runtime_remove(struct device *dev) {} -#endif /* !CONFIG_PM_RUNTIME */ +static inline int dpm_sysfs_add(struct device *dev) { return 0; } +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 #ifdef CONFIG_PM_SLEEP @@ -77,31 +99,3 @@ static inline void device_pm_init(struct device *dev) device_pm_sleep_init(dev); pm_runtime_init(dev); } - -#ifdef CONFIG_PM - -/* - * sysfs.c - */ - -extern int dpm_sysfs_add(struct device *dev); -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_resume_latency(struct device *dev); -extern void pm_qos_sysfs_remove_resume_latency(struct device *dev); -extern int pm_qos_sysfs_add_flags(struct device *dev); -extern void pm_qos_sysfs_remove_flags(struct device *dev); - -#else /* CONFIG_PM */ - -static inline int dpm_sysfs_add(struct device *dev) { return 0; } -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 36b9eb4862cb..a8fe4c1a8d07 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -599,7 +599,6 @@ int dev_pm_qos_add_ancestor_request(struct device *dev, } EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request); -#ifdef CONFIG_PM_RUNTIME static void __dev_pm_qos_drop_user_request(struct device *dev, enum dev_pm_qos_req_type type) { @@ -880,7 +879,3 @@ int dev_pm_qos_update_user_latency_tolerance(struct device *dev, s32 val) mutex_unlock(&dev_pm_qos_mtx); return ret; } -#else /* !CONFIG_PM_RUNTIME */ -static void __dev_pm_qos_hide_latency_limit(struct device *dev) {} -static void __dev_pm_qos_hide_flags(struct device *dev) {} -#endif /* CONFIG_PM_RUNTIME */ diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 67c7938e430b..5070c4fe8542 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -13,43 +13,38 @@ #include <trace/events/rpm.h> #include "power.h" -#define RPM_GET_CALLBACK(dev, cb) \ -({ \ - int (*__rpm_cb)(struct device *__d); \ - \ - if (dev->pm_domain) \ - __rpm_cb = dev->pm_domain->ops.cb; \ - else if (dev->type && dev->type->pm) \ - __rpm_cb = dev->type->pm->cb; \ - else if (dev->class && dev->class->pm) \ - __rpm_cb = dev->class->pm->cb; \ - else if (dev->bus && dev->bus->pm) \ - __rpm_cb = dev->bus->pm->cb; \ - else \ - __rpm_cb = NULL; \ - \ - if (!__rpm_cb && dev->driver && dev->driver->pm) \ - __rpm_cb = dev->driver->pm->cb; \ - \ - __rpm_cb; \ -}) - -static int (*rpm_get_suspend_cb(struct device *dev))(struct device *) -{ - return RPM_GET_CALLBACK(dev, runtime_suspend); -} +typedef int (*pm_callback_t)(struct device *); -static int (*rpm_get_resume_cb(struct device *dev))(struct device *) +static pm_callback_t __rpm_get_callback(struct device *dev, size_t cb_offset) { - return RPM_GET_CALLBACK(dev, runtime_resume); -} + pm_callback_t cb; + const struct dev_pm_ops *ops; + + if (dev->pm_domain) + ops = &dev->pm_domain->ops; + else if (dev->type && dev->type->pm) + ops = dev->type->pm; + else if (dev->class && dev->class->pm) + ops = dev->class->pm; + else if (dev->bus && dev->bus->pm) + ops = dev->bus->pm; + else + ops = NULL; -#ifdef CONFIG_PM_RUNTIME -static int (*rpm_get_idle_cb(struct device *dev))(struct device *) -{ - return RPM_GET_CALLBACK(dev, runtime_idle); + if (ops) + cb = *(pm_callback_t *)((void *)ops + cb_offset); + else + cb = NULL; + + if (!cb && dev->driver && dev->driver->pm) + cb = *(pm_callback_t *)((void *)dev->driver->pm + cb_offset); + + return cb; } +#define RPM_GET_CALLBACK(dev, callback) \ + __rpm_get_callback(dev, offsetof(struct dev_pm_ops, callback)) + static int rpm_resume(struct device *dev, int rpmflags); static int rpm_suspend(struct device *dev, int rpmflags); @@ -347,7 +342,7 @@ static int rpm_idle(struct device *dev, int rpmflags) dev->power.idle_notification = true; - callback = rpm_get_idle_cb(dev); + callback = RPM_GET_CALLBACK(dev, runtime_idle); if (callback) retval = __rpm_callback(callback, dev); @@ -517,7 +512,7 @@ static int rpm_suspend(struct device *dev, int rpmflags) __update_runtime_status(dev, RPM_SUSPENDING); - callback = rpm_get_suspend_cb(dev); + callback = RPM_GET_CALLBACK(dev, runtime_suspend); retval = rpm_callback(callback, dev); if (retval) @@ -737,7 +732,7 @@ static int rpm_resume(struct device *dev, int rpmflags) __update_runtime_status(dev, RPM_RESUMING); - callback = rpm_get_resume_cb(dev); + callback = RPM_GET_CALLBACK(dev, runtime_resume); retval = rpm_callback(callback, dev); if (retval) { @@ -1402,7 +1397,6 @@ void pm_runtime_remove(struct device *dev) if (dev->power.irq_safe && dev->parent) pm_runtime_put(dev->parent); } -#endif /** * pm_runtime_force_suspend - Force a device into suspend state if needed. @@ -1422,16 +1416,10 @@ int pm_runtime_force_suspend(struct device *dev) int ret = 0; pm_runtime_disable(dev); - - /* - * Note that pm_runtime_status_suspended() returns false while - * !CONFIG_PM_RUNTIME, which means the device will be put into low - * power state. - */ if (pm_runtime_status_suspended(dev)) return 0; - callback = rpm_get_suspend_cb(dev); + callback = RPM_GET_CALLBACK(dev, runtime_suspend); if (!callback) { ret = -ENOSYS; @@ -1467,7 +1455,7 @@ int pm_runtime_force_resume(struct device *dev) int (*callback)(struct device *); int ret = 0; - callback = rpm_get_resume_cb(dev); + callback = RPM_GET_CALLBACK(dev, runtime_resume); if (!callback) { ret = -ENOSYS; diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index a9d26ed11bf4..d2be3f9c211c 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -95,7 +95,6 @@ const char power_group_name[] = "power"; EXPORT_SYMBOL_GPL(power_group_name); -#ifdef CONFIG_PM_RUNTIME static const char ctrl_auto[] = "auto"; static const char ctrl_on[] = "on"; @@ -330,7 +329,6 @@ static ssize_t pm_qos_remote_wakeup_store(struct device *dev, static DEVICE_ATTR(pm_qos_remote_wakeup, 0644, pm_qos_remote_wakeup_show, pm_qos_remote_wakeup_store); -#endif /* CONFIG_PM_RUNTIME */ #ifdef CONFIG_PM_SLEEP static const char _enabled[] = "enabled"; @@ -531,8 +529,6 @@ static DEVICE_ATTR(wakeup_prevent_sleep_time_ms, 0444, #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM_ADVANCED_DEBUG -#ifdef CONFIG_PM_RUNTIME - static ssize_t rtpm_usagecount_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -562,10 +558,7 @@ static DEVICE_ATTR(runtime_usage, 0444, rtpm_usagecount_show, NULL); static DEVICE_ATTR(runtime_active_kids, 0444, rtpm_children_show, NULL); static DEVICE_ATTR(runtime_enabled, 0444, rtpm_enabled_show, NULL); -#endif - #ifdef CONFIG_PM_SLEEP - static ssize_t async_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -595,7 +588,7 @@ static ssize_t async_store(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(async, 0644, async_show, async_store); -#endif +#endif /* CONFIG_PM_SLEEP */ #endif /* CONFIG_PM_ADVANCED_DEBUG */ static struct attribute *power_attrs[] = { @@ -603,12 +596,10 @@ static struct attribute *power_attrs[] = { #ifdef CONFIG_PM_SLEEP &dev_attr_async.attr, #endif -#ifdef CONFIG_PM_RUNTIME &dev_attr_runtime_status.attr, &dev_attr_runtime_usage.attr, &dev_attr_runtime_active_kids.attr, &dev_attr_runtime_enabled.attr, -#endif #endif /* CONFIG_PM_ADVANCED_DEBUG */ NULL, }; @@ -640,7 +631,6 @@ static struct attribute_group pm_wakeup_attr_group = { }; static struct attribute *runtime_attrs[] = { -#ifdef CONFIG_PM_RUNTIME #ifndef CONFIG_PM_ADVANCED_DEBUG &dev_attr_runtime_status.attr, #endif @@ -648,7 +638,6 @@ static struct attribute *runtime_attrs[] = { &dev_attr_runtime_suspended_time.attr, &dev_attr_runtime_active_time.attr, &dev_attr_autosuspend_delay_ms.attr, -#endif /* CONFIG_PM_RUNTIME */ NULL, }; static struct attribute_group pm_runtime_attr_group = { @@ -657,9 +646,7 @@ static struct attribute_group pm_runtime_attr_group = { }; static struct attribute *pm_qos_resume_latency_attrs[] = { -#ifdef CONFIG_PM_RUNTIME &dev_attr_pm_qos_resume_latency_us.attr, -#endif /* CONFIG_PM_RUNTIME */ NULL, }; static struct attribute_group pm_qos_resume_latency_attr_group = { @@ -668,9 +655,7 @@ static struct attribute_group pm_qos_resume_latency_attr_group = { }; static struct attribute *pm_qos_latency_tolerance_attrs[] = { -#ifdef CONFIG_PM_RUNTIME &dev_attr_pm_qos_latency_tolerance_us.attr, -#endif /* CONFIG_PM_RUNTIME */ NULL, }; static struct attribute_group pm_qos_latency_tolerance_attr_group = { @@ -679,10 +664,8 @@ static struct attribute_group pm_qos_latency_tolerance_attr_group = { }; static struct attribute *pm_qos_flags_attrs[] = { -#ifdef CONFIG_PM_RUNTIME &dev_attr_pm_qos_no_power_off.attr, &dev_attr_pm_qos_remote_wakeup.attr, -#endif /* CONFIG_PM_RUNTIME */ NULL, }; static struct attribute_group pm_qos_flags_attr_group = { diff --git a/drivers/base/property.c b/drivers/base/property.c new file mode 100644 index 000000000000..c45845874d4f --- /dev/null +++ b/drivers/base/property.c @@ -0,0 +1,431 @@ +/* + * property.c - Unified device property interface. + * + * Copyright (C) 2014, Intel Corporation + * Authors: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/property.h> +#include <linux/export.h> +#include <linux/acpi.h> +#include <linux/of.h> + +/** + * device_property_present - check if a property of a device is present + * @dev: Device whose property is being checked + * @propname: Name of the property + * + * Check if property @propname is present in the device firmware description. + */ +bool device_property_present(struct device *dev, const char *propname) +{ + if (IS_ENABLED(CONFIG_OF) && dev->of_node) + return of_property_read_bool(dev->of_node, propname); + + return !acpi_dev_prop_get(ACPI_COMPANION(dev), propname, NULL); +} +EXPORT_SYMBOL_GPL(device_property_present); + +/** + * fwnode_property_present - check if a property of a firmware node is present + * @fwnode: Firmware node whose property to check + * @propname: Name of the property + */ +bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname) +{ + if (is_of_node(fwnode)) + return of_property_read_bool(of_node(fwnode), propname); + else if (is_acpi_node(fwnode)) + return !acpi_dev_prop_get(acpi_node(fwnode), propname, NULL); + + return false; +} +EXPORT_SYMBOL_GPL(fwnode_property_present); + +#define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval) \ + (val) ? of_property_read_##type##_array((node), (propname), (val), (nval)) \ + : of_property_count_elems_of_size((node), (propname), sizeof(type)) + +#define DEV_PROP_READ_ARRAY(_dev_, _propname_, _type_, _proptype_, _val_, _nval_) \ + IS_ENABLED(CONFIG_OF) && _dev_->of_node ? \ + (OF_DEV_PROP_READ_ARRAY(_dev_->of_node, _propname_, _type_, \ + _val_, _nval_)) : \ + acpi_dev_prop_read(ACPI_COMPANION(_dev_), _propname_, \ + _proptype_, _val_, _nval_) + +/** + * device_property_read_u8_array - return a u8 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u8 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u8_array(struct device *dev, const char *propname, + u8 *val, size_t nval) +{ + return DEV_PROP_READ_ARRAY(dev, propname, u8, DEV_PROP_U8, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u8_array); + +/** + * device_property_read_u16_array - return a u16 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u16 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u16_array(struct device *dev, const char *propname, + u16 *val, size_t nval) +{ + return DEV_PROP_READ_ARRAY(dev, propname, u16, DEV_PROP_U16, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u16_array); + +/** + * device_property_read_u32_array - return a u32 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u32 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u32_array(struct device *dev, const char *propname, + u32 *val, size_t nval) +{ + return DEV_PROP_READ_ARRAY(dev, propname, u32, DEV_PROP_U32, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u32_array); + +/** + * device_property_read_u64_array - return a u64 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u64 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u64_array(struct device *dev, const char *propname, + u64 *val, size_t nval) +{ + return DEV_PROP_READ_ARRAY(dev, propname, u64, DEV_PROP_U64, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u64_array); + +/** + * device_property_read_string_array - return a string array property of device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of string properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property is not an array of strings, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_string_array(struct device *dev, const char *propname, + const char **val, size_t nval) +{ + return IS_ENABLED(CONFIG_OF) && dev->of_node ? + of_property_read_string_array(dev->of_node, propname, val, nval) : + acpi_dev_prop_read(ACPI_COMPANION(dev), propname, + DEV_PROP_STRING, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_string_array); + +/** + * device_property_read_string - return a string property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be a string. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not a string. + */ +int device_property_read_string(struct device *dev, const char *propname, + const char **val) +{ + return IS_ENABLED(CONFIG_OF) && dev->of_node ? + of_property_read_string(dev->of_node, propname, val) : + acpi_dev_prop_read(ACPI_COMPANION(dev), propname, + DEV_PROP_STRING, val, 1); +} +EXPORT_SYMBOL_GPL(device_property_read_string); + +#define FWNODE_PROP_READ_ARRAY(_fwnode_, _propname_, _type_, _proptype_, _val_, _nval_) \ +({ \ + int _ret_; \ + if (is_of_node(_fwnode_)) \ + _ret_ = OF_DEV_PROP_READ_ARRAY(of_node(_fwnode_), _propname_, \ + _type_, _val_, _nval_); \ + else if (is_acpi_node(_fwnode_)) \ + _ret_ = acpi_dev_prop_read(acpi_node(_fwnode_), _propname_, \ + _proptype_, _val_, _nval_); \ + else \ + _ret_ = -ENXIO; \ + _ret_; \ +}) + +/** + * fwnode_property_read_u8_array - return a u8 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u8 properties with @propname from @fwnode and stores them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u8_array(struct fwnode_handle *fwnode, + const char *propname, u8 *val, size_t nval) +{ + return FWNODE_PROP_READ_ARRAY(fwnode, propname, u8, DEV_PROP_U8, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u8_array); + +/** + * fwnode_property_read_u16_array - return a u16 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u16 properties with @propname from @fwnode and store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u16_array(struct fwnode_handle *fwnode, + const char *propname, u16 *val, size_t nval) +{ + return FWNODE_PROP_READ_ARRAY(fwnode, propname, u16, DEV_PROP_U16, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u16_array); + +/** + * fwnode_property_read_u32_array - return a u32 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u32 properties with @propname from @fwnode store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u32_array(struct fwnode_handle *fwnode, + const char *propname, u32 *val, size_t nval) +{ + return FWNODE_PROP_READ_ARRAY(fwnode, propname, u32, DEV_PROP_U32, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u32_array); + +/** + * fwnode_property_read_u64_array - return a u64 array property firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u64 properties with @propname from @fwnode and store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u64_array(struct fwnode_handle *fwnode, + const char *propname, u64 *val, size_t nval) +{ + return FWNODE_PROP_READ_ARRAY(fwnode, propname, u64, DEV_PROP_U64, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u64_array); + +/** + * fwnode_property_read_string_array - return string array property of a node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an string list property @propname from the given firmware node and store + * them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of strings, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, const char **val, + size_t nval) +{ + if (is_of_node(fwnode)) + return of_property_read_string_array(of_node(fwnode), propname, + val, nval); + else if (is_acpi_node(fwnode)) + return acpi_dev_prop_read(acpi_node(fwnode), propname, + DEV_PROP_STRING, val, nval); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(fwnode_property_read_string_array); + +/** + * fwnode_property_read_string - return a string property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be a string. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property is not a string, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_string(struct fwnode_handle *fwnode, + const char *propname, const char **val) +{ + if (is_of_node(fwnode)) + return of_property_read_string(of_node(fwnode),propname, val); + else if (is_acpi_node(fwnode)) + return acpi_dev_prop_read(acpi_node(fwnode), propname, + DEV_PROP_STRING, val, 1); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(fwnode_property_read_string); + +/** + * device_get_next_child_node - Return the next child node handle for a device + * @dev: Device to find the next child node for. + * @child: Handle to one of the device's child nodes or a null handle. + */ +struct fwnode_handle *device_get_next_child_node(struct device *dev, + struct fwnode_handle *child) +{ + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + struct device_node *node; + + node = of_get_next_available_child(dev->of_node, of_node(child)); + if (node) + return &node->fwnode; + } else if (IS_ENABLED(CONFIG_ACPI)) { + struct acpi_device *node; + + node = acpi_get_next_child(dev, acpi_node(child)); + if (node) + return acpi_fwnode_handle(node); + } + return NULL; +} +EXPORT_SYMBOL_GPL(device_get_next_child_node); + +/** + * fwnode_handle_put - Drop reference to a device node + * @fwnode: Pointer to the device node to drop the reference to. + * + * This has to be used when terminating device_for_each_child_node() iteration + * with break or return to prevent stale device node references from being left + * behind. + */ +void fwnode_handle_put(struct fwnode_handle *fwnode) +{ + if (is_of_node(fwnode)) + of_node_put(of_node(fwnode)); +} +EXPORT_SYMBOL_GPL(fwnode_handle_put); + +/** + * device_get_child_node_count - return the number of child nodes for device + * @dev: Device to cound the child nodes for + */ +unsigned int device_get_child_node_count(struct device *dev) +{ + struct fwnode_handle *child; + unsigned int count = 0; + + device_for_each_child_node(dev, child) + count++; + + return count; +} +EXPORT_SYMBOL_GPL(device_get_child_node_count); diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 8a3f51f7b1b9..db9d00c36a3e 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -3,12 +3,15 @@ # subsystems should select the appropriate symbols. config REGMAP - default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_MMIO || REGMAP_IRQ) + default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ) select LZO_COMPRESS select LZO_DECOMPRESS select IRQ_DOMAIN if REGMAP_IRQ bool +config REGMAP_AC97 + tristate + config REGMAP_I2C tristate depends on I2C diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index a7c670b4123a..0a533653ef3b 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_REGMAP) += regmap.o regcache.o obj-$(CONFIG_REGMAP) += regcache-rbtree.o regcache-lzo.o regcache-flat.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o +obj-$(CONFIG_REGMAP_AC97) += regmap-ac97.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o diff --git a/drivers/base/regmap/regcache-flat.c b/drivers/base/regmap/regcache-flat.c index d9762e41959b..0246f44ded74 100644 --- a/drivers/base/regmap/regcache-flat.c +++ b/drivers/base/regmap/regcache-flat.c @@ -10,9 +10,9 @@ * published by the Free Software Foundation. */ -#include <linux/slab.h> #include <linux/device.h> #include <linux/seq_file.h> +#include <linux/slab.h> #include "internal.h" diff --git a/drivers/base/regmap/regcache-lzo.c b/drivers/base/regmap/regcache-lzo.c index e210a6d1406a..2d53f6f138e1 100644 --- a/drivers/base/regmap/regcache-lzo.c +++ b/drivers/base/regmap/regcache-lzo.c @@ -10,9 +10,9 @@ * published by the Free Software Foundation. */ -#include <linux/slab.h> #include <linux/device.h> #include <linux/lzo.h> +#include <linux/slab.h> #include "internal.h" diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index f3e8fe0cc650..d453a2c98ad0 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -10,11 +10,11 @@ * published by the Free Software Foundation. */ -#include <linux/slab.h> -#include <linux/device.h> #include <linux/debugfs.h> +#include <linux/device.h> #include <linux/rbtree.h> #include <linux/seq_file.h> +#include <linux/slab.h> #include "internal.h" diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index f1280dc356d0..f373c35f9e1d 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -10,12 +10,12 @@ * published by the Free Software Foundation. */ -#include <linux/slab.h> -#include <linux/export.h> -#include <linux/device.h> -#include <trace/events/regmap.h> #include <linux/bsearch.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/slab.h> #include <linux/sort.h> +#include <trace/events/regmap.h> #include "internal.h" @@ -36,6 +36,23 @@ static int regcache_hw_init(struct regmap *map) if (!map->num_reg_defaults_raw) return -EINVAL; + /* calculate the size of reg_defaults */ + for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) + if (!regmap_volatile(map, i * map->reg_stride)) + count++; + + /* all registers are volatile, so just bypass */ + if (!count) { + map->cache_bypass = true; + return 0; + } + + map->num_reg_defaults = count; + map->reg_defaults = kmalloc_array(count, sizeof(struct reg_default), + GFP_KERNEL); + if (!map->reg_defaults) + return -ENOMEM; + if (!map->reg_defaults_raw) { u32 cache_bypass = map->cache_bypass; dev_warn(map->dev, "No cache defaults, reading back from HW\n"); @@ -43,40 +60,25 @@ static int regcache_hw_init(struct regmap *map) /* Bypass the cache access till data read from HW*/ map->cache_bypass = 1; tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL); - if (!tmp_buf) - return -EINVAL; + if (!tmp_buf) { + ret = -ENOMEM; + goto err_free; + } ret = regmap_raw_read(map, 0, tmp_buf, map->num_reg_defaults_raw); map->cache_bypass = cache_bypass; - if (ret < 0) { - kfree(tmp_buf); - return ret; - } + if (ret < 0) + goto err_cache_free; + map->reg_defaults_raw = tmp_buf; map->cache_free = 1; } - /* calculate the size of reg_defaults */ - for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) { - val = regcache_get_val(map, map->reg_defaults_raw, i); - if (regmap_volatile(map, i * map->reg_stride)) - continue; - count++; - } - - map->reg_defaults = kmalloc(count * sizeof(struct reg_default), - GFP_KERNEL); - if (!map->reg_defaults) { - ret = -ENOMEM; - goto err_free; - } - /* fill the reg_defaults */ - map->num_reg_defaults = count; for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { - val = regcache_get_val(map, map->reg_defaults_raw, i); if (regmap_volatile(map, i * map->reg_stride)) continue; + val = regcache_get_val(map, map->reg_defaults_raw, i); map->reg_defaults[j].reg = i * map->reg_stride; map->reg_defaults[j].def = val; j++; @@ -84,9 +86,10 @@ static int regcache_hw_init(struct regmap *map) return 0; +err_cache_free: + kfree(tmp_buf); err_free: - if (map->cache_free) - kfree(map->reg_defaults_raw); + kfree(map->reg_defaults); return ret; } @@ -150,6 +153,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = regcache_hw_init(map); if (ret < 0) return ret; + if (map->cache_bypass) + return 0; } if (!map->max_register) diff --git a/drivers/base/regmap/regmap-ac97.c b/drivers/base/regmap/regmap-ac97.c new file mode 100644 index 000000000000..e4c45d2299c1 --- /dev/null +++ b/drivers/base/regmap/regmap-ac97.c @@ -0,0 +1,114 @@ +/* + * Register map access API - AC'97 support + * + * Copyright 2013 Linaro Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <sound/ac97_codec.h> + +bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_RESET: + case AC97_POWERDOWN: + case AC97_INT_PAGING: + case AC97_EXTENDED_ID: + case AC97_EXTENDED_STATUS: + case AC97_EXTENDED_MID: + case AC97_EXTENDED_MSTATUS: + case AC97_GPIO_STATUS: + case AC97_MISC_AFE: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + case AC97_CODEC_CLASS_REV: + case AC97_PCI_SVID: + case AC97_PCI_SID: + case AC97_FUNC_SELECT: + case AC97_FUNC_INFO: + case AC97_SENSE_INFO: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(regmap_ac97_default_volatile); + +static int regmap_ac97_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct snd_ac97 *ac97 = context; + + *val = ac97->bus->ops->read(ac97, reg); + + return 0; +} + +static int regmap_ac97_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct snd_ac97 *ac97 = context; + + ac97->bus->ops->write(ac97, reg, val); + + return 0; +} + +static const struct regmap_bus ac97_regmap_bus = { + .reg_write = regmap_ac97_reg_write, + .reg_read = regmap_ac97_reg_read, +}; + +/** + * regmap_init_ac97(): Initialise AC'97 register map + * + * @ac97: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +struct regmap *regmap_init_ac97(struct snd_ac97 *ac97, + const struct regmap_config *config) +{ + return regmap_init(&ac97->dev, &ac97_regmap_bus, ac97, config); +} +EXPORT_SYMBOL_GPL(regmap_init_ac97); + +/** + * devm_regmap_init_ac97(): Initialise AC'97 register map + * + * @ac97: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct regmap. The regmap will be automatically freed by the + * device management code. + */ +struct regmap *devm_regmap_init_ac97(struct snd_ac97 *ac97, + const struct regmap_config *config) +{ + return devm_regmap_init(&ac97->dev, &ac97_regmap_bus, ac97, config); +} +EXPORT_SYMBOL_GPL(devm_regmap_init_ac97); + +MODULE_LICENSE("GPL v2"); |