From fcbd8037f7df694aa7bfb7ce82c0c7f5e53e7b7b Mon Sep 17 00:00:00 2001 From: Lukasz Luba Date: Wed, 5 Jun 2019 11:12:32 +0200 Subject: [PATCH 01/25] include: dt-bindings: add Performance Monitoring Unit for Exynos This patch add support of a new feature which can be used in DT: Performance Monitoring Unit with defined event data type. In this patch the event data types are defined for Exynos PPMU. The patch also updates the MAINTAINERS file accordingly and adds the header file to devfreq event subsystem. Acked-by: Chanwoo Choi Reviewed-by: Rob Herring Signed-off-by: Lukasz Luba Signed-off-by: Chanwoo Choi --- MAINTAINERS | 1 + include/dt-bindings/pmu/exynos_ppmu.h | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 include/dt-bindings/pmu/exynos_ppmu.h diff --git a/MAINTAINERS b/MAINTAINERS index cba1095547fd..2bf811b5dfd4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4778,6 +4778,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mzx/devfreq.git S: Supported F: drivers/devfreq/event/ F: drivers/devfreq/devfreq-event.c +F: include/dt-bindings/pmu/exynos_ppmu.h F: include/linux/devfreq-event.h F: Documentation/devicetree/bindings/devfreq/event/ diff --git a/include/dt-bindings/pmu/exynos_ppmu.h b/include/dt-bindings/pmu/exynos_ppmu.h new file mode 100644 index 000000000000..8724abe130f3 --- /dev/null +++ b/include/dt-bindings/pmu/exynos_ppmu.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Samsung Exynos PPMU event types for counting in regs + * + * Copyright (c) 2019, Samsung Electronics + * Author: Lukasz Luba + */ + +#ifndef __DT_BINDINGS_PMU_EXYNOS_PPMU_H +#define __DT_BINDINGS_PMU_EXYNOS_PPMU_H + +#define PPMU_RO_BUSY_CYCLE_CNT 0x0 +#define PPMU_WO_BUSY_CYCLE_CNT 0x1 +#define PPMU_RW_BUSY_CYCLE_CNT 0x2 +#define PPMU_RO_REQUEST_CNT 0x3 +#define PPMU_WO_REQUEST_CNT 0x4 +#define PPMU_RO_DATA_CNT 0x5 +#define PPMU_WO_DATA_CNT 0x6 +#define PPMU_RO_LATENCY 0x12 +#define PPMU_WO_LATENCY 0x16 +#define PPMU_V2_RO_DATA_CNT 0x4 +#define PPMU_V2_WO_DATA_CNT 0x5 +#define PPMU_V2_EVT3_RW_DATA_CNT 0x22 + +#endif From 3b7b37846ba69232343a2f2e7eb5f66aaa81c071 Mon Sep 17 00:00:00 2001 From: Lukasz Luba Date: Wed, 5 Jun 2019 11:12:35 +0200 Subject: [PATCH 02/25] Documentation: devicetree: add PPMU events description Extend the documenation by events description with new 'event-data-type' field. Add example how the event might be defined in DT. Signed-off-by: Lukasz Luba Acked-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- .../bindings/devfreq/event/exynos-ppmu.txt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/devfreq/event/exynos-ppmu.txt b/Documentation/devicetree/bindings/devfreq/event/exynos-ppmu.txt index 3e36c1d11386..fb46b491791c 100644 --- a/Documentation/devicetree/bindings/devfreq/event/exynos-ppmu.txt +++ b/Documentation/devicetree/bindings/devfreq/event/exynos-ppmu.txt @@ -10,14 +10,23 @@ The Exynos PPMU driver uses the devfreq-event class to provide event data to various devfreq devices. The devfreq devices would use the event data when derterming the current state of each IP. -Required properties: +Required properties for PPMU device: - compatible: Should be "samsung,exynos-ppmu" or "samsung,exynos-ppmu-v2. - reg: physical base address of each PPMU and length of memory mapped region. -Optional properties: +Optional properties for PPMU device: - clock-names : the name of clock used by the PPMU, "ppmu" - clocks : phandles for clock specified in "clock-names" property +Required properties for 'events' child node of PPMU device: +- event-name : the unique event name among PPMU device +Optional properties for 'events' child node of PPMU device: +- event-data-type : Define the type of data which shell be counted +by the counter. You can check include/dt-bindings/pmu/exynos_ppmu.h for +all possible type, i.e. count read requests, count write data in bytes, +etc. This field is optional and when it is missing, the driver code +will use default data type. + Example1 : PPMUv1 nodes in exynos3250.dtsi are listed below. ppmu_dmc0: ppmu_dmc0@106a0000 { @@ -145,3 +154,16 @@ Example3 : PPMUv2 nodes in exynos5433.dtsi are listed below. reg = <0x104d0000 0x2000>; status = "disabled"; }; + +Example4 : 'event-data-type' in exynos4412-ppmu-common.dtsi are listed below. + + &ppmu_dmc0 { + status = "okay"; + events { + ppmu_dmc0_3: ppmu-event3-dmc0 { + event-name = "ppmu-event3-dmc0"; + event-data-type = <(PPMU_RO_DATA_CNT | + PPMU_WO_DATA_CNT)>; + }; + }; + }; From df4d7b1451bf51e75406b6339e964d816f8e947e Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Wed, 18 Sep 2019 17:09:46 -0700 Subject: [PATCH 03/25] PM / devfreq: Make log message more explicit when devfreq device already exists Before creating a new devfreq device devfreq_add_device() checks if there is already a devfreq dev associated with the requesting device (parent). If that's the case the function rejects to create another devfreq dev for that parent and logs an error. The error message is very unspecific, make it a bit more explicit. Reviewed-by: Chanwoo Choi Signed-off-by: Matthias Kaehlcke Signed-off-by: MyungJoo Ham Signed-off-by: Chanwoo Choi --- drivers/devfreq/devfreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 446490c9d635..b905963cea7d 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -625,7 +625,7 @@ struct devfreq *devfreq_add_device(struct device *dev, devfreq = find_device_devfreq(dev); mutex_unlock(&devfreq_list_lock); if (!IS_ERR(devfreq)) { - dev_err(dev, "%s: Unable to create devfreq for the device.\n", + dev_err(dev, "%s: devfreq device already exists!\n", __func__); err = -EINVAL; goto err_out; From 36eba5deffac3793372ebad4899591103aa3a947 Mon Sep 17 00:00:00 2001 From: Kamil Konieczny Date: Wed, 7 Aug 2019 15:38:38 +0200 Subject: [PATCH 04/25] dt-bindings: devfreq: exynos-bus: Remove unused property Remove unused DT property "exynos,voltage-tolerance". Signed-off-by: Kamil Konieczny Acked-by: Chanwoo Choi Acked-by: Rob Herring Signed-off-by: Chanwoo Choi --- Documentation/devicetree/bindings/devfreq/exynos-bus.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/devicetree/bindings/devfreq/exynos-bus.txt b/Documentation/devicetree/bindings/devfreq/exynos-bus.txt index f8e946471a58..e71f752cc18f 100644 --- a/Documentation/devicetree/bindings/devfreq/exynos-bus.txt +++ b/Documentation/devicetree/bindings/devfreq/exynos-bus.txt @@ -50,8 +50,6 @@ Required properties only for passive bus device: 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. -- exynos,voltage-tolerance: the percentage value for bus voltage tolerance - which is used to calculate the max voltage. Detailed correlation between sub-blocks and power line according to Exynos SoC: - In case of Exynos3250, there are two power line as following: From d68adc8f85cd757bd33c8d7b2660ad6f16f7f3dc Mon Sep 17 00:00:00 2001 From: Leonard Crestez Date: Tue, 24 Sep 2019 10:26:53 +0300 Subject: [PATCH 05/25] PM / devfreq: Check NULL governor in available_governors_show The governor is initialized after sysfs attributes become visible so in theory the governor field can be NULL here. Fixes: bcf23c79c4e46 ("PM / devfreq: Fix available_governor sysfs") Signed-off-by: Leonard Crestez Reviewed-by: Matthias Kaehlcke Reviewed-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- drivers/devfreq/devfreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index b905963cea7d..60859a2400bc 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -1195,7 +1195,7 @@ 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 (df->governor && df->governor->immutable) { count = scnprintf(&buf[count], DEVFREQ_NAME_LEN, "%s ", df->governor_name); /* From 2abb0d5268ae7b5ddf82099b1f8d5aa8414637d4 Mon Sep 17 00:00:00 2001 From: Leonard Crestez Date: Tue, 24 Sep 2019 10:52:23 +0300 Subject: [PATCH 06/25] PM / devfreq: Lock devfreq in trans_stat_show There is no locking in this sysfs show function so stats printing can race with a devfreq_update_status called as part of freq switching or with initialization. Also add an assert in devfreq_update_status to make it clear that lock must be held by caller. Fixes: 39688ce6facd ("PM / devfreq: account suspend/resume for stats") Cc: stable@vger.kernel.org Signed-off-by: Leonard Crestez Reviewed-by: Matthias Kaehlcke Reviewed-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- drivers/devfreq/devfreq.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 60859a2400bc..d6c3dce9e9d5 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -160,6 +160,7 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq) int lev, prev_lev, ret = 0; unsigned long cur_time; + lockdep_assert_held(&devfreq->lock); cur_time = jiffies; /* Immediately exit if previous_freq is not initialized yet. */ @@ -1397,12 +1398,17 @@ static ssize_t trans_stat_show(struct device *dev, int i, j; unsigned int max_state = devfreq->profile->max_state; - if (!devfreq->stop_polling && - devfreq_update_status(devfreq, devfreq->previous_freq)) - return 0; if (max_state == 0) return sprintf(buf, "Not Supported.\n"); + mutex_lock(&devfreq->lock); + if (!devfreq->stop_polling && + devfreq_update_status(devfreq, devfreq->previous_freq)) { + mutex_unlock(&devfreq->lock); + return 0; + } + mutex_unlock(&devfreq->lock); + len = sprintf(buf, " From : To\n"); len += sprintf(buf + len, " :"); for (i = 0; i < max_state; i++) From 1f125dee4feda21bca39ed7f1165198d96fca233 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Tue, 1 Oct 2019 14:46:41 +0200 Subject: [PATCH 07/25] PM / devfreq: exynos-ppmu: remove useless assignment The error code is propagated to the caller, so there is no need to keep it additionally in the unused variable. Signed-off-by: Marek Szyprowski Acked-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- drivers/devfreq/event/exynos-ppmu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/devfreq/event/exynos-ppmu.c b/drivers/devfreq/event/exynos-ppmu.c index 87b42055e6bc..85c7a77bf3f0 100644 --- a/drivers/devfreq/event/exynos-ppmu.c +++ b/drivers/devfreq/event/exynos-ppmu.c @@ -673,7 +673,6 @@ static int exynos_ppmu_probe(struct platform_device *pdev) for (i = 0; i < info->num_events; i++) { edev[i] = devm_devfreq_event_add_edev(&pdev->dev, &desc[i]); if (IS_ERR(edev[i])) { - ret = PTR_ERR(edev[i]); dev_err(&pdev->dev, "failed to add devfreq-event device\n"); return PTR_ERR(edev[i]); From dccdea01adf3b1413fbbb342128702741069cb6c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:55:59 +0300 Subject: [PATCH 08/25] PM / devfreq: tegra30: Change irq type to unsigned int IRQ numbers are always positive, hence the corresponding variable should be unsigned to keep types consistent. This is a minor change that cleans up code a tad more. Suggested-by: Thierry Reding Acked-by: MyungJoo Ham Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index a6ba75f4106d..a27300f40b0b 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -160,7 +160,7 @@ struct tegra_devfreq { struct tegra_devfreq_device devices[ARRAY_SIZE(actmon_device_configs)]; - int irq; + unsigned int irq; }; struct tegra_actmon_emc_ratio { @@ -618,12 +618,12 @@ static int tegra_devfreq_probe(struct platform_device *pdev) return PTR_ERR(tegra->emc_clock); } - tegra->irq = platform_get_irq(pdev, 0); - if (tegra->irq < 0) { - err = tegra->irq; + err = platform_get_irq(pdev, 0); + if (err < 0) { dev_err(&pdev->dev, "Failed to get IRQ: %d\n", err); return err; } + tegra->irq = err; reset_control_assert(tegra->reset); From d49eeb1e838594b8fea9ac29736d7b4e949a530f Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:00 +0300 Subject: [PATCH 09/25] PM / devfreq: tegra30: Keep interrupt disabled while governor is stopped There is no real need to keep interrupt always-enabled, will be nicer to keep it disabled while governor is inactive. Suggested-by: Thierry Reding Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 47 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index a27300f40b0b..a0a5f3f7b789 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -416,8 +417,6 @@ static void tegra_actmon_start(struct tegra_devfreq *tegra) { unsigned int i; - disable_irq(tegra->irq); - actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1, ACTMON_GLB_PERIOD_CTRL); @@ -442,8 +441,6 @@ static void tegra_actmon_stop(struct tegra_devfreq *tegra) } actmon_write_barrier(tegra); - - enable_irq(tegra->irq); } static int tegra_devfreq_target(struct device *dev, unsigned long *freq, @@ -552,6 +549,12 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, { struct tegra_devfreq *tegra = dev_get_drvdata(devfreq->dev.parent); + /* + * Couple devfreq-device with the governor early because it is + * needed at the moment of governor's start (used by ISR). + */ + tegra->devfreq = devfreq; + switch (event) { case DEVFREQ_GOV_START: devfreq_monitor_start(devfreq); @@ -586,10 +589,11 @@ static struct devfreq_governor tegra_devfreq_governor = { static int tegra_devfreq_probe(struct platform_device *pdev) { - struct tegra_devfreq *tegra; struct tegra_devfreq_device *dev; - unsigned int i; + struct tegra_devfreq *tegra; + struct devfreq *devfreq; unsigned long rate; + unsigned int i; int err; tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); @@ -625,6 +629,16 @@ static int tegra_devfreq_probe(struct platform_device *pdev) } tegra->irq = err; + irq_set_status_flags(tegra->irq, IRQ_NOAUTOEN); + + err = devm_request_threaded_irq(&pdev->dev, tegra->irq, NULL, + actmon_thread_isr, IRQF_ONESHOT, + "tegra-devfreq", tegra); + if (err) { + dev_err(&pdev->dev, "Interrupt request failed: %d\n", err); + return err; + } + reset_control_assert(tegra->reset); err = clk_prepare_enable(tegra->clock); @@ -672,28 +686,15 @@ static int tegra_devfreq_probe(struct platform_device *pdev) } tegra_devfreq_profile.initial_freq = clk_get_rate(tegra->emc_clock); - tegra->devfreq = devfreq_add_device(&pdev->dev, - &tegra_devfreq_profile, - "tegra_actmon", - NULL); - if (IS_ERR(tegra->devfreq)) { - err = PTR_ERR(tegra->devfreq); + devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile, + "tegra_actmon", NULL); + if (IS_ERR(devfreq)) { + err = PTR_ERR(devfreq); goto remove_governor; } - err = devm_request_threaded_irq(&pdev->dev, tegra->irq, NULL, - actmon_thread_isr, IRQF_ONESHOT, - "tegra-devfreq", tegra); - if (err) { - dev_err(&pdev->dev, "Interrupt request failed: %d\n", err); - goto remove_devfreq; - } - return 0; -remove_devfreq: - devfreq_remove_device(tegra->devfreq); - remove_governor: devfreq_remove_governor(&tegra_devfreq_governor); From 7296443b900e6a432da3d6b1a7862beb83946d19 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:01 +0300 Subject: [PATCH 10/25] PM / devfreq: tegra30: Handle possible round-rate error The EMC clock rate rounding technically could fail, hence let's handle the error cases properly. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index a0a5f3f7b789..66dfa98d8c6b 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -592,8 +592,8 @@ static int tegra_devfreq_probe(struct platform_device *pdev) struct tegra_devfreq_device *dev; struct tegra_devfreq *tegra; struct devfreq *devfreq; - unsigned long rate; unsigned int i; + long rate; int err; tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); @@ -650,8 +650,14 @@ static int tegra_devfreq_probe(struct platform_device *pdev) reset_control_deassert(tegra->reset); - tegra->max_freq = clk_round_rate(tegra->emc_clock, ULONG_MAX) / KHZ; + rate = clk_round_rate(tegra->emc_clock, ULONG_MAX); + if (rate < 0) { + dev_err(&pdev->dev, "Failed to round clock rate: %ld\n", rate); + return rate; + } + tegra->cur_freq = clk_get_rate(tegra->emc_clock) / KHZ; + tegra->max_freq = rate / KHZ; for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) { dev = tegra->devices + i; @@ -662,6 +668,13 @@ static int tegra_devfreq_probe(struct platform_device *pdev) 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, 0); if (err) { dev_err(&pdev->dev, "Failed to add OPP: %d\n", err); From e7955a34a2344b5a237aabd4d8a43e30c3183b79 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:02 +0300 Subject: [PATCH 11/25] PM / devfreq: tegra30: Drop write-barrier There is no need in a write-barrier now, given that interrupt masking is handled by CPU's GIC now. Hence we know exactly that interrupt won't fire after stopping the devfreq's governor. In other cases we don't care about potential buffering of the writes to hardware and thus there is no need to stall CPU. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 66dfa98d8c6b..b50bd1615010 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -230,12 +230,6 @@ static void tegra_devfreq_update_wmark(struct tegra_devfreq *tegra, ACTMON_DEV_LOWER_WMARK); } -static void actmon_write_barrier(struct tegra_devfreq *tegra) -{ - /* ensure the update has reached the ACTMON */ - readl(tegra->regs + ACTMON_GLB_STATUS); -} - static void actmon_isr_device(struct tegra_devfreq *tegra, struct tegra_devfreq_device *dev) { @@ -287,8 +281,6 @@ static void actmon_isr_device(struct tegra_devfreq *tegra, device_writel(dev, dev_ctrl, ACTMON_DEV_CTRL); device_writel(dev, ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS); - - actmon_write_barrier(tegra); } static unsigned long actmon_cpu_to_emc_rate(struct tegra_devfreq *tegra, @@ -376,8 +368,6 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb, tegra_devfreq_update_wmark(tegra, dev); } - actmon_write_barrier(tegra); - return NOTIFY_OK; } @@ -423,8 +413,6 @@ static void tegra_actmon_start(struct tegra_devfreq *tegra) for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) tegra_actmon_configure_device(tegra, &tegra->devices[i]); - actmon_write_barrier(tegra); - enable_irq(tegra->irq); } @@ -439,8 +427,6 @@ static void tegra_actmon_stop(struct tegra_devfreq *tegra) device_writel(&tegra->devices[i], ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS); } - - actmon_write_barrier(tegra); } static int tegra_devfreq_target(struct device *dev, unsigned long *freq, From 53b4b2aeee26f42cde5ff2a16dd0d8590c51a55a Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:03 +0300 Subject: [PATCH 12/25] PM / devfreq: tegra30: Fix integer overflow on CPU's freq max out There is another kHz-conversion bug in the code, resulting in integer overflow. Although, this time the resulting value is 4294966296 and it's close to ULONG_MAX, which is okay in this case. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index b50bd1615010..7d7b7eecc19c 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -69,6 +69,8 @@ #define KHZ 1000 +#define KHZ_MAX (ULONG_MAX / KHZ) + /* Assume that the bus is saturated if the utilization is 25% */ #define BUS_SATURATION_RATIO 25 @@ -170,7 +172,7 @@ struct tegra_actmon_emc_ratio { }; static struct tegra_actmon_emc_ratio actmon_emc_ratios[] = { - { 1400000, ULONG_MAX }, + { 1400000, KHZ_MAX }, { 1200000, 750000 }, { 1100000, 600000 }, { 1000000, 500000 }, From 0ce3884654d186842eed858b6d41925539696454 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:04 +0300 Subject: [PATCH 13/25] PM / devfreq: tegra30: Use kHz units uniformly in the code Part of the code uses Hz units and the other kHz, let's switch to kHz everywhere for consistency. A small benefit from this change (besides code's cleanup) is that now powertop utility correctly displays devfreq's stats, for some reason it expects them to be in kHz. Tested-by: Peter Geis Reviewed-by: Chanwoo Choi Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 7d7b7eecc19c..9ccde64be0a0 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -448,7 +448,7 @@ static int tegra_devfreq_target(struct device *dev, unsigned long *freq, rate = dev_pm_opp_get_freq(opp); dev_pm_opp_put(opp); - err = clk_set_min_rate(tegra->emc_clock, rate); + err = clk_set_min_rate(tegra->emc_clock, rate * KHZ); if (err) return err; @@ -477,7 +477,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 * KHZ; + stat->current_frequency = cur_freq; actmon_dev = &tegra->devices[MCALL]; @@ -527,7 +527,7 @@ static int tegra_governor_get_target(struct devfreq *devfreq, target_freq = max(target_freq, dev->target_freq); } - *freq = target_freq * KHZ; + *freq = target_freq; return 0; } @@ -663,7 +663,7 @@ static int tegra_devfreq_probe(struct platform_device *pdev) goto remove_opps; } - err = dev_pm_opp_add(&pdev->dev, rate, 0); + 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; @@ -686,7 +686,8 @@ static int tegra_devfreq_probe(struct platform_device *pdev) goto unreg_notifier; } - tegra_devfreq_profile.initial_freq = clk_get_rate(tegra->emc_clock); + tegra_devfreq_profile.initial_freq = tegra->cur_freq; + devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile, "tegra_actmon", NULL); if (IS_ERR(devfreq)) { From 11eb6ec5c0d4ad513c62e33009bcb15d84dfff56 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:05 +0300 Subject: [PATCH 14/25] PM / devfreq: tegra30: Use CPUFreq notifier The CPU's client need to take into account that CPUFreq may change while memory activity not, staying high. Thus an appropriate frequency notifier should be used in addition to the clk-notifier. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 180 +++++++++++++++++++++++++----- 1 file changed, 155 insertions(+), 25 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 9ccde64be0a0..2d720e7e2236 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "governor.h" @@ -34,6 +35,8 @@ #define ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN BIT(30) #define ACTMON_DEV_CTRL_ENB BIT(31) +#define ACTMON_DEV_CTRL_STOP 0x00000000 + #define ACTMON_DEV_UPPER_WMARK 0x4 #define ACTMON_DEV_LOWER_WMARK 0x8 #define ACTMON_DEV_INIT_AVG 0xc @@ -159,7 +162,10 @@ struct tegra_devfreq { struct clk *emc_clock; unsigned long max_freq; unsigned long cur_freq; - struct notifier_block rate_change_nb; + struct notifier_block clk_rate_change_nb; + + struct delayed_work cpufreq_update_work; + struct notifier_block cpu_rate_change_nb; struct tegra_devfreq_device devices[ARRAY_SIZE(actmon_device_configs)]; @@ -303,22 +309,32 @@ static unsigned long actmon_cpu_to_emc_rate(struct tegra_devfreq *tegra, return 0; } +static unsigned long actmon_device_target_freq(struct tegra_devfreq *tegra, + struct tegra_devfreq_device *dev) +{ + unsigned int avg_sustain_coef; + unsigned long target_freq; + + target_freq = dev->avg_count / ACTMON_SAMPLING_PERIOD; + avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold; + target_freq = do_percent(target_freq, avg_sustain_coef); + target_freq += dev->boost_freq; + + return target_freq; +} + static void actmon_update_target(struct tegra_devfreq *tegra, struct tegra_devfreq_device *dev) { unsigned long cpu_freq = 0; unsigned long static_cpu_emc_freq = 0; - unsigned int avg_sustain_coef; if (dev->config->avg_dependency_threshold) { - cpu_freq = cpufreq_get(0); + cpu_freq = cpufreq_quick_get(0); static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); } - dev->target_freq = dev->avg_count / ACTMON_SAMPLING_PERIOD; - avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold; - dev->target_freq = do_percent(dev->target_freq, avg_sustain_coef); - dev->target_freq += dev->boost_freq; + dev->target_freq = actmon_device_target_freq(tegra, dev); if (dev->avg_count >= dev->config->avg_dependency_threshold) dev->target_freq = max(dev->target_freq, static_cpu_emc_freq); @@ -349,8 +365,8 @@ static irqreturn_t actmon_thread_isr(int irq, void *data) return handled ? IRQ_HANDLED : IRQ_NONE; } -static int tegra_actmon_rate_notify_cb(struct notifier_block *nb, - unsigned long action, void *ptr) +static int tegra_actmon_clk_notify_cb(struct notifier_block *nb, + unsigned long action, void *ptr) { struct clk_notifier_data *data = ptr; struct tegra_devfreq *tegra; @@ -360,7 +376,7 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb, if (action != POST_RATE_CHANGE) return NOTIFY_OK; - tegra = container_of(nb, struct tegra_devfreq, rate_change_nb); + tegra = container_of(nb, struct tegra_devfreq, clk_rate_change_nb); tegra->cur_freq = data->new_rate / KHZ; @@ -373,6 +389,79 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb, return NOTIFY_OK; } +static void tegra_actmon_delayed_update(struct work_struct *work) +{ + struct tegra_devfreq *tegra = container_of(work, struct tegra_devfreq, + cpufreq_update_work.work); + + mutex_lock(&tegra->devfreq->lock); + update_devfreq(tegra->devfreq); + mutex_unlock(&tegra->devfreq->lock); +} + +static unsigned long +tegra_actmon_cpufreq_contribution(struct tegra_devfreq *tegra, + unsigned int cpu_freq) +{ + unsigned long static_cpu_emc_freq, dev_freq; + + /* check whether CPU's freq is taken into account at all */ + if (tegra->devices[MCCPU].avg_count < + tegra->devices[MCCPU].config->avg_dependency_threshold) + return 0; + + static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); + dev_freq = actmon_device_target_freq(tegra, &tegra->devices[MCCPU]); + + if (dev_freq >= static_cpu_emc_freq) + return 0; + + return static_cpu_emc_freq; +} + +static int tegra_actmon_cpu_notify_cb(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + struct cpufreq_freqs *freqs = ptr; + struct tegra_devfreq *tegra; + unsigned long old, new, delay; + + if (action != CPUFREQ_POSTCHANGE) + return NOTIFY_OK; + + tegra = container_of(nb, struct tegra_devfreq, cpu_rate_change_nb); + + /* + * Quickly check whether CPU frequency should be taken into account + * at all, without blocking CPUFreq's core. + */ + if (mutex_trylock(&tegra->devfreq->lock)) { + old = tegra_actmon_cpufreq_contribution(tegra, freqs->old); + new = tegra_actmon_cpufreq_contribution(tegra, freqs->new); + mutex_unlock(&tegra->devfreq->lock); + + /* + * If CPU's frequency shouldn't be taken into account at + * the moment, then there is no need to update the devfreq's + * state because ISR will re-check CPU's frequency on the + * next interrupt. + */ + if (old == new) + return NOTIFY_OK; + } + + /* + * CPUFreq driver should support CPUFREQ_ASYNC_NOTIFICATION in order + * to allow asynchronous notifications. This means we can't block + * here for too long, otherwise CPUFreq's core will complain with a + * warning splat. + */ + delay = msecs_to_jiffies(ACTMON_SAMPLING_PERIOD); + schedule_delayed_work(&tegra->cpufreq_update_work, delay); + + return NOTIFY_OK; +} + static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, struct tegra_devfreq_device *dev) { @@ -405,9 +494,22 @@ static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, device_writel(dev, val, ACTMON_DEV_CTRL); } -static void tegra_actmon_start(struct tegra_devfreq *tegra) +static void tegra_actmon_stop_devices(struct tegra_devfreq *tegra) +{ + struct tegra_devfreq_device *dev = tegra->devices; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++, dev++) { + device_writel(dev, ACTMON_DEV_CTRL_STOP, ACTMON_DEV_CTRL); + device_writel(dev, ACTMON_INTR_STATUS_CLEAR, + ACTMON_DEV_INTR_STATUS); + } +} + +static int tegra_actmon_start(struct tegra_devfreq *tegra) { unsigned int i; + int err; actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1, ACTMON_GLB_PERIOD_CTRL); @@ -415,20 +517,41 @@ static void tegra_actmon_start(struct tegra_devfreq *tegra) for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) tegra_actmon_configure_device(tegra, &tegra->devices[i]); + /* + * We are estimating CPU's memory bandwidth requirement based on + * amount of memory accesses and system's load, judging by CPU's + * frequency. We also don't want to receive events about CPU's + * frequency transaction when governor is stopped, hence notifier + * is registered dynamically. + */ + err = cpufreq_register_notifier(&tegra->cpu_rate_change_nb, + CPUFREQ_TRANSITION_NOTIFIER); + if (err) { + dev_err(tegra->devfreq->dev.parent, + "Failed to register rate change notifier: %d\n", err); + goto err_stop; + } + enable_irq(tegra->irq); + + return 0; + +err_stop: + tegra_actmon_stop_devices(tegra); + + return err; } static void tegra_actmon_stop(struct tegra_devfreq *tegra) { - unsigned int i; - disable_irq(tegra->irq); - for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) { - device_writel(&tegra->devices[i], 0x00000000, ACTMON_DEV_CTRL); - device_writel(&tegra->devices[i], ACTMON_INTR_STATUS_CLEAR, - ACTMON_DEV_INTR_STATUS); - } + cpufreq_unregister_notifier(&tegra->cpu_rate_change_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + cancel_delayed_work_sync(&tegra->cpufreq_update_work); + + tegra_actmon_stop_devices(tegra); } static int tegra_devfreq_target(struct device *dev, unsigned long *freq, @@ -536,6 +659,7 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, unsigned int event, void *data) { struct tegra_devfreq *tegra = dev_get_drvdata(devfreq->dev.parent); + int ret = 0; /* * Couple devfreq-device with the governor early because it is @@ -546,7 +670,7 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, switch (event) { case DEVFREQ_GOV_START: devfreq_monitor_start(devfreq); - tegra_actmon_start(tegra); + ret = tegra_actmon_start(tegra); break; case DEVFREQ_GOV_STOP: @@ -561,11 +685,11 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, case DEVFREQ_GOV_RESUME: devfreq_monitor_resume(devfreq); - tegra_actmon_start(tegra); + ret = tegra_actmon_start(tegra); break; } - return 0; + return ret; } static struct devfreq_governor tegra_devfreq_governor = { @@ -672,8 +796,14 @@ static int tegra_devfreq_probe(struct platform_device *pdev) platform_set_drvdata(pdev, tegra); - tegra->rate_change_nb.notifier_call = tegra_actmon_rate_notify_cb; - err = clk_notifier_register(tegra->emc_clock, &tegra->rate_change_nb); + tegra->cpu_rate_change_nb.notifier_call = tegra_actmon_cpu_notify_cb; + + INIT_DELAYED_WORK(&tegra->cpufreq_update_work, + tegra_actmon_delayed_update); + + tegra->clk_rate_change_nb.notifier_call = tegra_actmon_clk_notify_cb; + err = clk_notifier_register(tegra->emc_clock, + &tegra->clk_rate_change_nb); if (err) { dev_err(&pdev->dev, "Failed to register rate change notifier\n"); @@ -701,7 +831,7 @@ remove_governor: devfreq_remove_governor(&tegra_devfreq_governor); unreg_notifier: - clk_notifier_unregister(tegra->emc_clock, &tegra->rate_change_nb); + clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); remove_opps: dev_pm_opp_remove_all_dynamic(&pdev->dev); @@ -719,7 +849,7 @@ static int tegra_devfreq_remove(struct platform_device *pdev) devfreq_remove_device(tegra->devfreq); devfreq_remove_governor(&tegra_devfreq_governor); - clk_notifier_unregister(tegra->emc_clock, &tegra->rate_change_nb); + clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); dev_pm_opp_remove_all_dynamic(&pdev->dev); reset_control_reset(tegra->reset); From 6f2a35d65b3c4bbc579a115589edcc7ed6239dce Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:06 +0300 Subject: [PATCH 15/25] PM / devfreq: tegra30: Move clk-notifier's registration to governor's start There is no point in receiving of the notifications while governor is stopped, let's keep them disabled like we do for the CPU freq-change notifications. This also fixes a potential use-after-free bug if notification happens after device's removal. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 39 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 2d720e7e2236..6960d8ba0577 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -514,6 +514,21 @@ static int tegra_actmon_start(struct tegra_devfreq *tegra) actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1, ACTMON_GLB_PERIOD_CTRL); + /* + * CLK notifications are needed in order to reconfigure the upper + * consecutive watermark in accordance to the actual clock rate + * to avoid unnecessary upper interrupts. + */ + err = clk_notifier_register(tegra->emc_clock, + &tegra->clk_rate_change_nb); + if (err) { + dev_err(tegra->devfreq->dev.parent, + "Failed to register rate change notifier\n"); + return err; + } + + tegra->cur_freq = clk_get_rate(tegra->emc_clock) / KHZ; + for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) tegra_actmon_configure_device(tegra, &tegra->devices[i]); @@ -539,6 +554,8 @@ static int tegra_actmon_start(struct tegra_devfreq *tegra) err_stop: tegra_actmon_stop_devices(tegra); + clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); + return err; } @@ -552,6 +569,8 @@ static void tegra_actmon_stop(struct tegra_devfreq *tegra) cancel_delayed_work_sync(&tegra->cpufreq_update_work); tegra_actmon_stop_devices(tegra); + + clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); } static int tegra_devfreq_target(struct device *dev, unsigned long *freq, @@ -768,7 +787,6 @@ static int tegra_devfreq_probe(struct platform_device *pdev) return rate; } - tegra->cur_freq = clk_get_rate(tegra->emc_clock) / KHZ; tegra->max_freq = rate / KHZ; for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) { @@ -796,27 +814,20 @@ static int tegra_devfreq_probe(struct platform_device *pdev) platform_set_drvdata(pdev, tegra); + tegra->clk_rate_change_nb.notifier_call = tegra_actmon_clk_notify_cb; tegra->cpu_rate_change_nb.notifier_call = tegra_actmon_cpu_notify_cb; INIT_DELAYED_WORK(&tegra->cpufreq_update_work, tegra_actmon_delayed_update); - tegra->clk_rate_change_nb.notifier_call = tegra_actmon_clk_notify_cb; - err = clk_notifier_register(tegra->emc_clock, - &tegra->clk_rate_change_nb); - if (err) { - dev_err(&pdev->dev, - "Failed to register rate change notifier\n"); - goto remove_opps; - } - err = devfreq_add_governor(&tegra_devfreq_governor); if (err) { dev_err(&pdev->dev, "Failed to add governor: %d\n", err); - goto unreg_notifier; + goto remove_opps; } - tegra_devfreq_profile.initial_freq = tegra->cur_freq; + 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); @@ -830,9 +841,6 @@ static int tegra_devfreq_probe(struct platform_device *pdev) remove_governor: devfreq_remove_governor(&tegra_devfreq_governor); -unreg_notifier: - clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); - remove_opps: dev_pm_opp_remove_all_dynamic(&pdev->dev); @@ -849,7 +857,6 @@ static int tegra_devfreq_remove(struct platform_device *pdev) devfreq_remove_device(tegra->devfreq); devfreq_remove_governor(&tegra_devfreq_governor); - clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); dev_pm_opp_remove_all_dynamic(&pdev->dev); reset_control_reset(tegra->reset); From 142665582736f7f0a5c11a606271705020ba2f4e Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:07 +0300 Subject: [PATCH 16/25] PM / devfreq: tegra30: Reset boosting on startup Governor could be stopped while boosting is active. We have assumption that everything is reset on governor's restart, including the boosting value, which was missed. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 6960d8ba0577..9cb2d6468175 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -467,6 +467,9 @@ static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, { u32 val = 0; + /* reset boosting on governor's restart */ + dev->boost_freq = 0; + dev->target_freq = tegra->cur_freq; dev->avg_count = tegra->cur_freq * ACTMON_SAMPLING_PERIOD; From 61d932084174ceb4876a2b1a792dceb4fe5526eb Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:08 +0300 Subject: [PATCH 17/25] PM / devfreq: tegra30: Don't enable consecutive-down interrupt on startup The consecutive-down event tells that we should perform frequency de-boosting, but boosting is in a reset state on start and hence the event won't do anything useful for us and it will be just a dummy interrupt request. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 9cb2d6468175..bc46af155b99 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -490,7 +490,6 @@ static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, << ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_NUM_SHIFT; val |= ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN; val |= ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN; - val |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; val |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; val |= ACTMON_DEV_CTRL_ENB; From b87dea3bbab276fc34f5cc65749f1f39f213afd0 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:09 +0300 Subject: [PATCH 18/25] PM / devfreq: tegra30: Constify structs Constify unmodifiable structs, for consistency. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index bc46af155b99..9bd4dd982927 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -108,7 +108,7 @@ enum tegra_actmon_device { MCCPU, }; -static struct tegra_devfreq_device_config actmon_device_configs[] = { +static const struct tegra_devfreq_device_config actmon_device_configs[] = { { /* MCALL: All memory accesses (including from the CPUs) */ .offset = 0x1c0, @@ -177,7 +177,7 @@ struct tegra_actmon_emc_ratio { unsigned long emc_freq; }; -static struct tegra_actmon_emc_ratio actmon_emc_ratios[] = { +static const struct tegra_actmon_emc_ratio actmon_emc_ratios[] = { { 1400000, KHZ_MAX }, { 1200000, 750000 }, { 1100000, 600000 }, @@ -295,7 +295,7 @@ static unsigned long actmon_cpu_to_emc_rate(struct tegra_devfreq *tegra, unsigned long cpu_freq) { unsigned int i; - struct tegra_actmon_emc_ratio *ratio = actmon_emc_ratios; + const struct tegra_actmon_emc_ratio *ratio = actmon_emc_ratios; for (i = 0; i < ARRAY_SIZE(actmon_emc_ratios); i++, ratio++) { if (cpu_freq >= ratio->cpu_freq) { From 9cff2177789f2cf87432be76620c13ba9c632b68 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:10 +0300 Subject: [PATCH 19/25] PM / devfreq: tegra30: Include appropriate header It's not very correct to include mod_devicetable.h for the OF device drivers and of_device.h should be included instead. Reviewed-by: Chanwoo Choi Tested-by: Peter Geis Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 9bd4dd982927..7c8126e74750 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include From 333abefb281218ab965e34932feecb1be064d535 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:11 +0300 Subject: [PATCH 20/25] PM / devfreq: tegra30: Don't enable already enabled consecutive interrupts Consecutive up/down interrupt-bit is set in the interrupt status register only if that interrupt was previously enabled. Thus enabling the already enabled interrupt doesn't do much for us. Reviewed-by: Chanwoo Choi Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 7c8126e74750..4a5d513904a2 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -261,8 +261,6 @@ static void actmon_isr_device(struct tegra_devfreq *tegra, if (dev->boost_freq >= tegra->max_freq) dev->boost_freq = tegra->max_freq; - else - dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; } else if (intr_status & ACTMON_DEV_INTR_CONSECUTIVE_LOWER) { /* * new_boost = old_boost * down_coef @@ -275,8 +273,6 @@ static void actmon_isr_device(struct tegra_devfreq *tegra, if (dev->boost_freq < (ACTMON_BOOST_FREQ_STEP >> 1)) dev->boost_freq = 0; - else - dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; } if (dev->config->avg_dependency_threshold) { From 88ec816446fab2012c73c7096fb8b1924fe6837c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:12 +0300 Subject: [PATCH 21/25] PM / devfreq: tegra30: Disable consecutive interrupts when appropriate Consecutive interrupts should be disabled when boosting is completed. Currently the disabling of "lower" interrupt happens only for MCCPU monitor that uses dependency threshold, but even in a case of MCCPU the interrupt isn't getting disabled if CPU's activity is above the threshold. This results in a lot of dummy interrupt requests. The boosting feature is used by both MCCPU and MCALL, boosting should be stopped once it reaches 0 for both of the monitors and regardless of the activity level. The boosting stops to grow once the maximum limit is hit and thus the "upper" interrupt needs to be disabled when the limit is reached. Reviewed-by: Chanwoo Choi Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 4a5d513904a2..852bde6249c7 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -259,8 +259,10 @@ static void actmon_isr_device(struct tegra_devfreq *tegra, dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; - if (dev->boost_freq >= tegra->max_freq) + if (dev->boost_freq >= tegra->max_freq) { + dev_ctrl &= ~ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; dev->boost_freq = tegra->max_freq; + } } else if (intr_status & ACTMON_DEV_INTR_CONSECUTIVE_LOWER) { /* * new_boost = old_boost * down_coef @@ -271,15 +273,10 @@ static void actmon_isr_device(struct tegra_devfreq *tegra, dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN; - if (dev->boost_freq < (ACTMON_BOOST_FREQ_STEP >> 1)) - dev->boost_freq = 0; - } - - if (dev->config->avg_dependency_threshold) { - if (dev->avg_count >= dev->config->avg_dependency_threshold) - dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; - else if (dev->boost_freq == 0) + if (dev->boost_freq < (ACTMON_BOOST_FREQ_STEP >> 1)) { dev_ctrl &= ~ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN; + dev->boost_freq = 0; + } } device_writel(dev, dev_ctrl, ACTMON_DEV_CTRL); From 28615e37be96877e5bb3559f566e50a291cf7a05 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:13 +0300 Subject: [PATCH 22/25] PM / devfreq: tegra30: Use kHz units for dependency threshold The dependency threshold designates a memory activity level below which CPU's frequency isn't accounted. Currently the threshold is given in "memory cycle" units and that value depends on the polling interval which is fixed to 12ms in the driver. Later on we'd want to add support for a variable polling interval and thus the threshold value either needs to be scaled in accordance to the polling interval or it needs to be represented in a units that do not depend on the polling interval. It is nicer to have threshold value being defined independently of the polling interval, thus this patch converts the dependency threshold units from "cycle" to "kHz". Having this change as a separate-preparatory patch will make easier to follow further patches. Signed-off-by: Dmitry Osipenko Reviewed-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 32 +++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 852bde6249c7..3bd920829bfd 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -96,9 +96,10 @@ struct tegra_devfreq_device_config { unsigned int boost_down_threshold; /* - * Threshold of activity (cycles) below which the CPU frequency isn't - * to be taken into account. This is to avoid increasing the EMC - * frequency when the CPU is very busy but not accessing the bus often. + * Threshold of activity (cycles translated to kHz) below which the + * CPU frequency isn't to be taken into account. This is to avoid + * increasing the EMC frequency when the CPU is very busy but not + * accessing the bus often. */ u32 avg_dependency_threshold; }; @@ -126,7 +127,7 @@ static const struct tegra_devfreq_device_config actmon_device_configs[] = { .boost_down_coeff = 90, .boost_up_threshold = 27, .boost_down_threshold = 10, - .avg_dependency_threshold = 50000, + .avg_dependency_threshold = 16000, /* 16MHz in kHz units */ }, }; @@ -311,7 +312,6 @@ static unsigned long actmon_device_target_freq(struct tegra_devfreq *tegra, target_freq = dev->avg_count / ACTMON_SAMPLING_PERIOD; avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold; target_freq = do_percent(target_freq, avg_sustain_coef); - target_freq += dev->boost_freq; return target_freq; } @@ -322,15 +322,18 @@ static void actmon_update_target(struct tegra_devfreq *tegra, unsigned long cpu_freq = 0; unsigned long static_cpu_emc_freq = 0; - if (dev->config->avg_dependency_threshold) { - cpu_freq = cpufreq_quick_get(0); - static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); - } - dev->target_freq = actmon_device_target_freq(tegra, dev); - if (dev->avg_count >= dev->config->avg_dependency_threshold) + if (dev->config->avg_dependency_threshold && + dev->config->avg_dependency_threshold <= dev->target_freq) { + cpu_freq = cpufreq_quick_get(0); + static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); + + dev->target_freq += dev->boost_freq; dev->target_freq = max(dev->target_freq, static_cpu_emc_freq); + } else { + dev->target_freq += dev->boost_freq; + } } static irqreturn_t actmon_thread_isr(int irq, void *data) @@ -396,15 +399,16 @@ static unsigned long tegra_actmon_cpufreq_contribution(struct tegra_devfreq *tegra, unsigned int cpu_freq) { + struct tegra_devfreq_device *actmon_dev = &tegra->devices[MCCPU]; unsigned long static_cpu_emc_freq, dev_freq; + dev_freq = actmon_device_target_freq(tegra, actmon_dev); + /* check whether CPU's freq is taken into account at all */ - if (tegra->devices[MCCPU].avg_count < - tegra->devices[MCCPU].config->avg_dependency_threshold) + if (dev_freq < actmon_dev->config->avg_dependency_threshold) return 0; static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq); - dev_freq = actmon_device_target_freq(tegra, &tegra->devices[MCCPU]); if (dev_freq >= static_cpu_emc_freq) return 0; From 5c0f6c79595760c9e366c3517314051af530e3e6 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:14 +0300 Subject: [PATCH 23/25] PM / devfreq: Add new interrupt_driven flag for governors Currently interrupt-driven governors (like NVIDIA Tegra30 ACTMON governor) are used to set polling_ms=0 in order to avoid periodic polling of device status by devfreq core. This means that polling interval can't be changed by userspace for such governors. The new governor flag allows interrupt-driven governors to convey that devfreq core shouldn't perform polling of device status and thus generic devfreq polling interval could be supported by these governors now. Signed-off-by: Dmitry Osipenko Reviewed-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- drivers/devfreq/devfreq.c | 17 +++++++++++++++++ drivers/devfreq/governor.h | 3 +++ 2 files changed, 20 insertions(+) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index d6c3dce9e9d5..f840e61e5a27 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -410,6 +410,9 @@ static void devfreq_monitor(struct work_struct *work) */ void devfreq_monitor_start(struct devfreq *devfreq) { + if (devfreq->governor->interrupt_driven) + return; + INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor); if (devfreq->profile->polling_ms) queue_delayed_work(devfreq_wq, &devfreq->work, @@ -427,6 +430,9 @@ EXPORT_SYMBOL(devfreq_monitor_start); */ void devfreq_monitor_stop(struct devfreq *devfreq) { + if (devfreq->governor->interrupt_driven) + return; + cancel_delayed_work_sync(&devfreq->work); } EXPORT_SYMBOL(devfreq_monitor_stop); @@ -454,6 +460,10 @@ void devfreq_monitor_suspend(struct devfreq *devfreq) devfreq_update_status(devfreq, devfreq->previous_freq); devfreq->stop_polling = true; mutex_unlock(&devfreq->lock); + + if (devfreq->governor->interrupt_driven) + return; + cancel_delayed_work_sync(&devfreq->work); } EXPORT_SYMBOL(devfreq_monitor_suspend); @@ -474,11 +484,15 @@ void devfreq_monitor_resume(struct devfreq *devfreq) if (!devfreq->stop_polling) goto out; + if (devfreq->governor->interrupt_driven) + goto out_update; + if (!delayed_work_pending(&devfreq->work) && devfreq->profile->polling_ms) queue_delayed_work(devfreq_wq, &devfreq->work, msecs_to_jiffies(devfreq->profile->polling_ms)); +out_update: devfreq->last_stat_updated = jiffies; devfreq->stop_polling = false; @@ -510,6 +524,9 @@ void devfreq_interval_update(struct devfreq *devfreq, unsigned int *delay) if (devfreq->stop_polling) goto out; + if (devfreq->governor->interrupt_driven) + goto out; + /* if new delay is zero, stop polling */ if (!new_delay) { mutex_unlock(&devfreq->lock); diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h index bbe5ff9fcecf..dc7533ccc3db 100644 --- a/drivers/devfreq/governor.h +++ b/drivers/devfreq/governor.h @@ -31,6 +31,8 @@ * @name: Governor's name * @immutable: Immutable flag for governor. If the value is 1, * this govenror is never changeable to other governor. + * @interrupt_driven: Devfreq core won't schedule polling work for this + * governor if value is set to 1. * @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 @@ -49,6 +51,7 @@ struct devfreq_governor { const char name[DEVFREQ_NAME_LEN]; const unsigned int immutable; + const unsigned int interrupt_driven; int (*get_target_freq)(struct devfreq *this, unsigned long *freq); int (*event_handler)(struct devfreq *devfreq, unsigned int event, void *data); From f61ee201068ac844cc8ed7f9112e47f3452f8262 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:15 +0300 Subject: [PATCH 24/25] PM / devfreq: tegra30: Support variable polling interval The ACTMON governor is interrupt-driven and currently hardware's polling interval is fixed to 16ms in the driver. Devfreq supports variable polling interval by the generic governors, let's re-use the generic interface for changing of the polling interval. Now the polling interval can be changed dynamically via /sys/class/devfreq/devfreq0/polling_interval. Signed-off-by: Dmitry Osipenko Reviewed-by: Chanwoo Choi Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 78 ++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 3bd920829bfd..e44f1a48f838 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -171,6 +171,8 @@ struct tegra_devfreq { struct tegra_devfreq_device devices[ARRAY_SIZE(actmon_device_configs)]; unsigned int irq; + + bool started; }; struct tegra_actmon_emc_ratio { @@ -209,18 +211,26 @@ static void device_writel(struct tegra_devfreq_device *dev, u32 val, writel_relaxed(val, dev->regs + offset); } -static unsigned long do_percent(unsigned long val, unsigned int pct) +static unsigned long do_percent(unsigned long long val, unsigned int pct) { - return val * pct / 100; + val = val * pct; + do_div(val, 100); + + /* + * High freq + high boosting percent + large polling interval are + * resulting in integer overflow when watermarks are calculated. + */ + return min_t(u64, val, U32_MAX); } static void tegra_devfreq_update_avg_wmark(struct tegra_devfreq *tegra, struct tegra_devfreq_device *dev) { - u32 avg = dev->avg_count; u32 avg_band_freq = tegra->max_freq * ACTMON_DEFAULT_AVG_BAND / KHZ; - u32 band = avg_band_freq * ACTMON_SAMPLING_PERIOD; + u32 band = avg_band_freq * tegra->devfreq->profile->polling_ms; + u32 avg; + avg = min(dev->avg_count, U32_MAX - band); device_writel(dev, avg + band, ACTMON_DEV_AVG_UPPER_WMARK); avg = max(dev->avg_count, band); @@ -230,7 +240,7 @@ static void tegra_devfreq_update_avg_wmark(struct tegra_devfreq *tegra, static void tegra_devfreq_update_wmark(struct tegra_devfreq *tegra, struct tegra_devfreq_device *dev) { - u32 val = tegra->cur_freq * ACTMON_SAMPLING_PERIOD; + u32 val = tegra->cur_freq * tegra->devfreq->profile->polling_ms; device_writel(dev, do_percent(val, dev->config->boost_up_threshold), ACTMON_DEV_UPPER_WMARK); @@ -309,7 +319,7 @@ static unsigned long actmon_device_target_freq(struct tegra_devfreq *tegra, unsigned int avg_sustain_coef; unsigned long target_freq; - target_freq = dev->avg_count / ACTMON_SAMPLING_PERIOD; + target_freq = dev->avg_count / tegra->devfreq->profile->polling_ms; avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold; target_freq = do_percent(target_freq, avg_sustain_coef); @@ -469,7 +479,7 @@ static void tegra_actmon_configure_device(struct tegra_devfreq *tegra, dev->target_freq = tegra->cur_freq; - dev->avg_count = tegra->cur_freq * ACTMON_SAMPLING_PERIOD; + dev->avg_count = tegra->cur_freq * tegra->devfreq->profile->polling_ms; device_writel(dev, dev->avg_count, ACTMON_DEV_INIT_AVG); tegra_devfreq_update_avg_wmark(tegra, dev); @@ -505,12 +515,15 @@ static void tegra_actmon_stop_devices(struct tegra_devfreq *tegra) } } -static int tegra_actmon_start(struct tegra_devfreq *tegra) +static int tegra_actmon_resume(struct tegra_devfreq *tegra) { unsigned int i; int err; - actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1, + if (!tegra->devfreq->profile->polling_ms || !tegra->started) + return 0; + + actmon_writel(tegra, tegra->devfreq->profile->polling_ms - 1, ACTMON_GLB_PERIOD_CTRL); /* @@ -558,8 +571,26 @@ err_stop: return err; } -static void tegra_actmon_stop(struct tegra_devfreq *tegra) +static int tegra_actmon_start(struct tegra_devfreq *tegra) { + int ret = 0; + + if (!tegra->started) { + tegra->started = true; + + ret = tegra_actmon_resume(tegra); + if (ret) + tegra->started = false; + } + + return ret; +} + +static void tegra_actmon_pause(struct tegra_devfreq *tegra) +{ + if (!tegra->devfreq->profile->polling_ms || !tegra->started) + return; + disable_irq(tegra->irq); cpufreq_unregister_notifier(&tegra->cpu_rate_change_nb, @@ -572,6 +603,12 @@ static void tegra_actmon_stop(struct tegra_devfreq *tegra) clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb); } +static void tegra_actmon_stop(struct tegra_devfreq *tegra) +{ + tegra_actmon_pause(tegra); + tegra->started = false; +} + static int tegra_devfreq_target(struct device *dev, unsigned long *freq, u32 flags) { @@ -629,7 +666,7 @@ static int tegra_devfreq_get_dev_status(struct device *dev, stat->busy_time *= 100 / BUS_SATURATION_RATIO; /* Number of cycles in a sampling period */ - stat->total_time = ACTMON_SAMPLING_PERIOD * cur_freq; + stat->total_time = tegra->devfreq->profile->polling_ms * cur_freq; stat->busy_time = min(stat->busy_time, stat->total_time); @@ -637,7 +674,7 @@ static int tegra_devfreq_get_dev_status(struct device *dev, } static struct devfreq_dev_profile tegra_devfreq_profile = { - .polling_ms = 0, + .polling_ms = ACTMON_SAMPLING_PERIOD, .target = tegra_devfreq_target, .get_dev_status = tegra_devfreq_get_dev_status, }; @@ -677,6 +714,7 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, unsigned int event, void *data) { struct tegra_devfreq *tegra = dev_get_drvdata(devfreq->dev.parent); + unsigned int *new_delay = data; int ret = 0; /* @@ -696,6 +734,21 @@ static int tegra_governor_event_handler(struct devfreq *devfreq, devfreq_monitor_stop(devfreq); break; + case DEVFREQ_GOV_INTERVAL: + /* + * ACTMON hardware supports up to 256 milliseconds for the + * sampling period. + */ + if (*new_delay > 256) { + ret = -EINVAL; + break; + } + + tegra_actmon_pause(tegra); + devfreq_interval_update(devfreq, new_delay); + ret = tegra_actmon_resume(tegra); + break; + case DEVFREQ_GOV_SUSPEND: tegra_actmon_stop(tegra); devfreq_monitor_suspend(devfreq); @@ -715,6 +768,7 @@ static struct devfreq_governor tegra_devfreq_governor = { .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) From fee22854c0273569836de2039d9c432ea4df2cfc Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 5 Nov 2019 00:56:16 +0300 Subject: [PATCH 25/25] PM / devfreq: tegra30: Tune up MCCPU boost-down coefficient MCCPU boosts up very aggressively by 800% and boosts down very mildly by 10%. This doesn't work well when system is idling because the very slow de-boosting results in lots of consecutive-down interrupts, in result memory stays clocked high and CPU doesn't enter deepest idling state instead of keeping memory at lowest freq and having CPU cluster turned off. A more faster de-boosting fixes the case of idling system and doesn't affect the case of an active system. Reviewed-by: Chanwoo Choi Signed-off-by: Dmitry Osipenko Signed-off-by: Chanwoo Choi --- drivers/devfreq/tegra30-devfreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index e44f1a48f838..0b65f89d74d5 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -124,7 +124,7 @@ static const struct tegra_devfreq_device_config actmon_device_configs[] = { .offset = 0x200, .irq_mask = 1 << 25, .boost_up_coeff = 800, - .boost_down_coeff = 90, + .boost_down_coeff = 40, .boost_up_threshold = 27, .boost_down_threshold = 10, .avg_dependency_threshold = 16000, /* 16MHz in kHz units */