From 40f77da0e2210bee8cbbff3d2dbb129214db4f17 Mon Sep 17 00:00:00 2001 From: Saravana Kannan Date: Wed, 16 Jan 2019 18:09:59 -0800 Subject: [PATCH] msm: devfreq_icc: Add devfreq driver for device bandwidth voting This driver registers itself as a devfreq device that allows devfreq governors to make IB/AB bandwidth votes. This driver allows the governors to be agnostic of the bandwidth voting APIs, the number of master ports or slave ports, the actual port numbers, the system topology, the available frequencies, etc. Change-Id: If055ddd580afd41f9668b111e6c09a047488b2e0 Signed-off-by: Saravana Kannan [avajid@codeaurora.org: moved to icc framework, renamed to devfreq_icc and made minor styling changes] Signed-off-by: Amir Vajid --- drivers/devfreq/Kconfig | 14 +++ drivers/devfreq/Makefile | 1 + drivers/devfreq/devfreq_icc.c | 201 +++++++++++++++++++++++++++++++++ include/soc/qcom/devfreq_icc.h | 25 ++++ 4 files changed, 241 insertions(+) create mode 100644 drivers/devfreq/devfreq_icc.c create mode 100644 include/soc/qcom/devfreq_icc.h diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 0ea7d5caba68..b12e224ee439 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -155,6 +155,20 @@ config DEVFREQ_SIMPLE_DEV Device driver for simple devices that control their frequency using clock APIs and don't have any form of status reporting. +config QCOM_DEVFREQ_ICC + bool "Qualcomm Technologies Inc. DEVFREQ device for device master <-> slave IB/AB BW voting" + depends on ARCH_QCOM + select DEVFREQ_GOV_PERFORMANCE + select DEVFREQ_GOV_POWERSAVE + select DEVFREQ_GOV_USERSPACE + select INTERCONNECT + default n + help + Different devfreq governors use this devfreq device to make CPU to + DDR IB/AB bandwidth votes. This driver provides a SoC topology + agnostic interface to so that some of the devfreq governors can be + shared across SoCs. + source "drivers/devfreq/event/Kconfig" endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 0ccd5fdea588..f761f0d8f194 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o +obj-$(CONFIG_QCOM_DEVFREQ_ICC) += devfreq_icc.o obj-$(CONFIG_DEVFREQ_SIMPLE_DEV) += devfreq_simple_dev.o # DEVFREQ Event Drivers diff --git a/drivers/devfreq/devfreq_icc.c b/drivers/devfreq/devfreq_icc.c new file mode 100644 index 000000000000..a052bb7a17d9 --- /dev/null +++ b/drivers/devfreq/devfreq_icc.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2014, 2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "devfreq-icc: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Has to be ULL to prevent overflow where this macro is used. */ +#define MBYTE (1ULL << 20) + +struct dev_data { + struct icc_path *icc_path; + u32 cur_ab; + u32 cur_ib; + unsigned long gov_ab; + struct devfreq *df; + struct devfreq_dev_profile dp; +}; + +static int set_bw(struct device *dev, u32 new_ib, u32 new_ab) +{ + struct dev_data *d = dev_get_drvdata(dev); + int ret; + + if (d->cur_ib == new_ib && d->cur_ab == new_ab) + return 0; + + dev_dbg(dev, "BW MBps: AB: %d IB: %d\n", new_ab, new_ib); + + ret = icc_set_bw(d->icc_path, Bps_to_icc(new_ab * MBYTE), + Bps_to_icc(new_ib * MBYTE)); + if (ret < 0) { + dev_err(dev, "icc set bandwidth request failed (%d)\n", ret); + } else { + d->cur_ib = new_ib; + d->cur_ab = new_ab; + } + + return ret; +} + +static void find_freq(struct devfreq_dev_profile *p, unsigned long *freq, + u32 flags) +{ + int i; + unsigned long atmost, atleast, f; + + atmost = p->freq_table[0]; + atleast = p->freq_table[p->max_state-1]; + for (i = 0; i < p->max_state; i++) { + f = p->freq_table[i]; + if (f <= *freq) + atmost = max(f, atmost); + if (f >= *freq) + atleast = min(f, atleast); + } + + if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) + *freq = atmost; + else + *freq = atleast; +} + +static int icc_target(struct device *dev, unsigned long *freq, u32 flags) +{ + struct dev_data *d = dev_get_drvdata(dev); + + find_freq(&d->dp, freq, flags); + return set_bw(dev, *freq, d->gov_ab); +} + +static int icc_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct dev_data *d = dev_get_drvdata(dev); + + stat->private_data = &d->gov_ab; + return 0; +} + +#define PROP_TBL "qcom,bw-tbl" +#define PROP_ACTIVE "qcom,active-only" +#define ACTIVE_ONLY_TAG 0x3 + +int devfreq_add_icc(struct device *dev) +{ + struct dev_data *d; + struct devfreq_dev_profile *p; + u32 *data; + const char *gov_name; + int ret, len, i; + + d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + dev_set_drvdata(dev, d); + + p = &d->dp; + p->polling_ms = 50; + p->target = icc_target; + p->get_dev_status = icc_get_dev_status; + + if (of_find_property(dev->of_node, PROP_TBL, &len)) { + len /= sizeof(*data); + data = devm_kzalloc(dev, len * sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + p->freq_table = devm_kzalloc(dev, + len * sizeof(*p->freq_table), + GFP_KERNEL); + if (!p->freq_table) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, PROP_TBL, + data, len); + if (ret < 0) + return ret; + + for (i = 0; i < len; i++) + p->freq_table[i] = data[i]; + p->max_state = len; + } + + d->icc_path = of_icc_get(dev, NULL); + if (IS_ERR(d->icc_path)) { + ret = PTR_ERR(d->icc_path); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Unable to register icc path: %d\n", ret); + return ret; + } + + if (of_property_read_bool(dev->of_node, PROP_ACTIVE)) + icc_set_tag(d->icc_path, ACTIVE_ONLY_TAG); + + if (of_property_read_string(dev->of_node, "governor", &gov_name)) + gov_name = "performance"; + + d->df = devfreq_add_device(dev, p, gov_name, NULL); + if (IS_ERR(d->df)) { + icc_put(d->icc_path); + return PTR_ERR(d->df); + } + + return 0; +} + +static int devfreq_icc_probe(struct platform_device *pdev) +{ + return devfreq_add_icc(&pdev->dev); +} + +int devfreq_remove_icc(struct device *dev) +{ + struct dev_data *d = dev_get_drvdata(dev); + + icc_put(d->icc_path); + devfreq_remove_device(d->df); + return 0; +} + +static int devfreq_icc_remove(struct platform_device *pdev) +{ + return devfreq_remove_icc(&pdev->dev); +} + +static const struct of_device_id devfreq_icc_match_table[] = { + { .compatible = "qcom,devfreq-icc" }, + {} +}; + +static struct platform_driver devfreq_icc_driver = { + .probe = devfreq_icc_probe, + .remove = devfreq_icc_remove, + .driver = { + .name = "devfreq-icc", + .of_match_table = devfreq_icc_match_table, + }, +}; + +module_platform_driver(devfreq_icc_driver); +MODULE_DESCRIPTION("Device DDR bandwidth voting driver MSM SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/devfreq_icc.h b/include/soc/qcom/devfreq_icc.h new file mode 100644 index 000000000000..f3e830d43b4e --- /dev/null +++ b/include/soc/qcom/devfreq_icc.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014, 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef _DEVFREQ_ICC_H +#define _DEVFREQ_ICC_H + +#include + +#ifdef CONFIG_QCOM_DEVFREQ_ICC +int devfreq_add_icc(struct device *dev); +int devfreq_remove_icc(struct device *dev); +#else +static inline int devfreq_add_icc(struct device *dev) +{ + return 0; +} +static inline int devfreq_remove_icc(struct device *dev) +{ + return 0; +} +#endif + +#endif /* _DEVFREQ_ICC_H */