Commit 4c277e2f authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

Merge branch 'pm-opp'

* pm-opp:
  opp: Don't parse icc paths unnecessarily
  opp: Remove bandwidth votes when target_freq is zero
  opp: core: add regulators enable and disable
  opp: Reorder the code for !target_freq case
  opp: Expose bandwidth information via debugfs
  cpufreq: dt: Add support for interconnect bandwidth scaling
  opp: Update the bandwidth on OPP frequency changes
  opp: Add sanity checks in _read_opp_key()
  opp: Add support for parsing interconnect bandwidth
  interconnect: Remove unused module exit code from core
  interconnect: Disallow interconnect core to be built as a module
  interconnect: Add of_icc_get_by_index() helper function
  OPP: Add helpers for reading the binding properties
  dt-bindings: opp: Introduce opp-peak-kBps and opp-avg-kBps bindings
parents afd8d7c7 4573e9ef
...@@ -83,9 +83,14 @@ properties. ...@@ -83,9 +83,14 @@ properties.
Required properties: Required properties:
- opp-hz: Frequency in Hz, expressed as a 64-bit big-endian integer. This is a - opp-hz: Frequency in Hz, expressed as a 64-bit big-endian integer. This is a
required property for all device nodes but devices like power domains. The required property for all device nodes, unless another "required" property to
power domain nodes must have another (implementation dependent) property which uniquely identify the OPP nodes exists. Devices like power domains must have
uniquely identifies the OPP nodes. another (implementation dependent) property.
- opp-peak-kBps: Peak bandwidth in kilobytes per second, expressed as an array
of 32-bit big-endian integers. Each element of the array represents the
peak bandwidth value of each interconnect path. The number of elements should
match the number of interconnect paths.
Optional properties: Optional properties:
- opp-microvolt: voltage in micro Volts. - opp-microvolt: voltage in micro Volts.
...@@ -132,6 +137,12 @@ Optional properties: ...@@ -132,6 +137,12 @@ Optional properties:
- opp-level: A value representing the performance level of the device, - opp-level: A value representing the performance level of the device,
expressed as a 32-bit integer. expressed as a 32-bit integer.
- opp-avg-kBps: Average bandwidth in kilobytes per second, expressed as an array
of 32-bit big-endian integers. Each element of the array represents the
average bandwidth value of each interconnect path. The number of elements
should match the number of interconnect paths. This property is only
meaningful in OPP tables where opp-peak-kBps is present.
- clock-latency-ns: Specifies the maximum possible transition latency (in - clock-latency-ns: Specifies the maximum possible transition latency (in
nanoseconds) for switching to this OPP from any other OPP. nanoseconds) for switching to this OPP from any other OPP.
......
...@@ -41,3 +41,7 @@ Temperature ...@@ -41,3 +41,7 @@ Temperature
Pressure Pressure
---------------------------------------- ----------------------------------------
-kpascal : kilopascal -kpascal : kilopascal
Throughput
----------------------------------------
-kBps : kilobytes per second
...@@ -121,6 +121,10 @@ static int resources_available(void) ...@@ -121,6 +121,10 @@ static int resources_available(void)
clk_put(cpu_clk); clk_put(cpu_clk);
ret = dev_pm_opp_of_find_icc_paths(cpu_dev, NULL);
if (ret)
return ret;
name = find_supply_name(cpu_dev); name = find_supply_name(cpu_dev);
/* Platform doesn't require regulator */ /* Platform doesn't require regulator */
if (!name) if (!name)
......
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
menuconfig INTERCONNECT menuconfig INTERCONNECT
tristate "On-Chip Interconnect management support" bool "On-Chip Interconnect management support"
help help
Support for management of the on-chip interconnects. Support for management of the on-chip interconnects.
......
...@@ -351,9 +351,9 @@ static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec) ...@@ -351,9 +351,9 @@ static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
} }
/** /**
* of_icc_get() - get a path handle from a DT node based on name * of_icc_get_by_index() - get a path handle from a DT node based on index
* @dev: device pointer for the consumer device * @dev: device pointer for the consumer device
* @name: interconnect path name * @idx: interconnect path index
* *
* This function will search for a path between two endpoints and return an * This function will search for a path between two endpoints and return an
* icc_path handle on success. Use icc_put() to release constraints when they * icc_path handle on success. Use icc_put() to release constraints when they
...@@ -365,13 +365,12 @@ static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec) ...@@ -365,13 +365,12 @@ static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
* Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned * Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned
* when the API is disabled or the "interconnects" DT property is missing. * when the API is disabled or the "interconnects" DT property is missing.
*/ */
struct icc_path *of_icc_get(struct device *dev, const char *name) struct icc_path *of_icc_get_by_index(struct device *dev, int idx)
{ {
struct icc_path *path = ERR_PTR(-EPROBE_DEFER); struct icc_path *path;
struct icc_node *src_node, *dst_node; struct icc_node *src_node, *dst_node;
struct device_node *np = NULL; struct device_node *np;
struct of_phandle_args src_args, dst_args; struct of_phandle_args src_args, dst_args;
int idx = 0;
int ret; int ret;
if (!dev || !dev->of_node) if (!dev || !dev->of_node)
...@@ -391,12 +390,6 @@ struct icc_path *of_icc_get(struct device *dev, const char *name) ...@@ -391,12 +390,6 @@ struct icc_path *of_icc_get(struct device *dev, const char *name)
* lets support only global ids and extend this in the future if needed * lets support only global ids and extend this in the future if needed
* without breaking DT compatibility. * without breaking DT compatibility.
*/ */
if (name) {
idx = of_property_match_string(np, "interconnect-names", name);
if (idx < 0)
return ERR_PTR(idx);
}
ret = of_parse_phandle_with_args(np, "interconnects", ret = of_parse_phandle_with_args(np, "interconnects",
"#interconnect-cells", idx * 2, "#interconnect-cells", idx * 2,
&src_args); &src_args);
...@@ -439,12 +432,8 @@ struct icc_path *of_icc_get(struct device *dev, const char *name) ...@@ -439,12 +432,8 @@ struct icc_path *of_icc_get(struct device *dev, const char *name)
return path; return path;
} }
if (name) path->name = kasprintf(GFP_KERNEL, "%s-%s",
path->name = kstrdup_const(name, GFP_KERNEL); src_node->name, dst_node->name);
else
path->name = kasprintf(GFP_KERNEL, "%s-%s",
src_node->name, dst_node->name);
if (!path->name) { if (!path->name) {
kfree(path); kfree(path);
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -452,6 +441,53 @@ struct icc_path *of_icc_get(struct device *dev, const char *name) ...@@ -452,6 +441,53 @@ struct icc_path *of_icc_get(struct device *dev, const char *name)
return path; return path;
} }
EXPORT_SYMBOL_GPL(of_icc_get_by_index);
/**
* of_icc_get() - get a path handle from a DT node based on name
* @dev: device pointer for the consumer device
* @name: interconnect path name
*
* This function will search for a path between two endpoints and return an
* icc_path handle on success. Use icc_put() to release constraints when they
* are not needed anymore.
* If the interconnect API is disabled, NULL is returned and the consumer
* drivers will still build. Drivers are free to handle this specifically,
* but they don't have to.
*
* Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned
* when the API is disabled or the "interconnects" DT property is missing.
*/
struct icc_path *of_icc_get(struct device *dev, const char *name)
{
struct device_node *np;
int idx = 0;
if (!dev || !dev->of_node)
return ERR_PTR(-ENODEV);
np = dev->of_node;
/*
* When the consumer DT node do not have "interconnects" property
* return a NULL path to skip setting constraints.
*/
if (!of_find_property(np, "interconnects", NULL))
return NULL;
/*
* We use a combination of phandle and specifier for endpoint. For now
* lets support only global ids and extend this in the future if needed
* without breaking DT compatibility.
*/
if (name) {
idx = of_property_match_string(np, "interconnect-names", name);
if (idx < 0)
return ERR_PTR(idx);
}
return of_icc_get_by_index(dev, idx);
}
EXPORT_SYMBOL_GPL(of_icc_get); EXPORT_SYMBOL_GPL(of_icc_get);
/** /**
...@@ -478,6 +514,24 @@ void icc_set_tag(struct icc_path *path, u32 tag) ...@@ -478,6 +514,24 @@ void icc_set_tag(struct icc_path *path, u32 tag)
} }
EXPORT_SYMBOL_GPL(icc_set_tag); EXPORT_SYMBOL_GPL(icc_set_tag);
/**
* icc_get_name() - Get name of the icc path
* @path: reference to the path returned by icc_get()
*
* This function is used by an interconnect consumer to get the name of the icc
* path.
*
* Returns a valid pointer on success, or NULL otherwise.
*/
const char *icc_get_name(struct icc_path *path)
{
if (!path)
return NULL;
return path->name;
}
EXPORT_SYMBOL_GPL(icc_get_name);
/** /**
* icc_set_bw() - set bandwidth constraints on an interconnect path * icc_set_bw() - set bandwidth constraints on an interconnect path
* @path: reference to the path returned by icc_get() * @path: reference to the path returned by icc_get()
...@@ -908,12 +962,7 @@ static int __init icc_init(void) ...@@ -908,12 +962,7 @@ static int __init icc_init(void)
return 0; return 0;
} }
static void __exit icc_exit(void) device_initcall(icc_init);
{
debugfs_remove_recursive(icc_debugfs_dir);
}
module_init(icc_init);
module_exit(icc_exit);
MODULE_AUTHOR("Georgi Djakov <georgi.djakov@linaro.org>"); MODULE_AUTHOR("Georgi Djakov <georgi.djakov@linaro.org>");
MODULE_DESCRIPTION("Interconnect Driver Core"); MODULE_DESCRIPTION("Interconnect Driver Core");
......
...@@ -664,7 +664,7 @@ static inline int _generic_set_opp_clk_only(struct device *dev, struct clk *clk, ...@@ -664,7 +664,7 @@ static inline int _generic_set_opp_clk_only(struct device *dev, struct clk *clk,
return ret; return ret;
} }
static int _generic_set_opp_regulator(const struct opp_table *opp_table, static int _generic_set_opp_regulator(struct opp_table *opp_table,
struct device *dev, struct device *dev,
unsigned long old_freq, unsigned long old_freq,
unsigned long freq, unsigned long freq,
...@@ -699,6 +699,18 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table, ...@@ -699,6 +699,18 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table,
goto restore_freq; goto restore_freq;
} }
/*
* Enable the regulator after setting its voltages, otherwise it breaks
* some boot-enabled regulators.
*/
if (unlikely(!opp_table->regulator_enabled)) {
ret = regulator_enable(reg);
if (ret < 0)
dev_warn(dev, "Failed to enable regulator: %d", ret);
else
opp_table->regulator_enabled = true;
}
return 0; return 0;
restore_freq: restore_freq:
...@@ -713,6 +725,34 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table, ...@@ -713,6 +725,34 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table,
return ret; return ret;
} }
static int _set_opp_bw(const struct opp_table *opp_table,
struct dev_pm_opp *opp, struct device *dev, bool remove)
{
u32 avg, peak;
int i, ret;
if (!opp_table->paths)
return 0;
for (i = 0; i < opp_table->path_count; i++) {
if (remove) {
avg = 0;
peak = 0;
} else {
avg = opp->bandwidth[i].avg;
peak = opp->bandwidth[i].peak;
}
ret = icc_set_bw(opp_table->paths[i], avg, peak);
if (ret) {
dev_err(dev, "Failed to %s bandwidth[%d]: %d\n",
remove ? "remove" : "set", i, ret);
return ret;
}
}
return 0;
}
static int _set_opp_custom(const struct opp_table *opp_table, static int _set_opp_custom(const struct opp_table *opp_table,
struct device *dev, unsigned long old_freq, struct device *dev, unsigned long old_freq,
unsigned long freq, unsigned long freq,
...@@ -817,15 +857,31 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) ...@@ -817,15 +857,31 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
} }
if (unlikely(!target_freq)) { if (unlikely(!target_freq)) {
if (opp_table->required_opp_tables) { /*
ret = _set_required_opps(dev, opp_table, NULL); * Some drivers need to support cases where some platforms may
} else if (!_get_opp_count(opp_table)) { * have OPP table for the device, while others don't and
* opp_set_rate() just needs to behave like clk_set_rate().
*/
if (!_get_opp_count(opp_table))
return 0; return 0;
} else {
if (!opp_table->required_opp_tables && !opp_table->regulators &&
!opp_table->paths) {
dev_err(dev, "target frequency can't be 0\n"); dev_err(dev, "target frequency can't be 0\n");
ret = -EINVAL; ret = -EINVAL;
goto put_opp_table;
}
ret = _set_opp_bw(opp_table, NULL, dev, true);
if (ret)
return ret;
if (opp_table->regulator_enabled) {
regulator_disable(opp_table->regulators[0]);
opp_table->regulator_enabled = false;
} }
ret = _set_required_opps(dev, opp_table, NULL);
goto put_opp_table; goto put_opp_table;
} }
...@@ -909,6 +965,9 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) ...@@ -909,6 +965,9 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
dev_err(dev, "Failed to set required opps: %d\n", ret); dev_err(dev, "Failed to set required opps: %d\n", ret);
} }
if (!ret)
ret = _set_opp_bw(opp_table, opp, dev, false);
put_opp: put_opp:
dev_pm_opp_put(opp); dev_pm_opp_put(opp);
put_old_opp: put_old_opp:
...@@ -999,6 +1058,12 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index) ...@@ -999,6 +1058,12 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index)
ret); ret);
} }
/* Find interconnect path(s) for the device */
ret = dev_pm_opp_of_find_icc_paths(dev, opp_table);
if (ret)
dev_warn(dev, "%s: Error finding interconnect paths: %d\n",
__func__, ret);
BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head); BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
INIT_LIST_HEAD(&opp_table->opp_list); INIT_LIST_HEAD(&opp_table->opp_list);
kref_init(&opp_table->kref); kref_init(&opp_table->kref);
...@@ -1057,6 +1122,7 @@ static void _opp_table_kref_release(struct kref *kref) ...@@ -1057,6 +1122,7 @@ static void _opp_table_kref_release(struct kref *kref)
{ {
struct opp_table *opp_table = container_of(kref, struct opp_table, kref); struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
struct opp_device *opp_dev, *temp; struct opp_device *opp_dev, *temp;
int i;
_of_clear_opp_table(opp_table); _of_clear_opp_table(opp_table);
...@@ -1064,6 +1130,12 @@ static void _opp_table_kref_release(struct kref *kref) ...@@ -1064,6 +1130,12 @@ static void _opp_table_kref_release(struct kref *kref)
if (!IS_ERR(opp_table->clk)) if (!IS_ERR(opp_table->clk))
clk_put(opp_table->clk); clk_put(opp_table->clk);
if (opp_table->paths) {
for (i = 0; i < opp_table->path_count; i++)
icc_put(opp_table->paths[i]);
kfree(opp_table->paths);
}
WARN_ON(!list_empty(&opp_table->opp_list)); WARN_ON(!list_empty(&opp_table->opp_list));
list_for_each_entry_safe(opp_dev, temp, &opp_table->dev_list, node) { list_for_each_entry_safe(opp_dev, temp, &opp_table->dev_list, node) {
...@@ -1243,19 +1315,23 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_remove_all_dynamic); ...@@ -1243,19 +1315,23 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_remove_all_dynamic);
struct dev_pm_opp *_opp_allocate(struct opp_table *table) struct dev_pm_opp *_opp_allocate(struct opp_table *table)
{ {
struct dev_pm_opp *opp; struct dev_pm_opp *opp;
int count, supply_size; int supply_count, supply_size, icc_size;
/* Allocate space for at least one supply */ /* Allocate space for at least one supply */
count = table->regulator_count > 0 ? table->regulator_count : 1; supply_count = table->regulator_count > 0 ? table->regulator_count : 1;
supply_size = sizeof(*opp->supplies) * count; supply_size = sizeof(*opp->supplies) * supply_count;
icc_size = sizeof(*opp->bandwidth) * table->path_count;
/* allocate new OPP node and supplies structures */ /* allocate new OPP node and supplies structures */
opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL); opp = kzalloc(sizeof(*opp) + supply_size + icc_size, GFP_KERNEL);
if (!opp) if (!opp)
return NULL; return NULL;
/* Put the supplies at the end of the OPP structure as an empty array */ /* Put the supplies at the end of the OPP structure as an empty array */
opp->supplies = (struct dev_pm_opp_supply *)(opp + 1); opp->supplies = (struct dev_pm_opp_supply *)(opp + 1);
if (icc_size)
opp->bandwidth = (struct dev_pm_opp_icc_bw *)(opp->supplies + supply_count);
INIT_LIST_HEAD(&opp->node); INIT_LIST_HEAD(&opp->node);
return opp; return opp;
...@@ -1286,11 +1362,24 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp, ...@@ -1286,11 +1362,24 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
return true; return true;
} }
int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
{
if (opp1->rate != opp2->rate)
return opp1->rate < opp2->rate ? -1 : 1;
if (opp1->bandwidth && opp2->bandwidth &&
opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
if (opp1->level != opp2->level)
return opp1->level < opp2->level ? -1 : 1;
return 0;
}
static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp, static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp,
struct opp_table *opp_table, struct opp_table *opp_table,
struct list_head **head) struct list_head **head)
{ {
struct dev_pm_opp *opp; struct dev_pm_opp *opp;
int opp_cmp;
/* /*
* Insert new OPP in order of increasing frequency and discard if * Insert new OPP in order of increasing frequency and discard if
...@@ -1301,12 +1390,13 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp, ...@@ -1301,12 +1390,13 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp,
* loop. * loop.
*/ */
list_for_each_entry(opp, &opp_table->opp_list, node) { list_for_each_entry(opp, &opp_table->opp_list, node) {
if (new_opp->rate > opp->rate) { opp_cmp = _opp_compare_key(new_opp, opp);
if (opp_cmp > 0) {
*head = &opp->node; *head = &opp->node;
continue; continue;
} }
if (new_opp->rate < opp->rate) if (opp_cmp < 0)
return 0; return 0;
/* Duplicate OPPs */ /* Duplicate OPPs */
...@@ -1670,6 +1760,13 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table) ...@@ -1670,6 +1760,13 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
/* Make sure there are no concurrent readers while updating opp_table */ /* Make sure there are no concurrent readers while updating opp_table */
WARN_ON(!list_empty(&opp_table->opp_list)); WARN_ON(!list_empty(&opp_table->opp_list));
if (opp_table->regulator_enabled) {
for (i = opp_table->regulator_count - 1; i >= 0; i--)
regulator_disable(opp_table->regulators[i]);
opp_table->regulator_enabled = false;
}
for (i = opp_table->regulator_count - 1; i >= 0; i--) for (i = opp_table->regulator_count - 1; i >= 0; i--)
regulator_put(opp_table->regulators[i]); regulator_put(opp_table->regulators[i]);
......
...@@ -32,6 +32,47 @@ void opp_debug_remove_one(struct dev_pm_opp *opp) ...@@ -32,6 +32,47 @@ void opp_debug_remove_one(struct dev_pm_opp *opp)
debugfs_remove_recursive(opp->dentry); debugfs_remove_recursive(opp->dentry);
} }
static ssize_t bw_name_read(struct file *fp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct icc_path *path = fp->private_data;
char buf[64];
int i;
i = scnprintf(buf, sizeof(buf), "%.62s\n", icc_get_name(path));
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
}
static const struct file_operations bw_name_fops = {
.open = simple_open,
.read = bw_name_read,
.llseek = default_llseek,
};
static void opp_debug_create_bw(struct dev_pm_opp *opp,
struct opp_table *opp_table,
struct dentry *pdentry)
{
struct dentry *d;
char name[11];
int i;
for (i = 0; i < opp_table->path_count; i++) {
snprintf(name, sizeof(name), "icc-path-%.1d", i);
/* Create per-path directory */
d = debugfs_create_dir(name, pdentry);
debugfs_create_file("name", S_IRUGO, d, opp_table->paths[i],
&bw_name_fops);
debugfs_create_u32("peak_bw", S_IRUGO, d,
&opp->bandwidth[i].peak);
debugfs_create_u32("avg_bw", S_IRUGO, d,
&opp->bandwidth[i].avg);
}
}
static void opp_debug_create_supplies(struct dev_pm_opp *opp, static void opp_debug_create_supplies(struct dev_pm_opp *opp,
struct opp_table *opp_table, struct opp_table *opp_table,
struct dentry *pdentry) struct dentry *pdentry)
...@@ -94,6 +135,7 @@ void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) ...@@ -94,6 +135,7 @@ void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
&opp->clock_latency_ns); &opp->clock_latency_ns);
opp_debug_create_supplies(opp, opp_table, d); opp_debug_create_supplies(opp, opp_table, d);
opp_debug_create_bw(opp, opp_table, d);
opp->dentry = d; opp->dentry = d;
} }
......
...@@ -332,6 +332,105 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table, ...@@ -332,6 +332,105 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table,
return ret; return ret;
} }
static int _bandwidth_supported(struct device *dev, struct opp_table *opp_table)
{
struct device_node *np, *opp_np;
struct property *prop;
if (!opp_table) {
np = of_node_get(dev->of_node);
if (!np)
return -ENODEV;
opp_np = _opp_of_get_opp_desc_node(np, 0);
of_node_put(np);
} else {
opp_np = of_node_get(opp_table->np);
}
/* Lets not fail in case we are parsing opp-v1 bindings */
if (!opp_np)
return 0;
/* Checking only first OPP is sufficient */
np = of_get_next_available_child(opp_np, NULL);
if (!np) {
dev_err(dev, "OPP table empty\n");
return -EINVAL;
}
of_node_put(opp_np);
prop = of_find_property(np, "opp-peak-kBps", NULL);
of_node_put(np);
if (!prop || !prop->length)
return 0;
return 1;
}
int dev_pm_opp_of_find_icc_paths(struct device *dev,
struct opp_table *opp_table)
{
struct device_node *np;
int ret, i, count, num_paths;
struct icc_path **paths;
ret = _bandwidth_supported(dev, opp_table);
if (ret <= 0)
return ret;
ret = 0;
np = of_node_get(dev->of_node);
if (!np)
return 0;
count = of_count_phandle_with_args(np, "interconnects",
"#interconnect-cells");
of_node_put(np);
if (count < 0)
return 0;
/* two phandles when #interconnect-cells = <1> */
if (count % 2) {
dev_err(dev, "%s: Invalid interconnects values\n", __func__);
return -EINVAL;
}
num_paths = count / 2;
paths = kcalloc(num_paths, sizeof(*paths), GFP_KERNEL);
if (!paths)
return -ENOMEM;
for (i = 0; i < num_paths; i++) {
paths[i] = of_icc_get_by_index(dev, i);
if (IS_ERR(paths[i])) {
ret = PTR_ERR(paths[i]);
if (ret != -EPROBE_DEFER) {
dev_err(dev, "%s: Unable to get path%d: %d\n",
__func__, i, ret);
}
goto err;
}
}
if (opp_table) {
opp_table->paths = paths;
opp_table->path_count = num_paths;
return 0;
}
err:
while (i--)
icc_put(paths[i]);
kfree(paths);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_of_find_icc_paths);
static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table, static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
struct device_node *np) struct device_node *np)
{ {
...@@ -521,6 +620,90 @@ void dev_pm_opp_of_remove_table(struct device *dev) ...@@ -521,6 +620,90 @@ void dev_pm_opp_of_remove_table(struct device *dev)
} }
EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table); EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table);
static int _read_bw(struct dev_pm_opp *new_opp, struct opp_table *table,
struct device_node *np, bool peak)
{
const char *name = peak ? "opp-peak-kBps" : "opp-avg-kBps";
struct property *prop;
int i, count, ret;
u32 *bw;
prop = of_find_property(np, name, NULL);
if (!prop)
return -ENODEV;
count = prop->length / sizeof(u32);
if (table->path_count != count) {
pr_err("%s: Mismatch between %s and paths (%d %d)\n",
__func__, name, count, table->path_count);
return -EINVAL;
}
bw = kmalloc_array(count, sizeof(*bw), GFP_KERNEL);
if (!bw)
return -ENOMEM;
ret = of_property_read_u32_array(np, name, bw, count);
if (ret) {
pr_err("%s: Error parsing %s: %d\n", __func__, name, ret);
goto out;
}
for (i = 0; i < count; i++) {
if (peak)
new_opp->bandwidth[i].peak = kBps_to_icc(bw[i]);
else
new_opp->bandwidth[i].avg = kBps_to_icc(bw[i]);
}
out:
kfree(bw);
return ret;
}
static int _read_opp_key(struct dev_pm_opp *new_opp, struct opp_table *table,
struct device_node *np, bool *rate_not_available)
{
bool found = false;
u64 rate;
int ret;
ret = of_property_read_u64(np, "opp-hz", &rate);
if (!ret) {
/*
* Rate is defined as an unsigned long in clk API, and so
* casting explicitly to its type. Must be fixed once rate is 64
* bit guaranteed in clk API.
*/
new_opp->rate = (unsigned long)rate;
found = true;
}
*rate_not_available = !!ret;
/*
* Bandwidth consists of peak and average (optional) values:
* opp-peak-kBps = <path1_value path2_value>;
* opp-avg-kBps = <path1_value path2_value>;
*/
ret = _read_bw(new_opp, table, np, true);
if (!ret) {
found = true;
ret = _read_bw(new_opp, table, np, false);
}
/* The properties were found but we failed to parse them */
if (ret && ret != -ENODEV)
return ret;
if (!of_property_read_u32(np, "opp-level", &new_opp->level))
found = true;
if (found)
return 0;
return ret;
}
/** /**
* _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings) * _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings)
* @opp_table: OPP table * @opp_table: OPP table
...@@ -558,26 +741,12 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, ...@@ -558,26 +741,12 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
if (!new_opp) if (!new_opp)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
ret = of_property_read_u64(np, "opp-hz", &rate); ret = _read_opp_key(new_opp, opp_table, np, &rate_not_available);
if (ret < 0) { if (ret < 0 && !opp_table->is_genpd) {
/* "opp-hz" is optional for devices like power domains. */ dev_err(dev, "%s: opp key field not found\n", __func__);
if (!opp_table->is_genpd) { goto free_opp;
dev_err(dev, "%s: opp-hz not found\n", __func__);
goto free_opp;
}
rate_not_available = true;
} else {
/*
* Rate is defined as an unsigned long in clk API, and so
* casting explicitly to its type. Must be fixed once rate is 64
* bit guaranteed in clk API.
*/
new_opp->rate = (unsigned long)rate;
} }
of_property_read_u32(np, "opp-level", &new_opp->level);
/* Check if the OPP supports hardware's hierarchy of versions or not */ /* Check if the OPP supports hardware's hierarchy of versions or not */
if (!_opp_is_supported(dev, opp_table, np)) { if (!_opp_is_supported(dev, opp_table, np)) {
dev_dbg(dev, "OPP not supported by hardware: %llu\n", rate); dev_dbg(dev, "OPP not supported by hardware: %llu\n", rate);
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#define __DRIVER_OPP_H__ #define __DRIVER_OPP_H__
#include <linux/device.h> #include <linux/device.h>
#include <linux/interconnect.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/kref.h> #include <linux/kref.h>
#include <linux/list.h> #include <linux/list.h>
...@@ -59,6 +60,7 @@ extern struct list_head opp_tables; ...@@ -59,6 +60,7 @@ extern struct list_head opp_tables;
* @rate: Frequency in hertz * @rate: Frequency in hertz
* @level: Performance level * @level: Performance level
* @supplies: Power supplies voltage/current values * @supplies: Power supplies voltage/current values
* @bandwidth: Interconnect bandwidth values
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
* frequency from any other OPP's frequency. * frequency from any other OPP's frequency.
* @required_opps: List of OPPs that are required by this OPP. * @required_opps: List of OPPs that are required by this OPP.
...@@ -81,6 +83,7 @@ struct dev_pm_opp { ...@@ -81,6 +83,7 @@ struct dev_pm_opp {
unsigned int level; unsigned int level;
struct dev_pm_opp_supply *supplies; struct dev_pm_opp_supply *supplies;
struct dev_pm_opp_icc_bw *bandwidth;
unsigned long clock_latency_ns; unsigned long clock_latency_ns;
...@@ -144,8 +147,11 @@ enum opp_table_access { ...@@ -144,8 +147,11 @@ enum opp_table_access {
* @clk: Device's clock handle * @clk: Device's clock handle
* @regulators: Supply regulators * @regulators: Supply regulators
* @regulator_count: Number of power supply regulators. Its value can be -1 * @regulator_count: Number of power supply regulators. Its value can be -1
* @regulator_enabled: Set to true if regulators were previously enabled.
* (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt * (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt
* property). * property).
* @paths: Interconnect path handles
* @path_count: Number of interconnect paths
* @genpd_performance_state: Device's power domain support performance state. * @genpd_performance_state: Device's power domain support performance state.
* @is_genpd: Marks if the OPP table belongs to a genpd. * @is_genpd: Marks if the OPP table belongs to a genpd.
* @set_opp: Platform specific set_opp callback * @set_opp: Platform specific set_opp callback
...@@ -189,6 +195,9 @@ struct opp_table { ...@@ -189,6 +195,9 @@ struct opp_table {
struct clk *clk; struct clk *clk;
struct regulator **regulators; struct regulator **regulators;
int regulator_count; int regulator_count;
bool regulator_enabled;
struct icc_path **paths;
unsigned int path_count;
bool genpd_performance_state; bool genpd_performance_state;
bool is_genpd; bool is_genpd;
...@@ -211,6 +220,7 @@ struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_ ...@@ -211,6 +220,7 @@ struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_
void _dev_pm_opp_find_and_remove_table(struct device *dev); void _dev_pm_opp_find_and_remove_table(struct device *dev);
struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table); struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table);
void _opp_free(struct dev_pm_opp *opp); void _opp_free(struct dev_pm_opp *opp);
int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2);
int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available); int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available);
int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic); int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic);
void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu); void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu);
......
...@@ -28,9 +28,11 @@ struct device; ...@@ -28,9 +28,11 @@ struct device;
struct icc_path *icc_get(struct device *dev, const int src_id, struct icc_path *icc_get(struct device *dev, const int src_id,
const int dst_id); const int dst_id);
struct icc_path *of_icc_get(struct device *dev, const char *name); struct icc_path *of_icc_get(struct device *dev, const char *name);
struct icc_path *of_icc_get_by_index(struct device *dev, int idx);
void icc_put(struct icc_path *path); void icc_put(struct icc_path *path);
int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw); int icc_set_bw(struct icc_path *path, u32 avg_bw, u32 peak_bw);
void icc_set_tag(struct icc_path *path, u32 tag); void icc_set_tag(struct icc_path *path, u32 tag);
const char *icc_get_name(struct icc_path *path);
#else #else
...@@ -46,6 +48,11 @@ static inline struct icc_path *of_icc_get(struct device *dev, ...@@ -46,6 +48,11 @@ static inline struct icc_path *of_icc_get(struct device *dev,
return NULL; return NULL;
} }
static inline struct icc_path *of_icc_get_by_index(struct device *dev, int idx)
{
return NULL;
}
static inline void icc_put(struct icc_path *path) static inline void icc_put(struct icc_path *path)
{ {
} }
...@@ -59,6 +66,11 @@ static inline void icc_set_tag(struct icc_path *path, u32 tag) ...@@ -59,6 +66,11 @@ static inline void icc_set_tag(struct icc_path *path, u32 tag)
{ {
} }
static inline const char *icc_get_name(struct icc_path *path)
{
return NULL;
}
#endif /* CONFIG_INTERCONNECT */ #endif /* CONFIG_INTERCONNECT */
#endif /* __LINUX_INTERCONNECT_H */ #endif /* __LINUX_INTERCONNECT_H */
...@@ -41,6 +41,18 @@ struct dev_pm_opp_supply { ...@@ -41,6 +41,18 @@ struct dev_pm_opp_supply {
unsigned long u_amp; unsigned long u_amp;
}; };
/**
* struct dev_pm_opp_icc_bw - Interconnect bandwidth values
* @avg: Average bandwidth corresponding to this OPP (in icc units)
* @peak: Peak bandwidth corresponding to this OPP (in icc units)
*
* This structure stores the bandwidth values for a single interconnect path.
*/
struct dev_pm_opp_icc_bw {
u32 avg;
u32 peak;
};
/** /**
* struct dev_pm_opp_info - OPP freq/voltage/current values * struct dev_pm_opp_info - OPP freq/voltage/current values
* @rate: Target clk rate in hz * @rate: Target clk rate in hz
...@@ -360,6 +372,7 @@ int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpuma ...@@ -360,6 +372,7 @@ int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpuma
struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev); struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev);
struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp); struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp);
int of_get_required_opp_performance_state(struct device_node *np, int index); int of_get_required_opp_performance_state(struct device_node *np, int index);
int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_table *opp_table);
void dev_pm_opp_of_register_em(struct cpumask *cpus); void dev_pm_opp_of_register_em(struct cpumask *cpus);
#else #else
static inline int dev_pm_opp_of_add_table(struct device *dev) static inline int dev_pm_opp_of_add_table(struct device *dev)
...@@ -408,6 +421,11 @@ static inline int of_get_required_opp_performance_state(struct device_node *np, ...@@ -408,6 +421,11 @@ static inline int of_get_required_opp_performance_state(struct device_node *np,
{ {
return -ENOTSUPP; return -ENOTSUPP;
} }
static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_table *opp_table)
{
return -ENOTSUPP;
}
#endif #endif
#endif /* __LINUX_OPP_H__ */ #endif /* __LINUX_OPP_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment