Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b5d5fad9 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge branch 'pm-opp'

* pm-opp:
  PM / OPP: Rename structures for clarity
  PM / OPP: Fix incorrect comments
  PM / OPP: Initialize regulator pointer to an error value
  PM / OPP: Initialize u_volt_min/max to a valid value
  PM / OPP: Fix NULL pointer dereference crash when disabling OPPs
  PM / OPP: Add dev_pm_opp_set_rate()
  PM / OPP: Manage device clk
  PM / OPP: Parse clock-latency and voltage-tolerance for v1 bindings
  PM / OPP: Introduce dev_pm_opp_get_max_transition_latency()
  PM / OPP: Introduce dev_pm_opp_get_max_volt_latency()
  PM / OPP: Disable OPPs that aren't supported by the regulator
  PM / OPP: get/put regulators from OPP core
parents 07cc77e0 2c2709dc
Loading
Loading
Loading
Loading
+757 −322

File changed.

Preview size limit exceeded, changes collapsed.

+11 −11
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@
 * @table:	Cpufreq table returned back to caller
 *
 * Generate a cpufreq table for a provided device- this assumes that the
 * opp list is already initialized and ready for usage.
 * opp table is already initialized and ready for usage.
 *
 * This function allocates required memory for the cpufreq table. It is
 * expected that the caller does the required maintenance such as freeing
@@ -44,7 +44,7 @@
 * WARNING: It is  important for the callers to ensure refreshing their copy of
 * the table if any of the mentioned functions have been invoked in the interim.
 *
 * Locking: The internal device_opp and opp structures are RCU protected.
 * Locking: The internal opp_table and opp structures are RCU protected.
 * Since we just use the regular accessor functions to access the internal data
 * structures, we use RCU read lock inside this function. As a result, users of
 * this function DONOT need to use explicit locks for invoking.
@@ -122,15 +122,15 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_free_cpufreq_table);
/* Required only for V1 bindings, as v2 can manage it from DT itself */
int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, cpumask_var_t cpumask)
{
	struct device_list_opp *list_dev;
	struct device_opp *dev_opp;
	struct opp_device *opp_dev;
	struct opp_table *opp_table;
	struct device *dev;
	int cpu, ret = 0;

	mutex_lock(&dev_opp_list_lock);
	mutex_lock(&opp_table_lock);

	dev_opp = _find_device_opp(cpu_dev);
	if (IS_ERR(dev_opp)) {
	opp_table = _find_opp_table(cpu_dev);
	if (IS_ERR(opp_table)) {
		ret = -EINVAL;
		goto unlock;
	}
@@ -146,15 +146,15 @@ int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, cpumask_var_t cpumask)
			continue;
		}

		list_dev = _add_list_dev(dev, dev_opp);
		if (!list_dev) {
			dev_err(dev, "%s: failed to add list-dev for cpu%d device\n",
		opp_dev = _add_opp_dev(dev, opp_table);
		if (!opp_dev) {
			dev_err(dev, "%s: failed to add opp-dev for cpu%d device\n",
				__func__, cpu);
			continue;
		}
	}
unlock:
	mutex_unlock(&dev_opp_list_lock);
	mutex_unlock(&opp_table_lock);

	return ret;
}
+42 −43
Original line number Diff line number Diff line
@@ -34,9 +34,9 @@ void opp_debug_remove_one(struct dev_pm_opp *opp)
	debugfs_remove_recursive(opp->dentry);
}

int opp_debug_create_one(struct dev_pm_opp *opp, struct device_opp *dev_opp)
int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
{
	struct dentry *pdentry = dev_opp->dentry;
	struct dentry *pdentry = opp_table->dentry;
	struct dentry *d;
	char name[25];	/* 20 chars for 64 bit value + 5 (opp:\0) */

@@ -83,52 +83,52 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct device_opp *dev_opp)
	return 0;
}

static int device_opp_debug_create_dir(struct device_list_opp *list_dev,
				       struct device_opp *dev_opp)
static int opp_list_debug_create_dir(struct opp_device *opp_dev,
				     struct opp_table *opp_table)
{
	const struct device *dev = list_dev->dev;
	const struct device *dev = opp_dev->dev;
	struct dentry *d;

	opp_set_dev_name(dev, dev_opp->dentry_name);
	opp_set_dev_name(dev, opp_table->dentry_name);

	/* Create device specific directory */
	d = debugfs_create_dir(dev_opp->dentry_name, rootdir);
	d = debugfs_create_dir(opp_table->dentry_name, rootdir);
	if (!d) {
		dev_err(dev, "%s: Failed to create debugfs dir\n", __func__);
		return -ENOMEM;
	}

