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

Commit 7e0c383b authored by Amir Vajid's avatar Amir Vajid
Browse files

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: default avatarAmir Vajid <avajid@codeaurora.org>
parent e1bdeb41
Loading
Loading
Loading
Loading
+61 −14
Original line number Diff line number Diff line
// 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,6 +158,9 @@ static int populate_l3_opp_table(struct device *dev)
		if (i > 0 && prev_freq == freq)
			break;

		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] },
	{}
};