PM / devfreq: icc: add support for L3 BW devices

Add support for L3 bandwidth devices that vote in units
of MBPS. This includes creating OPP tables in units of
MBPS as well as requiring that these devices define a
width in DT which is used to convert the votes to Hz
before submitting them to the L3 provider.

Change-Id: I47a569f76b9e57caac57788592784921c689505b
Signed-off-by: Amir Vajid <[email protected]>
This commit is contained in:
Amir Vajid 2020-05-06 09:11:13 -07:00
commit 7e0c383b72

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2014, 2018-2019, The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2014, 2018-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "devfreq-icc: " fmt
@ -21,6 +21,7 @@
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <trace/events/power.h>
#include <linux/platform_device.h>
#include <linux/interconnect.h>
@ -28,13 +29,27 @@
/* Has to be ULL to prevent overflow where this macro is used. */
#define MBYTE (1ULL << 20)
#define HZ_TO_MBPS(hz, w) (mult_frac(w, hz, MBYTE))
#define MBPS_TO_HZ(mbps, w) (mult_frac(mbps, MBYTE, w))
enum dev_type {
STD_MBPS_DEV,
L3_HZ_DEV,
L3_MBPS_DEV,
NUM_DEV_TYPES
};
struct devfreq_icc_spec {
enum dev_type type;
};
struct dev_data {
struct icc_path *icc_path;
u32 cur_ab;
u32 cur_ib;
unsigned long gov_ab;
bool is_l3;
const struct devfreq_icc_spec *spec;
unsigned int width;
struct devfreq *df;
struct devfreq_dev_profile dp;
};
@ -53,7 +68,10 @@ static int set_bw(struct device *dev, u32 new_ib, u32 new_ab)
if (d->cur_ib == new_ib && d->cur_ab == new_ab)
return 0;
if (!d->is_l3) {
if (d->spec->type == L3_MBPS_DEV) {
icc_ib = MBPS_TO_HZ(new_ib, d->width);
icc_ab = MBPS_TO_HZ(new_ab, d->width);
} else if (d->spec->type == STD_MBPS_DEV) {
icc_ib = Bps_to_icc(new_ib * MBYTE);
icc_ab = Bps_to_icc(new_ab * MBYTE);
}
@ -101,6 +119,7 @@ static int icc_get_dev_status(struct device *dev,
static int populate_l3_opp_table(struct device *dev)
{
struct dev_data *d = dev_get_drvdata(dev);
int idx, ret;
u32 data, src, mult, i;
unsigned long freq, prev_freq = 0;
@ -139,7 +158,10 @@ static int populate_l3_opp_table(struct device *dev)
if (i > 0 && prev_freq == freq)
break;
dev_pm_opp_add(dev, freq, 0);
if (d->spec->type == L3_MBPS_DEV)
dev_pm_opp_add(dev, HZ_TO_MBPS(freq, d->width), 0);
else
dev_pm_opp_add(dev, freq, 0);
l3_freqs[i] = freq;
prev_freq = freq;
}
@ -152,13 +174,18 @@ static int populate_l3_opp_table(struct device *dev)
static int copy_l3_opp_table(struct device *dev)
{
struct dev_data *d = dev_get_drvdata(dev);
int idx;
for (idx = 0; idx < MAX_L3_ENTRIES; idx++) {
if (l3_freqs[idx])
dev_pm_opp_add(dev, l3_freqs[idx], 0);
else
if (!l3_freqs[idx])
break;
if (d->spec->type == L3_MBPS_DEV)
dev_pm_opp_add(dev,
HZ_TO_MBPS(l3_freqs[idx], d->width), 0);
else
dev_pm_opp_add(dev, l3_freqs[idx], 0);
}
if (!idx) {
@ -186,6 +213,12 @@ int devfreq_add_icc(struct device *dev)
return -ENOMEM;
dev_set_drvdata(dev, d);
d->spec = of_device_get_match_data(dev);
if (!d->spec) {
dev_err(dev, "Unknown device type!\n");
return -ENODEV;
}
p = &d->dp;
p->polling_ms = 50;
p->target = icc_target;
@ -200,7 +233,16 @@ int devfreq_add_icc(struct device *dev)
}
}
if (of_device_is_compatible(dev->of_node, "qcom,devfreq-icc-l3")) {
if (d->spec->type == L3_MBPS_DEV) {
ret = of_property_read_u32(dev->of_node, "qcom,bus-width",
&d->width);
if (ret < 0 || !d->width) {
dev_err(dev, "Missing or invalid bus-width: %d\n", ret);
return -EINVAL;
}
}
if (d->spec->type == L3_HZ_DEV || d->spec->type == L3_MBPS_DEV) {
mutex_lock(&l3_freqs_lock);
if (use_cached_l3_freqs) {
mutex_unlock(&l3_freqs_lock);
@ -209,10 +251,8 @@ int devfreq_add_icc(struct device *dev)
ret = populate_l3_opp_table(dev);
mutex_unlock(&l3_freqs_lock);
}
d->is_l3 = true;
} else {
ret = dev_pm_opp_of_add_table(dev);
d->is_l3 = false;
}
if (ret < 0)
dev_err(dev, "Couldn't parse OPP table:%d\n", ret);
@ -273,11 +313,18 @@ static int devfreq_icc_remove(struct platform_device *pdev)
return devfreq_remove_icc(&pdev->dev);
}
static const struct devfreq_icc_spec spec[] = {
[0] = { STD_MBPS_DEV },
[1] = { L3_HZ_DEV },
[2] = { L3_MBPS_DEV },
};
static const struct of_device_id devfreq_icc_match_table[] = {
{ .compatible = "qcom,devfreq-icc-l3" },
{ .compatible = "qcom,devfreq-icc-llcc" },
{ .compatible = "qcom,devfreq-icc-ddr" },
{ .compatible = "qcom,devfreq-icc" },
{ .compatible = "qcom,devfreq-icc-l3bw", .data = &spec[2] },
{ .compatible = "qcom,devfreq-icc-l3", .data = &spec[1] },
{ .compatible = "qcom,devfreq-icc-llcc", .data = &spec[0] },
{ .compatible = "qcom,devfreq-icc-ddr", .data = &spec[0] },
{ .compatible = "qcom,devfreq-icc", .data = &spec[0] },
{}
};