	list_dev->dentry = d;
	dev_opp->dentry = d;
	opp_dev->dentry = d;
	opp_table->dentry = d;

	return 0;
}

static int device_opp_debug_create_link(struct device_list_opp *list_dev,
					struct device_opp *dev_opp)
static int opp_list_debug_create_link(struct opp_device *opp_dev,
				      struct opp_table *opp_table)
{
	const struct device *dev = list_dev->dev;
	const struct device *dev = opp_dev->dev;
	char name[NAME_MAX];
	struct dentry *d;

	opp_set_dev_name(list_dev->dev, name);
	opp_set_dev_name(opp_dev->dev, name);

	/* Create device specific directory link */
	d = debugfs_create_symlink(name, rootdir, dev_opp->dentry_name);
	d = debugfs_create_symlink(name, rootdir, opp_table->dentry_name);
	if (!d) {
		dev_err(dev, "%s: Failed to create link\n", __func__);
		return -ENOMEM;
	}

	list_dev->dentry = d;
	opp_dev->dentry = d;

	return 0;
}

/**
 * opp_debug_register - add a device opp node to the debugfs 'opp' directory
 * @list_dev: list-dev pointer for device
 * @dev_opp: the device-opp being added
 * @opp_dev: opp-dev pointer for device
 * @opp_table: the device-opp being added
 *
 * Dynamically adds device specific directory in debugfs 'opp' directory. If the
 * device-opp is shared with other devices, then links will be created for all
@@ -136,73 +136,72 @@ static int device_opp_debug_create_link(struct device_list_opp *list_dev,
 *
 * Return: 0 on success, otherwise negative error.
 */
int opp_debug_register(struct device_list_opp *list_dev,
		       struct device_opp *dev_opp)
int opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table)
{
	if (!rootdir) {
		pr_debug("%s: Uninitialized rootdir\n", __func__);
		return -EINVAL;
	}

	if (dev_opp->dentry)
		return device_opp_debug_create_link(list_dev, dev_opp);
	if (opp_table->dentry)
		return opp_list_debug_create_link(opp_dev, opp_table);

	return device_opp_debug_create_dir(list_dev, dev_opp);
	return opp_list_debug_create_dir(opp_dev, opp_table);
}

static void opp_migrate_dentry(struct device_list_opp *list_dev,
			       struct device_opp *dev_opp)
static void opp_migrate_dentry(struct opp_device *opp_dev,
			       struct opp_table *opp_table)
{
	struct device_list_opp *new_dev;
	struct opp_device *new_dev;
	const struct device *dev;
	struct dentry *dentry;

	/* Look for next list-dev */
	list_for_each_entry(new_dev, &dev_opp->dev_list, node)
		if (new_dev != list_dev)
	/* Look for next opp-dev */
	list_for_each_entry(new_dev, &opp_table->dev_list, node)
		if (new_dev != opp_dev)
			break;

	/* new_dev is guaranteed to be valid here */
	dev = new_dev->dev;
	debugfs_remove_recursive(new_dev->dentry);

	opp_set_dev_name(dev, dev_opp->dentry_name);
	opp_set_dev_name(dev, opp_table->dentry_name);

	dentry = debugfs_rename(rootdir, list_dev->dentry, rootdir,
				dev_opp->dentry_name);
	dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir,
				opp_table->dentry_name);
	if (!dentry) {
		dev_err(dev, "%s: Failed to rename link from: %s to %s\n",
			__func__, dev_name(list_dev->dev), dev_name(dev));
			__func__, dev_name(opp_dev->dev), dev_name(dev));
		return;
	}

	new_dev->dentry = dentry;
	dev_opp->dentry = dentry;
	opp_table->dentry = dentry;
}

/**
 * opp_debug_unregister - remove a device opp node from debugfs opp directory
 * @list_dev: list-dev pointer for device
 * @dev_opp: the device-opp being removed
 * @opp_dev: opp-dev pointer for device
 * @opp_table: the device-opp being removed
 *
 * Dynamically removes device specific directory from debugfs 'opp' directory.
 */
void opp_debug_unregister(struct device_list_opp *list_dev,
			  struct device_opp *dev_opp)
