summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2020-12-11 19:40:00 +0100
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2020-12-11 19:40:00 +0100
commitd3569c149dfe05f1e6e322d4851eed42dbad3df6 (patch)
treee5a2fbd5bc7d08fc39975ba76984a3b2bfccdf62
parent0477e92881850d44910a7e94fc2c46f96faa131f (diff)
parent6a575e84f11e15078629f0d16bff2bc354a6bfc0 (diff)
Merge tag 'devfreq-next-for-5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux
Pull devfreq updates for 5.11 from Chanwoo Choi: 1. Update devfreq core - Add new devfreq_frequency tracepoint to show the frequency change information. - Add governor feature flag. The devfreq governor is able to set the specific flag in order to support a non-common feature. For example, if the governor supports the 'immutable' feature, don't allow user space to change the governor via sysfs. - Add governor sysfs attribute flag for each sysfs file. Prior to that the devfreq subsystem allowed all of the sysfs files to be accessed regardless of the governor type. But some sysfs fils are not supported by specific devfreq governors. In order to only allow the sysfs files supported by the governor to be accessed, clarify the access permissions of sysfs attributes according to the governor. When adding the devfreq governor, specify the available attribute information by using DEVFREQ_GOV_ATTR_* symbols. The user can read or write the sysfs attributes in accordance to the specified access permissions. - Clean-up the code to reduce duplication for the devfreq tracepoint and to remove redundant governor_name field from struct devfreq. 2. Update exynos-bus.c devfreq driver - Add interconnect API support to the Samsung Exynos Bus Frequency driver, exynos-bus.c. Complementing the devfreq driver with interconnect functionality allows to ensure that the QoS requirements regarding devices accessing the system memory (e.g. video processing devices) will be met and allows to avoid issues like DMA underrun. 3. Update tegra devfreq driver - Add interconnect support and OPP interface to tegra30-devfreq.c. Also, it is to guarantee the QoS requirement of some devices like the display controller. - Move tegra20-devfreq.c from drivers/devfreq/ into drivers/memory/tegra/ in order to use the more proper monitoring feature such as EMC_STAT which is located in drivers/memory/tegra/. - Separate the configuration information for different SoCs in tegra30-devfrqe.c. The tegra30-devfreq.c had been supporting both tegra30-actmon and tegra124-actmon devices. In order to use the more correct configuration data, separate them. - Use dev_err_probe() to handle the deferred probe error in tegra30-devfreq.c. 4. Pull the request of 'Tegra SoC and clock controller changes for v5.11' sent by Krzysztof Kozlowski <krzk@kernel.org> in order to avoid a build error." * tag 'devfreq-next-for-5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux: PM / devfreq: tegra30: Separate configurations per-SoC generation PM / devfreq: tegra30: Support interconnect and OPPs from device-tree PM / devfreq: tegra20: Deprecate in a favor of emc-stat based driver PM / devfreq: exynos-bus: Add registration of interconnect child device dt-bindings: devfreq: Add documentation for the interconnect properties soc/tegra: fuse: Add stub for tegra_sku_info soc/tegra: fuse: Export tegra_read_ram_code() clk: tegra: Export Tegra20 EMC kernel symbols PM / devfreq: tegra30: Silence deferred probe error PM / devfreq: tegra20: Relax Kconfig dependency PM / devfreq: tegra20: Silence deferred probe error PM / devfreq: Remove redundant governor_name from struct devfreq PM / devfreq: Add governor attribute flag for specifc sysfs nodes PM / devfreq: Add governor feature flag PM / devfreq: Add tracepoint for frequency changes PM / devfreq: Unify frequency change to devfreq_update_target func trace: events: devfreq: Use fixed indentation size to improve readability
-rw-r--r--Documentation/ABI/testing/sysfs-class-devfreq54
-rw-r--r--Documentation/devicetree/bindings/devfreq/exynos-bus.txt71
-rw-r--r--MAINTAINERS1
-rw-r--r--drivers/clk/tegra/clk-tegra20-emc.c3
-rw-r--r--drivers/devfreq/Kconfig10
-rw-r--r--drivers/devfreq/Makefile1
-rw-r--r--drivers/devfreq/devfreq.c242
-rw-r--r--drivers/devfreq/exynos-bus.c17
-rw-r--r--drivers/devfreq/governor.h33
-rw-r--r--drivers/devfreq/governor_passive.c44
-rw-r--r--drivers/devfreq/governor_simpleondemand.c2
-rw-r--r--drivers/devfreq/tegra20-devfreq.c212
-rw-r--r--drivers/devfreq/tegra30-devfreq.c159
-rw-r--r--drivers/soc/tegra/fuse/tegra-apbmisc.c2
-rw-r--r--include/linux/devfreq.h4
-rw-r--r--include/soc/tegra/fuse.h4
-rw-r--r--include/trace/events/devfreq.h30
17 files changed, 456 insertions, 433 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-devfreq b/Documentation/ABI/testing/sysfs-class-devfreq
index b8ebff4b1c4c..386bc230a33d 100644
--- a/Documentation/ABI/testing/sysfs-class-devfreq
+++ b/Documentation/ABI/testing/sysfs-class-devfreq
@@ -37,20 +37,6 @@ Description:
The /sys/class/devfreq/.../target_freq shows the next governor
predicted target frequency of the corresponding devfreq object.
-What: /sys/class/devfreq/.../polling_interval
-Date: September 2011
-Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
-Description:
- The /sys/class/devfreq/.../polling_interval shows and sets
- the requested polling interval of the corresponding devfreq
- object. The values are represented in ms. If the value is
- less than 1 jiffy, it is considered to be 0, which means
- no polling. This value is meaningless if the governor is
- not polling; thus. If the governor is not using
- devfreq-provided central polling
- (/sys/class/devfreq/.../central_polling is 0), this value
- may be useless.
-
What: /sys/class/devfreq/.../trans_stat
Date: October 2012
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
@@ -66,14 +52,6 @@ Description:
echo 0 > /sys/class/devfreq/.../trans_stat
-What: /sys/class/devfreq/.../userspace/set_freq
-Date: September 2011
-Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
-Description:
- The /sys/class/devfreq/.../userspace/set_freq shows and
- sets the requested frequency for the devfreq object if
- userspace governor is in effect.
-
What: /sys/class/devfreq/.../available_frequencies
Date: October 2012
Contact: Nishanth Menon <nm@ti.com>
@@ -110,6 +88,35 @@ Description:
The max_freq overrides min_freq because max_freq may be
used to throttle devices to avoid overheating.
+What: /sys/class/devfreq/.../polling_interval
+Date: September 2011
+Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
+Description:
+ The /sys/class/devfreq/.../polling_interval shows and sets
+ the requested polling interval of the corresponding devfreq
+ object. The values are represented in ms. If the value is
+ less than 1 jiffy, it is considered to be 0, which means
+ no polling. This value is meaningless if the governor is
+ not polling; thus. If the governor is not using
+ devfreq-provided central polling
+ (/sys/class/devfreq/.../central_polling is 0), this value
+ may be useless.
+
+ A list of governors that support the node:
+ - simple_ondmenad
+ - tegra_actmon
+
+What: /sys/class/devfreq/.../userspace/set_freq
+Date: September 2011
+Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
+Description:
+ The /sys/class/devfreq/.../userspace/set_freq shows and
+ sets the requested frequency for the devfreq object if
+ userspace governor is in effect.
+
+ A list of governors that support the node:
+ - userspace
+
What: /sys/class/devfreq/.../timer
Date: July 2020
Contact: Chanwoo Choi <cw00.choi@samsung.com>
@@ -122,3 +129,6 @@ Description:
echo deferrable > /sys/class/devfreq/.../timer
echo delayed > /sys/class/devfreq/.../timer
+
+ A list of governors that support the node:
+ - simple_ondemand
diff --git a/Documentation/devicetree/bindings/devfreq/exynos-bus.txt b/Documentation/devicetree/bindings/devfreq/exynos-bus.txt
index e71f752cc18f..bcaa2c08ac11 100644
--- a/Documentation/devicetree/bindings/devfreq/exynos-bus.txt
+++ b/Documentation/devicetree/bindings/devfreq/exynos-bus.txt
@@ -51,6 +51,19 @@ Optional properties only for parent bus device:
- exynos,saturation-ratio: the percentage value which is used to calibrate
the performance count against total cycle count.
+Optional properties for the interconnect functionality (QoS frequency
+constraints):
+- #interconnect-cells: should be 0.
+- interconnects: as documented in ../interconnect.txt, describes a path at the
+ higher level interconnects used by this interconnect provider.
+ If this interconnect provider is directly linked to a top level interconnect
+ provider the property contains only one phandle. The provider extends
+ the interconnect graph by linking its node to a node registered by provider
+ pointed to by first phandle in the 'interconnects' property.
+
+- samsung,data-clock-ratio: ratio of the data throughput in B/s to minimum data
+ clock frequency in Hz, default value is 8 when this property is missing.
+
Detailed correlation between sub-blocks and power line according to Exynos SoC:
- In case of Exynos3250, there are two power line as following:
VDD_MIF |--- DMC
@@ -135,7 +148,7 @@ Detailed correlation between sub-blocks and power line according to Exynos SoC:
|--- PERIC (Fixed clock rate)
|--- FSYS (Fixed clock rate)
-Example1:
+Example 1:
Show the AXI buses of Exynos3250 SoC. Exynos3250 divides the buses to
power line (regulator). The MIF (Memory Interface) AXI bus is used to
transfer data between DRAM and CPU and uses the VDD_MIF regulator.
@@ -184,7 +197,7 @@ Example1:
|L5 |200000 |200000 |400000 |300000 | ||1000000 |
----------------------------------------------------------
-Example2 :
+Example 2:
The bus of DMC (Dynamic Memory Controller) block in exynos3250.dtsi
is listed below:
@@ -419,3 +432,57 @@ Example2 :
devfreq = <&bus_leftbus>;
status = "okay";
};
+
+Example 3:
+ An interconnect path "bus_display -- bus_leftbus -- bus_dmc" on
+ Exynos4412 SoC with video mixer as an interconnect consumer device.
+
+ soc {
+ bus_dmc: bus_dmc {
+ compatible = "samsung,exynos-bus";
+ clocks = <&clock CLK_DIV_DMC>;
+ clock-names = "bus";
+ operating-points-v2 = <&bus_dmc_opp_table>;
+ samsung,data-clock-ratio = <4>;
+ #interconnect-cells = <0>;
+ };
+
+ bus_leftbus: bus_leftbus {
+ compatible = "samsung,exynos-bus";
+ clocks = <&clock CLK_DIV_GDL>;
+ clock-names = "bus";
+ operating-points-v2 = <&bus_leftbus_opp_table>;
+ #interconnect-cells = <0>;
+ interconnects = <&bus_dmc>;
+ };
+
+ bus_display: bus_display {
+ compatible = "samsung,exynos-bus";
+ clocks = <&clock CLK_ACLK160>;
+ clock-names = "bus";
+ operating-points-v2 = <&bus_display_opp_table>;
+ #interconnect-cells = <0>;
+ interconnects = <&bus_leftbus &bus_dmc>;
+ };
+
+ bus_dmc_opp_table: opp_table1 {
+ compatible = "operating-points-v2";
+ /* ... */
+ }
+
+ bus_leftbus_opp_table: opp_table3 {
+ compatible = "operating-points-v2";
+ /* ... */
+ };
+
+ bus_display_opp_table: opp_table4 {
+ compatible = "operating-points-v2";
+ /* .. */
+ };
+
+ &mixer {
+ compatible = "samsung,exynos4212-mixer";
+ interconnects = <&bus_display &bus_dmc>;
+ /* ... */
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f474153dbec..d2c06365f3dc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11313,7 +11313,6 @@ L: linux-pm@vger.kernel.org
L: linux-tegra@vger.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux.git
S: Maintained
-F: drivers/devfreq/tegra20-devfreq.c
F: drivers/devfreq/tegra30-devfreq.c
MEMORY MANAGEMENT
diff --git a/drivers/clk/tegra/clk-tegra20-emc.c b/drivers/clk/tegra/clk-tegra20-emc.c
index 03bf0009a33c..dd74b8543bf1 100644
--- a/drivers/clk/tegra/clk-tegra20-emc.c
+++ b/drivers/clk/tegra/clk-tegra20-emc.c
@@ -13,6 +13,7 @@
#include <linux/clk-provider.h>
#include <linux/clk/tegra.h>
#include <linux/err.h>
+#include <linux/export.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -235,6 +236,7 @@ void tegra20_clk_set_emc_round_callback(tegra20_clk_emc_round_cb *round_cb,
emc->cb_arg = cb_arg;
}
}
+EXPORT_SYMBOL_GPL(tegra20_clk_set_emc_round_callback);
bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw)
{
@@ -291,3 +293,4 @@ int tegra20_clk_prepare_emc_mc_same_freq(struct clk *emc_clk, bool same)
return 0;
}
+EXPORT_SYMBOL_GPL(tegra20_clk_prepare_emc_mc_same_freq);
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index 37dc40d1fcfb..00704efe6398 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -121,16 +121,6 @@ config ARM_TEGRA_DEVFREQ
It reads ACTMON counters of memory controllers and adjusts the
operating frequencies and voltages with OPP support.
-config ARM_TEGRA20_DEVFREQ
- tristate "NVIDIA Tegra20 DEVFREQ Driver"
- depends on (TEGRA_MC && TEGRA20_EMC) || COMPILE_TEST
- depends on COMMON_CLK
- select DEVFREQ_GOV_SIMPLE_ONDEMAND
- help
- This adds the DEVFREQ driver for the Tegra20 family of SoCs.
- It reads Memory Controller counters and adjusts the operating
- frequencies and voltages with OPP support.
-
config ARM_RK3399_DMC_DEVFREQ
tristate "ARM RK3399 DMC DEVFREQ Driver"
depends on (ARCH_ROCKCHIP && HAVE_ARM_SMCCC) || \
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 3ca1ad0ecb97..a16333ea7034 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -13,7 +13,6 @@ obj-$(CONFIG_ARM_IMX_BUS_DEVFREQ) += imx-bus.o
obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ) += imx8m-ddrc.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
# DEVFREQ Event Drivers
obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index 861c100f9fac..6aa10de792b3 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -31,6 +31,8 @@
#define CREATE_TRACE_POINTS
#include <trace/events/devfreq.h>
+#define IS_SUPPORTED_FLAG(f, name) ((f & DEVFREQ_GOV_FLAG_##name) ? true : false)
+#define IS_SUPPORTED_ATTR(f, name) ((f & DEVFREQ_GOV_ATTR_##name) ? true : false)
#define HZ_PER_KHZ 1000
static struct class *devfreq_class;
@@ -367,6 +369,14 @@ static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq,
return err;
}
+ /*
+ * Print devfreq_frequency trace information between DEVFREQ_PRECHANGE
+ * and DEVFREQ_POSTCHANGE because for showing the correct frequency
+ * change order of between devfreq device and passive devfreq device.
+ */
+ if (trace_devfreq_frequency_enabled() && new_freq != cur_freq)
+ trace_devfreq_frequency(devfreq, new_freq, cur_freq);
+
freqs.new = new_freq;
devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
@@ -382,18 +392,19 @@ static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq,
return err;
}
-/* Load monitoring helper functions for governors use */
-
/**
- * update_devfreq() - Reevaluate the device and configure frequency.
+ * devfreq_update_target() - Reevaluate the device and configure frequency
+ * on the final stage.
* @devfreq: the devfreq instance.
+ * @freq: the new frequency of parent device. This argument
+ * is only used for devfreq device using passive governor.
*
- * Note: Lock devfreq->lock before calling update_devfreq
- * This function is exported for governors.
+ * Note: Lock devfreq->lock before calling devfreq_update_target. This function
+ * should be only used by both update_devfreq() and devfreq governors.
*/
-int update_devfreq(struct devfreq *devfreq)
+int devfreq_update_target(struct devfreq *devfreq, unsigned long freq)
{
- unsigned long freq, min_freq, max_freq;
+ unsigned long min_freq, max_freq;
int err = 0;
u32 flags = 0;
@@ -418,7 +429,21 @@ int update_devfreq(struct devfreq *devfreq)
}
return devfreq_set_target(devfreq, freq, flags);
+}
+EXPORT_SYMBOL(devfreq_update_target);
+/* Load monitoring helper functions for governors use */
+
+/**
+ * update_devfreq() - Reevaluate the device and configure frequency.
+ * @devfreq: the devfreq instance.
+ *
+ * Note: Lock devfreq->lock before calling update_devfreq
+ * This function is exported for governors.
+ */
+int update_devfreq(struct devfreq *devfreq)
+{
+ return devfreq_update_target(devfreq, 0L);
}
EXPORT_SYMBOL(update_devfreq);
@@ -456,7 +481,7 @@ static void devfreq_monitor(struct work_struct *work)
*/
void devfreq_monitor_start(struct devfreq *devfreq)
{
- if (devfreq->governor->interrupt_driven)
+ if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN))
return;
switch (devfreq->profile->timer) {
@@ -486,7 +511,7 @@ EXPORT_SYMBOL(devfreq_monitor_start);
*/
void devfreq_monitor_stop(struct devfreq *devfreq)
{
- if (devfreq->governor->interrupt_driven)
+ if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN))
return;
cancel_delayed_work_sync(&devfreq->work);
@@ -517,7 +542,7 @@ void devfreq_monitor_suspend(struct devfreq *devfreq)
devfreq->stop_polling = true;
mutex_unlock(&devfreq->lock);
- if (devfreq->governor->interrupt_driven)
+ if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN))
return;
cancel_delayed_work_sync(&devfreq->work);
@@ -537,12 +562,13 @@ void devfreq_monitor_resume(struct devfreq *devfreq)
unsigned long freq;
mutex_lock(&devfreq->lock);
- if (!devfreq->stop_polling)
- goto out;
- if (devfreq->governor->interrupt_driven)
+ if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN))
goto out_update;
+ if (!devfreq->stop_polling)
+ goto out;
+
if (!delayed_work_pending(&devfreq->work) &&
devfreq->profile->polling_ms)
queue_delayed_work(devfreq_wq, &devfreq->work,
@@ -577,10 +603,10 @@ void devfreq_update_interval(struct devfreq *devfreq, unsigned int *delay)
mutex_lock(&devfreq->lock);
devfreq->profile->polling_ms = new_delay;
- if (devfreq->stop_polling)
+ if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN))
goto out;
- if (devfreq->governor->interrupt_driven)
+ if (devfreq->stop_polling)
goto out;
/* if new delay is zero, stop polling */
@@ -735,6 +761,11 @@ static void devfreq_dev_release(struct device *dev)
kfree(devfreq);
}
+static void create_sysfs_files(struct devfreq *devfreq,
+ const struct devfreq_governor *gov);
+static void remove_sysfs_files(struct devfreq *devfreq,
+ const struct devfreq_governor *gov);
+
/**
* devfreq_add_device() - Add devfreq feature to the device
* @dev: the device to add devfreq feature.
@@ -780,7 +811,6 @@ struct devfreq *devfreq_add_device(struct device *dev,
devfreq->dev.release = devfreq_dev_release;
INIT_LIST_HEAD(&devfreq->node);
devfreq->profile = profile;
- strscpy(devfreq->governor_name, governor_name, DEVFREQ_NAME_LEN);
devfreq->previous_freq = profile->initial_freq;
devfreq->last_status.current_frequency = profile->initial_freq;
devfreq->data = data;
@@ -876,7 +906,7 @@ struct devfreq *devfreq_add_device(struct device *dev,
mutex_lock(&devfreq_list_lock);
- governor = try_then_request_governor(devfreq->governor_name);
+ governor = try_then_request_governor(governor_name);
if (IS_ERR(governor)) {
dev_err(dev, "%s: Unable to find governor for the device\n",
__func__);
@@ -892,6 +922,7 @@ struct devfreq *devfreq_add_device(struct device *dev,
__func__);
goto err_init;
}
+ create_sysfs_files(devfreq, devfreq->governor);
list_add(&devfreq->node, &devfreq_list);
@@ -922,9 +953,12 @@ int devfreq_remove_device(struct devfreq *devfreq)
if (!devfreq)
return -EINVAL;
- if (devfreq->governor)
+ if (devfreq->governor) {
devfreq->governor->event_handler(devfreq,
DEVFREQ_GOV_STOP, NULL);
+ remove_sysfs_files(devfreq, devfreq->governor);
+ }
+
device_unregister(&devfreq->dev);
return 0;
@@ -1214,7 +1248,7 @@ int devfreq_add_governor(struct devfreq_governor *governor)
int ret = 0;
struct device *dev = devfreq->dev.parent;
- if (!strncmp(devfreq->governor_name, governor->name,
+ if (!strncmp(devfreq->governor->name, governor->name,
DEVFREQ_NAME_LEN)) {
/* The following should never occur */
if (devfreq->governor) {
@@ -1276,7 +1310,7 @@ int devfreq_remove_governor(struct devfreq_governor *governor)
int ret;
struct device *dev = devfreq->dev.parent;
- if (!strncmp(devfreq->governor_name, governor->name,
+ if (!strncmp(devfreq->governor->name, governor->name,
DEVFREQ_NAME_LEN)) {
/* we should have a devfreq governor! */
if (!devfreq->governor) {
@@ -1347,36 +1381,53 @@ static ssize_t governor_store(struct device *dev, struct device_attribute *attr,
if (df->governor == governor) {
ret = 0;
goto out;
- } else if (df->governor->immutable || governor->immutable) {
+ } else if (IS_SUPPORTED_FLAG(df->governor->flags, IMMUTABLE)
+ || IS_SUPPORTED_FLAG(governor->flags, IMMUTABLE)) {
ret = -EINVAL;
goto out;
}
+ /*
+ * Stop the current governor and remove the specific sysfs files
+ * which depend on current governor.
+ */
ret = df->governor->event_handler(df, DEVFREQ_GOV_STOP, NULL);
if (ret) {
dev_warn(dev, "%s: Governor %s not stopped(%d)\n",
__func__, df->governor->name, ret);
goto out;
}
+ remove_sysfs_files(df, df->governor);
+ /*
+ * Start the new governor and create the specific sysfs files
+ * which depend on the new governor.
+ */
prev_governor = df->governor;
df->governor = governor;
- strncpy(df->governor_name, governor->name, DEVFREQ_NAME_LEN);
ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL);
if (ret) {
dev_warn(dev, "%s: Governor %s not started(%d)\n",
__func__, df->governor->name, ret);
+
+ /* Restore previous governor */
df->governor = prev_governor;
- strncpy(df->governor_name, prev_governor->name,
- DEVFREQ_NAME_LEN);
ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL);
if (ret) {
dev_err(dev,
"%s: reverting to Governor %s failed (%d)\n",
- __func__, df->governor_name, ret);
+ __func__, prev_governor->name, ret);
df->governor = NULL;
+ goto out;
}
}
+
+ /*
+ * Create the sysfs files for the new governor. But if failed to start
+ * the new governor, restore the sysfs files of previous governor.
+ */
+ create_sysfs_files(df, df->governor);
+
out:
mutex_unlock(&devfreq_list_lock);
@@ -1402,9 +1453,9 @@ static ssize_t available_governors_show(struct device *d,
* The devfreq with immutable governor (e.g., passive) shows
* only own governor.
*/
- if (df->governor->immutable) {
+ if (IS_SUPPORTED_FLAG(df->governor->flags, IMMUTABLE)) {
count = scnprintf(&buf[count], DEVFREQ_NAME_LEN,
- "%s ", df->governor_name);
+ "%s ", df->governor->name);
/*
* The devfreq device shows the registered governor except for
* immutable governors such as passive governor .
@@ -1413,7 +1464,7 @@ static ssize_t available_governors_show(struct device *d,
struct devfreq_governor *governor;
list_for_each_entry(governor, &devfreq_governor_list, node) {
- if (governor->immutable)
+ if (IS_SUPPORTED_FLAG(governor->flags, IMMUTABLE))
continue;
count += scnprintf(&buf[count], (PAGE_SIZE - count - 2),
"%s ", governor->name);
@@ -1458,39 +1509,6 @@ static ssize_t target_freq_show(struct device *dev,
}
static DEVICE_ATTR_RO(target_freq);
-static ssize_t polling_interval_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct devfreq *df = to_devfreq(dev);
-
- if (!df->profile)
- return -EINVAL;
-
- return sprintf(buf, "%d\n", df->profile->polling_ms);
-}
-
-static ssize_t polling_interval_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct devfreq *df = to_devfreq(dev);
- unsigned int value;
- int ret;
-
- if (!df->governor)
- return -EINVAL;
-
- ret = sscanf(buf, "%u", &value);
- if (ret != 1)
- return -EINVAL;
-
- df->governor->event_handler(df, DEVFREQ_GOV_UPDATE_INTERVAL, &value);
- ret = count;
-
- return ret;
-}
-static DEVICE_ATTR_RW(polling_interval);
-
static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -1698,6 +1716,53 @@ static ssize_t trans_stat_store(struct device *dev,
}
static DEVICE_ATTR_RW(trans_stat);
+static struct attribute *devfreq_attrs[] = {
+ &dev_attr_name.attr,
+ &dev_attr_governor.attr,
+ &dev_attr_available_governors.attr,
+ &dev_attr_cur_freq.attr,
+ &dev_attr_available_frequencies.attr,
+ &dev_attr_target_freq.attr,
+ &dev_attr_min_freq.attr,
+ &dev_attr_max_freq.attr,
+ &dev_attr_trans_stat.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(devfreq);
+
+static ssize_t polling_interval_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct devfreq *df = to_devfreq(dev);
+
+ if (!df->profile)
+ return -EINVAL;
+
+ return sprintf(buf, "%d\n", df->profile->polling_ms);
+}
+
+static ssize_t polling_interval_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct devfreq *df = to_devfreq(dev);
+ unsigned int value;
+ int ret;
+
+ if (!df->governor)
+ return -EINVAL;
+
+ ret = sscanf(buf, "%u", &value);
+ if (ret != 1)
+ return -EINVAL;
+
+ df->governor->event_handler(df, DEVFREQ_GOV_UPDATE_INTERVAL, &value);
+ ret = count;
+
+ return ret;
+}
+static DEVICE_ATTR_RW(polling_interval);
+
static ssize_t timer_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -1761,21 +1826,36 @@ out:
}
static DEVICE_ATTR_RW(timer);
-static struct attribute *devfreq_attrs[] = {
- &dev_attr_name.attr,
- &dev_attr_governor.attr,
- &dev_attr_available_governors.attr,
- &dev_attr_cur_freq.attr,
- &dev_attr_available_frequencies.attr,
- &dev_attr_target_freq.attr,
- &dev_attr_polling_interval.attr,
- &dev_attr_min_freq.attr,
- &dev_attr_max_freq.attr,
- &dev_attr_trans_stat.attr,
- &dev_attr_timer.attr,
- NULL,
-};
-ATTRIBUTE_GROUPS(devfreq);
+#define CREATE_SYSFS_FILE(df, name) \
+{ \
+ int ret; \
+ ret = sysfs_create_file(&df->dev.kobj, &dev_attr_##name.attr); \
+ if (ret < 0) { \
+ dev_warn(&df->dev, \
+ "Unable to create attr(%s)\n", "##name"); \
+ } \
+} \
+
+/* Create the specific sysfs files which depend on each governor. */
+static void create_sysfs_files(struct devfreq *devfreq,
+ const struct devfreq_governor *gov)
+{
+ if (IS_SUPPORTED_ATTR(gov->attrs, POLLING_INTERVAL))
+ CREATE_SYSFS_FILE(devfreq, polling_interval);
+ if (IS_SUPPORTED_ATTR(gov->attrs, TIMER))
+ CREATE_SYSFS_FILE(devfreq, timer);
+}
+
+/* Remove the specific sysfs files which depend on each governor. */
+static void remove_sysfs_files(struct devfreq *devfreq,
+ const struct devfreq_governor *gov)
+{
+ if (IS_SUPPORTED_ATTR(gov->attrs, POLLING_INTERVAL))
+ sysfs_remove_file(&devfreq->dev.kobj,
+ &dev_attr_polling_interval.attr);
+ if (IS_SUPPORTED_ATTR(gov->attrs, TIMER))
+ sysfs_remove_file(&devfreq->dev.kobj, &dev_attr_timer.attr);
+}
/**
* devfreq_summary_show() - Show the summary of the devfreq devices
@@ -1818,7 +1898,7 @@ static int devfreq_summary_show(struct seq_file *s, void *data)
list_for_each_entry_reverse(devfreq, &devfreq_list, node) {
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE)
- if (!strncmp(devfreq->governor_name, DEVFREQ_GOV_PASSIVE,
+ if (!strncmp(devfreq->governor->name, DEVFREQ_GOV_PASSIVE,
DEVFREQ_NAME_LEN)) {
struct devfreq_passive_data *data = devfreq->data;
@@ -1832,15 +1912,19 @@ static int devfreq_summary_show(struct seq_file *s, void *data)
mutex_lock(&devfreq->lock);
cur_freq = devfreq->previous_freq;
get_freq_range(devfreq, &min_freq, &max_freq);
- polling_ms = devfreq->profile->polling_ms;
timer = devfreq->profile->timer;
+
+ if (IS_SUPPORTED_ATTR(devfreq->governor->attrs, POLLING_INTERVAL))
+ polling_ms = devfreq->profile->polling_ms;
+ else
+ polling_ms = 0;
mutex_unlock(&devfreq->lock);
seq_printf(s,
"%-30s %-30s %-15s %-10s %10d %12ld %12ld %12ld\n",
dev_name(&devfreq->dev),
p_devfreq ? dev_name(&p_devfreq->dev) : "null",
- devfreq->governor_name,
+ devfreq->governor->name,
polling_ms ? timer_name[timer] : "null",
polling_ms,
cur_freq,
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index 1e684a448c9e..20447a401af4 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -24,6 +24,7 @@
struct exynos_bus {
struct device *dev;
+ struct platform_device *icc_pdev;
struct devfreq *devfreq;
struct devfreq_event_dev **edev;
@@ -156,6 +157,8 @@ static void exynos_bus_exit(struct device *dev)
if (ret < 0)
dev_warn(dev, "failed to disable the devfreq-event devices\n");
+ platform_device_unregister(bus->icc_pdev);
+
dev_pm_opp_of_remove_table(dev);
clk_disable_unprepare(bus->clk);
if (bus->opp_table) {
@@ -168,6 +171,8 @@ static void exynos_bus_passive_exit(struct device *dev)
{
struct exynos_bus *bus = dev_get_drvdata(dev);
+ platform_device_unregister(bus->icc_pdev);
+
dev_pm_opp_of_remove_table(dev);
clk_disable_unprepare(bus->clk);
}
@@ -432,6 +437,18 @@ static int exynos_bus_probe(struct platform_device *pdev)
if (ret < 0)
goto err;
+ /* Create child platform device for the interconnect provider */
+ if (of_get_property(dev->of_node, "#interconnect-cells", NULL)) {
+ bus->icc_pdev = platform_device_register_data(
+ dev, "exynos-generic-icc",
+ PLATFORM_DEVID_AUTO, NULL, 0);
+
+ if (IS_ERR(bus->icc_pdev)) {
+ ret = PTR_ERR(bus->icc_pdev);
+ goto err;
+ }
+ }
+
max_state = bus->devfreq->profile->max_state;
min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h
index ae4d0cc18359..2a52f97b542d 100644
--- a/drivers/devfreq/governor.h
+++ b/drivers/devfreq/governor.h
@@ -13,6 +13,8 @@
#include <linux/devfreq.h>
+#define DEVFREQ_NAME_LEN 16
+
#define to_devfreq(DEV) container_of((DEV), struct devfreq, dev)
/* Devfreq events */
@@ -25,14 +27,32 @@
#define DEVFREQ_MIN_FREQ 0
#define DEVFREQ_MAX_FREQ ULONG_MAX
+/*
+ * Definition of the governor feature flags
+ * - DEVFREQ_GOV_FLAG_IMMUTABLE
+ * : This governor is never changeable to other governors.
+ * - DEVFREQ_GOV_FLAG_IRQ_DRIVEN
+ * : The devfreq won't schedule the work for this governor.
+ */
+#define DEVFREQ_GOV_FLAG_IMMUTABLE BIT(0)
+#define DEVFREQ_GOV_FLAG_IRQ_DRIVEN BIT(1)
+
+/*
+ * Definition of governor attribute flags except for common sysfs attributes
+ * - DEVFREQ_GOV_ATTR_POLLING_INTERVAL
+ * : Indicate polling_interal sysfs attribute
+ * - DEVFREQ_GOV_ATTR_TIMER
+ * : Indicate timer sysfs attribute
+ */
+#define DEVFREQ_GOV_ATTR_POLLING_INTERVAL BIT(0)
+#define DEVFREQ_GOV_ATTR_TIMER BIT(1)
+
/**
* struct devfreq_governor - Devfreq policy governor
* @node: list node - contains registered devfreq governors
* @name: Governor's name
- * @immutable: Immutable flag for governor. If the value is 1,
- * this governor is never changeable to other governor.
- * @interrupt_driven: Devfreq core won't schedule polling work for this
- * governor if value is set to 1.
+ * @attrs: Governor's sysfs attribute flags
+ * @flags: Governor's feature flags
* @get_target_freq: Returns desired operating frequency for the device.
* Basically, get_target_freq will run
* devfreq_dev_profile.get_dev_status() to get the
@@ -50,8 +70,8 @@ struct devfreq_governor {
struct list_head node;
const char name[DEVFREQ_NAME_LEN];
- const unsigned int immutable;
- const unsigned int interrupt_driven;
+ const u64 attrs;
+ const u64 flags;
int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
int (*event_handler)(struct devfreq *devfreq,
unsigned int event, void *data);
@@ -67,6 +87,7 @@ int devfreq_add_governor(struct devfreq_governor *governor);
int devfreq_remove_governor(struct devfreq_governor *governor);
int devfreq_update_status(struct devfreq *devfreq, unsigned long freq);
+int devfreq_update_target(struct devfreq *devfreq, unsigned long freq);
static inline int devfreq_update_stats(struct devfreq *df)
{
diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c
index be6eeab9c814..63332e4a65ae 100644
--- a/drivers/devfreq/governor_passive.c
+++ b/drivers/devfreq/governor_passive.c
@@ -92,36 +92,6 @@ out:
return ret;
}
-static int update_devfreq_passive(struct devfreq *devfreq, unsigned long freq)
-{
- int ret;
-
- if (!devfreq->governor)
- return -EINVAL;
-
- mutex_lock_nested(&devfreq->lock, SINGLE_DEPTH_NESTING);
-
- ret = devfreq->governor->get_target_freq(devfreq, &freq);
- if (ret < 0)
- goto out;
-
- ret = devfreq->profile->target(devfreq->dev.parent, &freq, 0);
- if (ret < 0)
- goto out;
-
- if (devfreq->profile->freq_table
- && (devfreq_update_status(devfreq, freq)))
- dev_err(&devfreq->dev,
- "Couldn't update frequency transition information.\n");
-
- devfreq->previous_freq = freq;
-
-out:
- mutex_unlock(&devfreq->lock);
-
- return 0;
-}
-
static int devfreq_passive_notifier_call(struct notifier_block *nb,
unsigned long event, void *ptr)
{
@@ -131,17 +101,25 @@ static int devfreq_passive_notifier_call(struct notifier_block *nb,
struct devfreq *parent = (struct devfreq *)data->parent;
struct devfreq_freqs *freqs = (struct devfreq_freqs *)ptr;
unsigned long freq = freqs->new;
+ int ret = 0;
+ mutex_lock_nested(&devfreq->lock, SINGLE_DEPTH_NESTING);
switch (event) {
case DEVFREQ_PRECHANGE:
if (parent->previous_freq > freq)
- update_devfreq_passive(devfreq, freq);
+ ret = devfreq_update_target(devfreq, freq);
+
break;
case DEVFREQ_POSTCHANGE:
if (parent->previous_freq < freq)
- update_devfreq_passive(devfreq, freq);
+ ret = devfreq_update_target(devfreq, freq);
break;
}
+ mutex_unlock(&devfreq->lock);
+
+ if (ret < 0)
+ dev_warn(&devfreq->dev,
+ "failed to update devfreq using passive governor\n");
return NOTIFY_DONE;
}
@@ -180,7 +158,7 @@ static int devfreq_passive_event_handler(struct devfreq *devfreq,
static struct devfreq_governor devfreq_passive = {
.name = DEVFREQ_GOV_PASSIVE,
- .immutable = 1,
+ .flags = DEVFREQ_GOV_FLAG_IMMUTABLE,
.get_target_freq = devfreq_passive_get_target_freq,
.event_handler = devfreq_passive_event_handler,
};
diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c
index 1b314e1df028..d57b82a2b570 100644
--- a/drivers/devfreq/governor_simpleondemand.c
+++ b/drivers/devfreq/governor_simpleondemand.c
@@ -117,6 +117,8 @@ static int devfreq_simple_ondemand_handler(struct devfreq *devfreq,
static struct devfreq_governor devfreq_simple_ondemand = {
.name = DEVFREQ_GOV_SIMPLE_ONDEMAND,
+ .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL
+ | DEVFREQ_GOV_ATTR_TIMER,
.get_target_freq = devfreq_simple_ondemand_func,
.event_handler = devfreq_simple_ondemand_handler,
};
diff --git a/drivers/devfreq/tegra20-devfreq.c b/drivers/devfreq/tegra20-devfreq.c
deleted file mode 100644
index ff82bac9ee4e..000000000000
--- a/drivers/devfreq/tegra20-devfreq.c
+++ /dev/null
@@ -1,212 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * NVIDIA Tegra20 devfreq driver
- *
- * Copyright (C) 2019 GRATE-DRIVER project
- */
-
-#include <linux/clk.h>
-#include <linux/devfreq.h>
-#include <linux/io.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/of_device.h>
-#include <linux/platform_device.h>
-#include <linux/pm_opp.h>
-#include <linux/slab.h>
-
-#include <soc/tegra/mc.h>
-
-#include "governor.h"
-
-#define MC_STAT_CONTROL 0x90
-#define MC_STAT_EMC_CLOCK_LIMIT 0xa0
-#define MC_STAT_EMC_CLOCKS 0xa4
-#define MC_STAT_EMC_CONTROL 0xa8
-#define MC_STAT_EMC_COUNT 0xb8
-
-#define EMC_GATHER_CLEAR (1 << 8)
-#define EMC_GATHER_ENABLE (3 << 8)
-
-struct tegra_devfreq {
- struct devfreq *devfreq;
- struct clk *emc_clock;
- void __iomem *regs;
-};
-
-static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
- u32 flags)
-{
- struct tegra_devfreq *tegra = dev_get_drvdata(dev);
- struct devfreq *devfreq = tegra->devfreq;
- struct dev_pm_opp *opp;
- unsigned long rate;
- int err;
-
- opp = devfreq_recommended_opp(dev, freq, flags);
- if (IS_ERR(opp))
- return PTR_ERR(opp);
-
- rate = dev_pm_opp_get_freq(opp);
- dev_pm_opp_put(opp);
-
- err = clk_set_min_rate(tegra->emc_clock, rate);
- if (err)
- return err;
-
- err = clk_set_rate(tegra->emc_clock, 0);
- if (err)
- goto restore_min_rate;
-
- return 0;
-
-restore_min_rate:
- clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq);
-
- return err;
-}
-
-static int tegra_devfreq_get_dev_status(struct device *dev,
- struct devfreq_dev_status *stat)
-{
- struct tegra_devfreq *tegra = dev_get_drvdata(dev);
-
- /*
- * EMC_COUNT returns number of memory events, that number is lower
- * than the number of clocks. Conversion ratio of 1/8 results in a
- * bit higher bandwidth than actually needed, it is good enough for
- * the time being because drivers don't support requesting minimum
- * needed memory bandwidth yet.
- *
- * TODO: adjust the ratio value once relevant drivers will support
- * memory bandwidth management.
- */
- stat->busy_time = readl_relaxed(tegra->regs + MC_STAT_EMC_COUNT);
- stat->total_time = readl_relaxed(tegra->regs + MC_STAT_EMC_CLOCKS) / 8;
- stat->current_frequency = clk_get_rate(tegra->emc_clock);
-
- writel_relaxed(EMC_GATHER_CLEAR, tegra->regs + MC_STAT_CONTROL);
- writel_relaxed(EMC_GATHER_ENABLE, tegra->regs + MC_STAT_CONTROL);
-
- return 0;
-}
-
-static struct devfreq_dev_profile tegra_devfreq_profile = {
- .polling_ms = 500,
- .target = tegra_devfreq_target,
- .get_dev_status = tegra_devfreq_get_dev_status,
-};
-
-static struct tegra_mc *tegra_get_memory_controller(void)
-{
- struct platform_device *pdev;
- struct device_node *np;
- struct tegra_mc *mc;
-
- np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-mc-gart");
- if (!np)
- return ERR_PTR(-ENOENT);
-
- pdev = of_find_device_by_node(np);
- of_node_put(np);
- if (!pdev)
- return ERR_PTR(-ENODEV);
-
- mc = platform_get_drvdata(pdev);
- if (!mc)
- return ERR_PTR(-EPROBE_DEFER);
-
- return mc;
-}
-
-static int tegra_devfreq_probe(struct platform_device *pdev)
-{
- struct tegra_devfreq *tegra;
- struct tegra_mc *mc;
- unsigned long max_rate;
- unsigned long rate;
- int err;
-
- mc = tegra_get_memory_controller();
- if (IS_ERR(mc)) {
- err = PTR_ERR(mc);
- dev_err(&pdev->dev, "failed to get memory controller: %d\n",
- err);
- return err;
- }
-
- tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
- if (!tegra)
- return -ENOMEM;
-
- /* EMC is a system-critical clock that is always enabled */
- tegra->emc_clock = devm_clk_get(&pdev->dev, "emc");
- if (IS_ERR(tegra->emc_clock)) {
- err = PTR_ERR(tegra->emc_clock);
- dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
- return err;
- }
-
- tegra->regs = mc->regs;
-
- max_rate = clk_round_rate(tegra->emc_clock, ULONG_MAX);
-
- for (rate = 0; rate <= max_rate; rate++) {
- rate = clk_round_rate(tegra->emc_clock, rate);
-
- err = dev_pm_opp_add(&pdev->dev, rate, 0);
- if (err) {
- dev_err(&pdev->dev, "failed to add opp: %d\n", err);
- goto remove_opps;
- }
- }
-
- /*
- * Reset statistic gathers state, select global bandwidth for the
- * statistics collection mode and set clocks counter saturation
- * limit to maximum.
- */
- writel_relaxed(0x00000000, tegra->regs + MC_STAT_CONTROL);
- writel_relaxed(0x00000000, tegra->regs + MC_STAT_EMC_CONTROL);
- writel_relaxed(0xffffffff, tegra->regs + MC_STAT_EMC_CLOCK_LIMIT);
-
- platform_set_drvdata(pdev, tegra);
-
- tegra->devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile,
- DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL);
- if (IS_ERR(tegra->devfreq)) {
- err = PTR_ERR(tegra->devfreq);
- goto remove_opps;
- }
-
- return 0;
-
-remove_opps:
- dev_pm_opp_remove_all_dynamic(&pdev->dev);
-
- return err;
-}
-
-static int tegra_devfreq_remove(struct platform_device *pdev)
-{
- struct tegra_devfreq *tegra = platform_get_drvdata(pdev);
-
- devfreq_remove_device(tegra->devfreq);
- dev_pm_opp_remove_all_dynamic(&pdev->dev);
-
- return 0;
-}
-
-static struct platform_driver tegra_devfreq_driver = {
- .probe = tegra_devfreq_probe,
- .remove = tegra_devfreq_remove,
- .driver = {
- .name = "tegra20-devfreq",
- },
-};
-module_platform_driver(tegra_devfreq_driver);
-
-MODULE_ALIAS("platform:tegra20-devfreq");
-MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
-MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c
index f5e74c2ede85..117cad7968ab 100644
--- a/drivers/devfreq/tegra30-devfreq.c
+++ b/drivers/devfreq/tegra30-devfreq.c
@@ -19,6 +19,8 @@
#include <linux/reset.h>
#include <linux/workqueue.h>
+#include <soc/tegra/fuse.h>
+
#include "governor.h"
#define ACTMON_GLB_STATUS 0x0
@@ -56,13 +58,6 @@
#define ACTMON_BOOST_FREQ_STEP 16000
/*
- * Activity counter is incremented every 256 memory transactions, and each
- * transaction takes 4 EMC clocks for Tegra124; So the COUNT_WEIGHT is
- * 4 * 256 = 1024.
- */
-#define ACTMON_COUNT_WEIGHT 0x400
-
-/*
* ACTMON_AVERAGE_WINDOW_LOG2: default value for @DEV_CTRL_K_VAL, which
* translates to 2 ^ (K_VAL + 1). ex: 2 ^ (6 + 1) = 128
*/
@@ -109,7 +104,7 @@ enum tegra_actmon_device {
MCCPU,
};
-static const struct tegra_devfreq_device_config actmon_device_configs[] = {
+static const struct tegra_devfreq_device_config tegra124_device_configs[] = {
{
/* MCALL: All memory accesses (including from the CPUs) */
.offset = 0x1c0,
@@ -131,6 +126,28 @@ static const struct tegra_devfreq_device_config actmon_device_configs[] = {
},
};
+static const struct tegra_devfreq_device_config tegra30_device_configs[] = {
+ {
+ /* MCALL: All memory accesses (including from the CPUs) */
+ .offset = 0x1c0,
+ .irq_mask = 1 << 26,
+ .boost_up_coeff = 200,
+ .boost_down_coeff = 50,
+ .boost_up_threshold = 20,
+ .boost_down_threshold = 10,
+ },
+ {
+ /* MCCPU: memory accesses from the CPUs */
+ .offset = 0x200,
+ .irq_mask = 1 << 25,
+ .boost_up_coeff = 800,
+ .boost_down_coeff = 40,
+ .boost_up_threshold = 27,
+ .boost_down_threshold = 10,
+ .avg_dependency_threshold = 16000, /* 16MHz in kHz units */
+ },
+};
+
/**
* struct tegra_devfreq_device - state specific to an ACTMON device
*
@@ -153,8 +170,15 @@ struct tegra_devfreq_device {
unsigned long target_freq;
};
+struct tegra_devfreq_soc_data {
+ const struct tegra_devfreq_device_config *configs;
+ /* Weight value for count measurements */
+ unsigned int count_weight;
+};
+
struct tegra_devfreq {
struct devfreq *devfreq;
+ struct opp_table *opp_table;
struct reset_control *reset;
struct clk *clock;
@@ -168,11 +192,13 @@ struct tegra_devfreq {
struct delayed_work cpufreq_update_work;
struct notifier_block cpu_rate_change_nb;
- struct tegra_devfreq_device devices[ARRAY_SIZE(actmon_device_configs)];
+ struct tegra_devfreq_device devices[2];
unsigned int irq;
bool started;
+
+ const struct tegra_devfreq_soc_data *soc;
};
struct tegra_actmon_emc_ratio {
@@ -485,7 +511,7 @@ static void tegra_actmon_configure_device(struct tegra_devfreq *tegra,
tegra_devfreq_update_avg_wmark(tegra, dev);
tegra_devfreq_update_wmark(tegra, dev);
- device_writel(dev, ACTMON_COUNT_WEIGHT, ACTMON_DEV_COUNT_WEIGHT);
+ device_writel(dev, tegra->soc->count_weight, ACTMON_DEV_COUNT_WEIGHT);
device_writel(dev, ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS);
val |= ACTMON_DEV_CTRL_ENB_PERIODIC;
@@ -612,34 +638,19 @@ static void tegra_actmon_stop(struct tegra_devfreq *tegra)
static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
- struct tegra_devfreq *tegra = dev_get_drvdata(dev);
- struct devfreq *devfreq = tegra->devfreq;
struct dev_pm_opp *opp;
- unsigned long rate;
- int err;
+ int ret;
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp)) {
dev_err(dev, "Failed to find opp for %lu Hz\n", *freq);
return PTR_ERR(opp);
}
- rate = dev_pm_opp_get_freq(opp);
- dev_pm_opp_put(opp);
-
- err = clk_set_min_rate(tegra->emc_clock, rate * KHZ);
- if (err)
- return err;
-
- err = clk_set_rate(tegra->emc_clock, 0);
- if (err)
- goto restore_min_rate;
- return 0;
-
-restore_min_rate:
- clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq);
+ ret = dev_pm_opp_set_bw(dev, opp);
+ dev_pm_opp_put(opp);
- return err;
+ return ret;
}
static int tegra_devfreq_get_dev_status(struct device *dev,
@@ -655,7 +666,7 @@ static int tegra_devfreq_get_dev_status(struct device *dev,
stat->private_data = tegra;
/* The below are to be used by the other governors */
- stat->current_frequency = cur_freq;
+ stat->current_frequency = cur_freq * KHZ;
actmon_dev = &tegra->devices[MCALL];
@@ -705,7 +716,12 @@ static int tegra_governor_get_target(struct devfreq *devfreq,
target_freq = max(target_freq, dev->target_freq);
}
- *freq = target_freq;
+ /*
+ * tegra-devfreq driver operates with KHz units, while OPP table
+ * entries use Hz units. Hence we need to convert the units for the
+ * devfreq core.
+ */
+ *freq = target_freq * KHZ;
return 0;
}
@@ -765,14 +781,16 @@ static int tegra_governor_event_handler(struct devfreq *devfreq,
static struct devfreq_governor tegra_devfreq_governor = {
.name = "tegra_actmon",
+ .attrs = DEVFREQ_GOV_ATTR_POLLING_INTERVAL,
+ .flags = DEVFREQ_GOV_FLAG_IMMUTABLE
+ | DEVFREQ_GOV_FLAG_IRQ_DRIVEN,
.get_target_freq = tegra_governor_get_target,
.event_handler = tegra_governor_event_handler,
- .immutable = true,
- .interrupt_driven = true,
};
static int tegra_devfreq_probe(struct platform_device *pdev)
{
+ u32 hw_version = BIT(tegra_sku_info.soc_speedo_id);
struct tegra_devfreq_device *dev;
struct tegra_devfreq *tegra;
struct devfreq *devfreq;
@@ -784,6 +802,8 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
if (!tegra)
return -ENOMEM;
+ tegra->soc = of_device_get_match_data(&pdev->dev);
+
tegra->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(tegra->regs))
return PTR_ERR(tegra->regs);
@@ -801,10 +821,9 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
}
tegra->emc_clock = devm_clk_get(&pdev->dev, "emc");
- if (IS_ERR(tegra->emc_clock)) {
- dev_err(&pdev->dev, "Failed to get emc clock\n");
- return PTR_ERR(tegra->emc_clock);
- }
+ if (IS_ERR(tegra->emc_clock))
+ return dev_err_probe(&pdev->dev, PTR_ERR(tegra->emc_clock),
+ "Failed to get emc clock\n");
err = platform_get_irq(pdev, 0);
if (err < 0)
@@ -822,11 +841,25 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
return err;
}
+ tegra->opp_table = dev_pm_opp_set_supported_hw(&pdev->dev,
+ &hw_version, 1);
+ err = PTR_ERR_OR_ZERO(tegra->opp_table);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to set supported HW: %d\n", err);
+ return err;
+ }
+
+ err = dev_pm_opp_of_add_table(&pdev->dev);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to add OPP table: %d\n", err);
+ goto put_hw;
+ }
+
err = clk_prepare_enable(tegra->clock);
if (err) {
dev_err(&pdev->dev,
"Failed to prepare and enable ACTMON clock\n");
- return err;
+ goto remove_table;
}
err = reset_control_reset(tegra->reset);
@@ -844,29 +877,12 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
tegra->max_freq = rate / KHZ;
- for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) {
+ for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
dev = tegra->devices + i;
- dev->config = actmon_device_configs + i;
+ dev->config = tegra->soc->configs + i;
dev->regs = tegra->regs + dev->config->offset;
}
- for (rate = 0; rate <= tegra->max_freq * KHZ; rate++) {
- rate = clk_round_rate(tegra->emc_clock, rate);
-
- if (rate < 0) {
- dev_err(&pdev->dev,
- "Failed to round clock rate: %ld\n", rate);
- err = rate;
- goto remove_opps;
- }
-
- err = dev_pm_opp_add(&pdev->dev, rate / KHZ, 0);
- if (err) {
- dev_err(&pdev->dev, "Failed to add OPP: %d\n", err);
- goto remove_opps;
- }
- }
-
platform_set_drvdata(pdev, tegra);
tegra->clk_rate_change_nb.notifier_call = tegra_actmon_clk_notify_cb;
@@ -882,7 +898,6 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
}
tegra_devfreq_profile.initial_freq = clk_get_rate(tegra->emc_clock);
- tegra_devfreq_profile.initial_freq /= KHZ;
devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile,
"tegra_actmon", NULL);
@@ -902,6 +917,10 @@ remove_opps:
reset_control_reset(tegra->reset);
disable_clk:
clk_disable_unprepare(tegra->clock);
+remove_table:
+ dev_pm_opp_of_remove_table(&pdev->dev);
+put_hw:
+ dev_pm_opp_put_supported_hw(tegra->opp_table);
return err;
}
@@ -913,17 +932,33 @@ static int tegra_devfreq_remove(struct platform_device *pdev)
devfreq_remove_device(tegra->devfreq);
devfreq_remove_governor(&tegra_devfreq_governor);
- dev_pm_opp_remove_all_dynamic(&pdev->dev);
-
reset_control_reset(tegra->reset);
clk_disable_unprepare(tegra->clock);
+ dev_pm_opp_of_remove_table(&pdev->dev);
+ dev_pm_opp_put_supported_hw(tegra->opp_table);
+
return 0;
}
+static const struct tegra_devfreq_soc_data tegra124_soc = {
+ .configs = tegra124_device_configs,
+
+ /*
+ * Activity counter is incremented every 256 memory transactions,
+ * and each transaction takes 4 EMC clocks.
+ */
+ .count_weight = 4 * 256,
+};
+
+static const struct tegra_devfreq_soc_data tegra30_soc = {
+ .configs = tegra30_device_configs,
+ .count_weight = 2 * 256,
+};
+
static const struct of_device_id tegra_devfreq_of_match[] = {
- { .compatible = "nvidia,tegra30-actmon" },
- { .compatible = "nvidia,tegra124-actmon" },
+ { .compatible = "nvidia,tegra30-actmon", .data = &tegra30_soc, },
+ { .compatible = "nvidia,tegra124-actmon", .data = &tegra124_soc, },
{ },
};
diff --git a/drivers/soc/tegra/fuse/tegra-apbmisc.c b/drivers/soc/tegra/fuse/tegra-apbmisc.c
index cee207d10024..590c862538d0 100644
--- a/drivers/soc/tegra/fuse/tegra-apbmisc.c
+++ b/drivers/soc/tegra/fuse/tegra-apbmisc.c
@@ -3,6 +3,7 @@
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
*/
+#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -90,6 +91,7 @@ u32 tegra_read_ram_code(void)
return straps >> PMC_STRAPPING_OPT_A_RAM_CODE_SHIFT;
}
+EXPORT_SYMBOL_GPL(tegra_read_ram_code);
static const struct of_device_id apbmisc_match[] __initconst = {
{ .compatible = "nvidia,tegra20-apbmisc", },
diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h
index 121a2430d7f7..b6d3bae1c74d 100644
--- a/include/linux/devfreq.h
+++ b/include/linux/devfreq.h
@@ -15,8 +15,6 @@
#include <linux/pm_opp.h>
#include <linux/pm_qos.h>
-#define DEVFREQ_NAME_LEN 16
-
/* DEVFREQ governor name */
#define DEVFREQ_GOV_SIMPLE_ONDEMAND "simple_ondemand"
#define DEVFREQ_GOV_PERFORMANCE "performance"
@@ -139,7 +137,6 @@ struct devfreq_stats {
* using devfreq.
* @profile: device-specific devfreq profile
* @governor: method how to choose frequency based on the usage.
- * @governor_name: devfreq governor name for use with this devfreq
* @nb: notifier block used to notify devfreq object that it should
* reevaluate operable frequencies. Devfreq users may use
* devfreq.nb to the corresponding register notifier call chain.
@@ -176,7 +173,6 @@ struct devfreq {
struct device dev;
struct devfreq_dev_profile *profile;
const struct devfreq_governor *governor;
- char governor_name[DEVFREQ_NAME_LEN];
struct notifier_block nb;
struct delayed_work work;
diff --git a/include/soc/tegra/fuse.h b/include/soc/tegra/fuse.h
index c702bd2911bc..78cbc787a4dc 100644
--- a/include/soc/tegra/fuse.h
+++ b/include/soc/tegra/fuse.h
@@ -56,7 +56,11 @@ u32 tegra_read_straps(void);
u32 tegra_read_ram_code(void);
int tegra_fuse_readl(unsigned long offset, u32 *value);
+#ifdef CONFIG_ARCH_TEGRA
extern struct tegra_sku_info tegra_sku_info;
+#else
+static struct tegra_sku_info tegra_sku_info __maybe_unused;
+#endif
struct device *tegra_soc_device_register(void);
diff --git a/include/trace/events/devfreq.h b/include/trace/events/devfreq.h
index cf5b8772175d..7627c620bbda 100644
--- a/include/trace/events/devfreq.h
+++ b/include/trace/events/devfreq.h
@@ -8,6 +8,34 @@
#include <linux/devfreq.h>
#include <linux/tracepoint.h>
+TRACE_EVENT(devfreq_frequency,
+ TP_PROTO(struct devfreq *devfreq, unsigned long freq,
+ unsigned long prev_freq),
+
+ TP_ARGS(devfreq, freq, prev_freq),
+
+ TP_STRUCT__entry(
+ __string(dev_name, dev_name(&devfreq->dev))
+ __field(unsigned long, freq)
+ __field(unsigned long, prev_freq)
+ __field(unsigned long, busy_time)
+ __field(unsigned long, total_time)
+ ),
+
+ TP_fast_assign(
+ __assign_str(dev_name, dev_name(&devfreq->dev));
+ __entry->freq = freq;
+ __entry->prev_freq = prev_freq;
+ __entry->busy_time = devfreq->last_status.busy_time;
+ __entry->total_time = devfreq->last_status.total_time;
+ ),
+
+ TP_printk("dev_name=%-30s freq=%-12lu prev_freq=%-12lu load=%-2lu",
+ __get_str(dev_name), __entry->freq, __entry->prev_freq,
+ __entry->total_time == 0 ? 0 :
+ (100 * __entry->busy_time) / __entry->total_time)
+);
+
TRACE_EVENT(devfreq_monitor,
TP_PROTO(struct devfreq *devfreq),
@@ -29,7 +57,7 @@ TRACE_EVENT(devfreq_monitor,
__assign_str(dev_name, dev_name(&devfreq->dev));
),
- TP_printk("dev_name=%s freq=%lu polling_ms=%u load=%lu",
+ TP_printk("dev_name=%-30s freq=%-12lu polling_ms=%-3u load=%-2lu",
__get_str(dev_name), __entry->freq, __entry->polling_ms,
__entry->total_time == 0 ? 0 :
(100 * __entry->busy_time) / __entry->total_time)