summaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/Makefile2
-rw-r--r--drivers/base/power/clock_ops.c150
-rw-r--r--drivers/base/power/domain.c147
-rw-r--r--drivers/base/power/domain_governor.c11
-rw-r--r--drivers/base/power/opp.c196
-rw-r--r--drivers/base/power/power.h56
-rw-r--r--drivers/base/power/qos.c5
-rw-r--r--drivers/base/power/runtime.c76
-rw-r--r--drivers/base/power/sysfs.c19
-rw-r--r--drivers/base/property.c431
10 files changed, 771 insertions, 322 deletions
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 6922cd6850a..53c3fe1aeb2 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/power/clock_ops.c b/drivers/base/power/clock_ops.c
index 78369305e06..d626576a4f7 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 fb83d4acd40..6a103a35ea9 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 d88a62e104d..2a4154a09e4 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 89ced955faf..2d195f3a199 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 a21223d9592..b6b8a273c5d 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 36b9eb4862c..a8fe4c1a8d0 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 67c7938e430..5070c4fe854 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 a9d26ed11bf..d2be3f9c211 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 00000000000..c45845874d4
--- /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);