void opp_debug_unregister(struct opp_device *opp_dev,
			  struct opp_table *opp_table)
{
	if (list_dev->dentry == dev_opp->dentry) {
	if (opp_dev->dentry == opp_table->dentry) {
		/* Move the real dentry object under another device */
		if (!list_is_singular(&dev_opp->dev_list)) {
			opp_migrate_dentry(list_dev, dev_opp);
		if (!list_is_singular(&opp_table->dev_list)) {
			opp_migrate_dentry(opp_dev, opp_table);
			goto out;
		}
		dev_opp->dentry = NULL;
		opp_table->dentry = NULL;
	}

	debugfs_remove_recursive(list_dev->dentry);
	debugfs_remove_recursive(opp_dev->dentry);

out:
	list_dev->dentry = NULL;
	opp_dev->dentry = NULL;
}

static int __init opp_debug_init(void)
+42 −32
Original line number Diff line number Diff line
@@ -22,13 +22,16 @@
#include <linux/rculist.h>
#include <linux/rcupdate.h>

struct clk;
struct regulator;

/* Lock to allow exclusive modification to the device and opp lists */
extern struct mutex dev_opp_list_lock;
extern struct mutex opp_table_lock;

/*
 * Internal data structure organization with the OPP layer library is as
 * follows:
 * dev_opp_list (root)
 * opp_tables (root)
 *	|- device 1 (represents voltage domain 1)
 *	|	|- opp 1 (availability, freq, voltage)
 *	|	|- opp 2 ..
@@ -37,18 +40,18 @@ extern struct mutex dev_opp_list_lock;
 *	|- device 2 (represents the next voltage domain)
 *	...
 *	`- device m (represents mth voltage domain)
 * device 1, 2.. are represented by dev_opp structure while each opp
 * device 1, 2.. are represented by opp_table structure while each opp
 * is represented by the opp structure.
 */

/**
 * struct dev_pm_opp - Generic OPP description structure
 * @node:	opp list node. The nodes are maintained throughout the lifetime
 * @node:	opp table node. The nodes are maintained throughout the lifetime
 *		of boot. It is expected only an optimal set of OPPs are
 *		added to the library by the SoC framework.
 *		RCU usage: opp list is traversed with RCU locks. node
 *		RCU usage: opp table is traversed with RCU locks. node
 *		modification is possible realtime, hence the modifications
 *		are protected by the dev_opp_list_lock for integrity.
 *		are protected by the opp_table_lock for integrity.
 *		IMPORTANT: the opp nodes should be maintained in increasing
 *		order.
 * @available:	true/false - marks if this OPP as available or not
@@ -62,7 +65,7 @@ extern struct mutex dev_opp_list_lock;
 * @u_amp:	Maximum current drawn by the device in microamperes
 * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
 *		frequency from any other OPP's frequency.
 * @dev_opp:	points back to the device_opp struct this opp belongs to
 * @opp_table:	points back to the opp_table struct this opp belongs to
 * @rcu_head:	RCU callback head used for deferred freeing
 * @np:		OPP's device node.
 * @dentry:	debugfs dentry pointer (per opp)
@@ -84,7 +87,7 @@ struct dev_pm_opp {
	unsigned long u_amp;
	unsigned long clock_latency_ns;

	struct device_opp *dev_opp;
	struct opp_table *opp_table;
	struct rcu_head rcu_head;

	struct device_node *np;
@@ -95,16 +98,16 @@ struct dev_pm_opp {
};

/**
 * struct device_list_opp - devices managed by 'struct device_opp'
 * struct opp_device - devices managed by 'struct opp_table'
 * @node:	list node
 * @dev:	device to which the struct object belongs
 * @rcu_head:	RCU callback head used for deferred freeing
 * @dentry:	debugfs dentry pointer (per device)
 *
 * This is an internal data structure maintaining the list of devices that are
 * managed by 'struct device_opp'.
 * This is an internal data structure maintaining the devices that are managed
 * by 'struct opp_table'.
 */
struct device_list_opp {
struct opp_device {
	struct list_head node;
	const struct device *dev;
	struct rcu_head rcu_head;
@@ -115,16 +118,16 @@ struct device_list_opp {
};

/**
 * struct device_opp - Device opp structure
 * @node:	list node - contains the devices with OPPs that
 * struct opp_table - Device opp structure
 * @node:	table node - contains the devices with OPPs that
 *		have been registered. Nodes once added are not modified in this
 *		list.
 *		RCU usage: nodes are not modified in the list of device_opp,
 *		however addition is possible and is secured by dev_opp_list_lock
 *		table.
 *		RCU usage: nodes are not modified in the table of opp_table,
 *		however addition is possible and is secured by opp_table_lock
 * @srcu_head:	notifier head to notify the OPP availability changes.
 * @rcu_head:	RCU callback head used for deferred freeing
 * @dev_list:	list of devices that share these OPPs
 * @opp_list:	list of opps
 * @opp_list:	table of opps
 * @np:		struct device_node pointer for opp's DT node.
 * @clock_latency_ns_max: Max clock latency in nanoseconds.
 * @shared_opp: OPP is shared between multiple devices.
@@ -132,9 +135,13 @@ struct device_list_opp {
 * @supported_hw: Array of version number to support.
 * @supported_hw_count: Number of elements in supported_hw array.
 * @prop_name: A name to postfix to many DT properties, while parsing them.
 * @clk: Device's clock handle
 * @regulator: Supply regulator
 * @dentry:	debugfs dentry pointer of the real device directory (not links).
 * @dentry_name: Name of the real dentry.
 *
 * @voltage_tolerance_v1: In percentage, for v1 bindings only.
 *
 * This is an internal data structure maintaining the link to opps attached to
 * a device. This structure is not meant to be shared to users as it is
 * meant for book keeping and private to OPP library.
@@ -143,7 +150,7 @@ struct device_list_opp {
 * need to wait for the grace period of both of them before freeing any
 * resources. And so we have used kfree_rcu() from within call_srcu() handlers.
 */
struct device_opp {
struct opp_table {
	struct list_head node;

	struct srcu_notifier_head srcu_head;
@@ -153,12 +160,18 @@ struct device_opp {

	struct device_node *np;
	unsigned long clock_latency_ns_max;

	/* For backward compatibility with v1 bindings */
	unsigned int voltage_tolerance_v1;

	bool shared_opp;
	struct dev_pm_opp *suspend_opp;

	unsigned int *supported_hw;
	unsigned int supported_hw_count;
	const char *prop_name;
	struct clk *clk;
	struct regulator *regulator;

#ifdef CONFIG_DEBUG_FS
	struct dentry *dentry;
@@ -167,30 +180,27 @@ struct device_opp {
};

/* Routines internal to opp core */
struct device_opp *_find_device_opp(struct device *dev);
struct device_list_opp *_add_list_dev(const struct device *dev,
				      struct device_opp *dev_opp);
struct opp_table *_find_opp_table(struct device *dev);
struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table);
struct device_node *_of_get_opp_desc_node(struct device *dev);

#ifdef CONFIG_DEBUG_FS
void opp_debug_remove_one(struct dev_pm_opp *opp);
int opp_debug_create_one(struct dev_pm_opp *opp, struct device_opp *dev_opp);
int opp_debug_register(struct device_list_opp *list_dev,
		       struct device_opp *dev_opp);
void opp_debug_unregister(struct device_list_opp *list_dev,
			  struct device_opp *dev_opp);
int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table);
int opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table);
void opp_debug_unregister(struct opp_device *opp_dev, struct opp_table *opp_table);
#else
static inline void opp_debug_remove_one(struct dev_pm_opp *opp) {}

static inline int opp_debug_create_one(struct dev_pm_opp *opp,
				       struct device_opp *dev_opp)
				       struct opp_table *opp_table)
{ return 0; }
static inline int opp_debug_register(struct device_list_opp *list_dev,
				     struct device_opp *dev_opp)
static inline int opp_debug_register(struct opp_device *opp_dev,
				     struct opp_table *opp_table)
{ return 0; }

static inline void opp_debug_unregister(struct device_list_opp *list_dev,
					struct device_opp *dev_opp)
static inline void opp_debug_unregister(struct opp_device *opp_dev,
					struct opp_table *opp_table)
{ }
#endif		/* DEBUG_FS */

+27 −0
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp);

int dev_pm_opp_get_opp_count(struct device *dev);
unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev);
unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev);
unsigned long dev_pm_opp_get_max_transition_latency(struct device *dev);
struct dev_pm_opp *dev_pm_opp_get_suspend_opp(struct device *dev);

struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
@@ -60,6 +62,9 @@ int dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions,
void dev_pm_opp_put_supported_hw(struct device *dev);
int dev_pm_opp_set_prop_name(struct device *dev, const char *name);
void dev_pm_opp_put_prop_name(struct device *dev);
int dev_pm_opp_set_regulator(struct device *dev, const char *name);
void dev_pm_opp_put_regulator(struct device *dev);
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
#else
static inline unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
{
@@ -86,6 +91,16 @@ static inline unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev)
	return 0;
}

static inline unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
{
	return 0;
}

static inline unsigned long dev_pm_opp_get_max_transition_latency(struct device *dev)
{
	return 0;
}

static inline struct dev_pm_opp *dev_pm_opp_get_suspend_opp(struct device *dev)
{
	return NULL;
@@ -151,6 +166,18 @@ static inline int dev_pm_opp_set_prop_name(struct device *dev, const char *name)

static inline void dev_pm_opp_put_prop_name(struct device *dev) {}

static inline int dev_pm_opp_set_regulator(struct device *dev, const char *name)
{
	return -EINVAL;
}

static inline void dev_pm_opp_put_regulator(struct device *dev) {}

static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
	return -EINVAL;
}

#endif		/* CONFIG_PM_OPP */

#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)