Update devfreq for 5.5
Detailed description for this pull request: 1. Update devfreq core - Check NULL governor in available_governors_show sysfs in order to prevent showing the wrong governor information. - Fix the race condition between devfreq_update_status() and trans_stat_show() - Add new 'interrupt-driven' flag for devfreq goveror. Each devfreq driver can add the their own governor (NIVIDIA Tegra30 ACTMON governor) which is interrupt-driven governor. It needs to use the following sysfs interface to get the new polling interval in order to change the NVIDIA Tegra30 hardware's polling interval. : /sys/class/devfreq/devfreqX/polling_interval So, if 'interrupt-driven' flag of devfreq governor is 1, the devfreq governor is able to use the 'polling_interval' sysfs interface to get the new polling interval value. But, the devfreq core doesn't schedule out the polling work for this governor like NVIDIA Tegra30 ACTMON governor. 2. Update devfreq driver - For exynos-bus.c, remove unused property from dt-binding documentation - For tegra30-devfreq.c, update the internal behavior like fixing the overflow integer issue and clean-up code. 3. Update devfreq-event driver - For exynos-ppmu.c, add exynos_ppmu.h dt-binding file for 'event-data-type' filed. and update dt-binging documentation. Also, Fix minor coding style. -----BEGIN PGP SIGNATURE----- iQJKBAABCgA0FiEEsSpuqBtbWtRe4rLGnM3fLN7rz1MFAl3DkgMWHGN3MDAuY2hv aUBzYW1zdW5nLmNvbQAKCRCczd8s3uvPU++zD/9sQWoJCap6a8sill+P1gXB0IJw cPu4U35tUag6CphuT6DYRpljXQGTGLhUriP8xJ5RP2BNgMzqhPzlI+CiODgoJZvv PfimRi9KBK4ayavKYt3OI6t+O4ULihoglPHUuc2F4o1jPPUAki7Y82ihTzBlZ+U8 702fsibivKPP3c2rSugo3cfO5E/Pb4ITSOWTnuoEEsSV3TfbcH6ThK2Cisu5SY83 XgQIs+G0FWRJZj/TCEghcq43Ccgac1njS+Wat7RqeC29vjGxxsUGo+TBo5CLSgWl FeD9W6DzNwA6dv7SNetYUPQRycZydty8ItmvZP6Y2o/eLIiqePcjcxfAFlxcQAQA qbwRMIsZRlYZ3ZeIJkhAJnkbYNalHuRU3QPPAEtIgnzabJqWYeVoY2nvYzh8nr6F 2ksCY2ufqcAjuXxoiRbj4PvMWJhdeKMyIuP9NGKIhqAc749MjexoZz0+gl4dD56X k9d94WV8NwRMguWr7UNuiLQIqAYJeQJta4V5yT6qnxmBhVuSuYPNzcHaGS6Sh05a JTjCsb6y5jM5fakvktAPdj2JE4ie7Dxuwf8YuYmXdY0sWskAY6vauGL7g2JrM56R GfQW8tsa0G2iPEzX8gnuH2k+tBLO8NnctC01iKm7W4S2epMB8uGDzHob9jSk3hq4 5NwbEVDWuKMlKfJY4Q== =LMM9 -----END PGP SIGNATURE----- Merge tag 'devfreq-next-for-5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux Pull devfreq updates for v5.5 from Chanwoo Choi: "1. Update devfreq core - Check NULL governor in available_governors_show sysfs in order to prevent showing wrong governor information. - Fix race condition between devfreq_update_status() and trans_stat_show() - Add new 'interrupt-driven' flag for devfreq goveroris. Each devfreq driver can add their own interrupt-driven governor (NIVIDIA Tegra30 ACTMON governor). It needs to use the following sysfs interface to get the new polling interval in order to change the NVIDIA Tegra30 hardware's polling interval. : /sys/class/devfreq/devfreqX/polling_interval So, if 'interrupt-driven' flag of devfreq governor is 1, the devfreq governor is able to use the 'polling_interval' sysfs interface to get the new polling interval value. But, the devfreq core doesn't schedule out the polling work for this governor like NVIDIA Tegra30 ACTMON governor. 2. Update devfreq drivers - For exynos-bus.c, remove unused property from dt-binding documentation - For tegra30-devfreq.c, update the internal behavior like fixing the overflow integer issue and clean-up code. 3. Update devfreq-event driver - For exynos-ppmu.c, add exynos_ppmu.h dt-binding file for 'event-data-type' filed. and update dt-binging documentation. Also, Fix minor coding style." * tag 'devfreq-next-for-5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux: (25 commits) PM / devfreq: tegra30: Tune up MCCPU boost-down coefficient PM / devfreq: tegra30: Support variable polling interval PM / devfreq: Add new interrupt_driven flag for governors PM / devfreq: tegra30: Use kHz units for dependency threshold PM / devfreq: tegra30: Disable consecutive interrupts when appropriate PM / devfreq: tegra30: Don't enable already enabled consecutive interrupts PM / devfreq: tegra30: Include appropriate header PM / devfreq: tegra30: Constify structs PM / devfreq: tegra30: Don't enable consecutive-down interrupt on startup PM / devfreq: tegra30: Reset boosting on startup PM / devfreq: tegra30: Move clk-notifier's registration to governor's start PM / devfreq: tegra30: Use CPUFreq notifier PM / devfreq: tegra30: Use kHz units uniformly in the code PM / devfreq: tegra30: Fix integer overflow on CPU's freq max out PM / devfreq: tegra30: Drop write-barrier PM / devfreq: tegra30: Handle possible round-rate error PM / devfreq: tegra30: Keep interrupt disabled while governor is stopped PM / devfreq: tegra30: Change irq type to unsigned int PM / devfreq: exynos-ppmu: remove useless assignment PM / devfreq: Lock devfreq in trans_stat_show ...
This commit is contained in:
commit
2b328421e4
|
@ -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)>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -4778,6 +4778,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux.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/
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
@ -409,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,
|
||||
|
@ -426,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);
|
||||
|
@ -453,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);
|
||||
|
@ -473,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;
|
||||
|
||||
|
@ -509,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);
|
||||
|
@ -625,7 +643,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;
|
||||
|
@ -1195,7 +1213,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);
|
||||
/*
|
||||
|
@ -1397,12 +1415,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++)
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
#include <linux/devfreq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "governor.h"
|
||||
|
||||
|
@ -33,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
|
||||
|
@ -68,6 +72,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
|
||||
|
||||
|
@ -90,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;
|
||||
};
|
||||
|
@ -102,7 +109,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,
|
||||
|
@ -117,10 +124,10 @@ static 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 = 50000,
|
||||
.avg_dependency_threshold = 16000, /* 16MHz in kHz units */
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -156,11 +163,16 @@ 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)];
|
||||
|
||||
int irq;
|
||||
unsigned int irq;
|
||||
|
||||
bool started;
|
||||
};
|
||||
|
||||
struct tegra_actmon_emc_ratio {
|
||||
|
@ -168,8 +180,8 @@ struct tegra_actmon_emc_ratio {
|
|||
unsigned long emc_freq;
|
||||
};
|
||||
|
||||
static struct tegra_actmon_emc_ratio actmon_emc_ratios[] = {
|
||||
{ 1400000, ULONG_MAX },
|
||||
static const struct tegra_actmon_emc_ratio actmon_emc_ratios[] = {
|
||||
{ 1400000, KHZ_MAX },
|
||||
{ 1200000, 750000 },
|
||||
{ 1100000, 600000 },
|
||||
{ 1000000, 500000 },
|
||||
|
@ -199,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);
|
||||
|
@ -220,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);
|
||||
|
@ -229,12 +249,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)
|
||||
{
|
||||
|
@ -256,10 +270,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
|
||||
dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN;
|
||||
}
|
||||
} else if (intr_status & ACTMON_DEV_INTR_CONSECUTIVE_LOWER) {
|
||||
/*
|
||||
* new_boost = old_boost * down_coef
|
||||
|
@ -270,31 +284,22 @@ 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;
|
||||
else
|
||||
dev_ctrl |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
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) {
|
||||
|
@ -308,25 +313,37 @@ 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 / tegra->devfreq->profile->polling_ms;
|
||||
avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold;
|
||||
target_freq = do_percent(target_freq, avg_sustain_coef);
|
||||
|
||||
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);
|
||||
dev->target_freq = actmon_device_target_freq(tegra, dev);
|
||||
|
||||
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->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;
|
||||
|
||||
if (dev->avg_count >= dev->config->avg_dependency_threshold)
|
||||
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)
|
||||
|
@ -354,7 +371,7 @@ 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,
|
||||
static int tegra_actmon_clk_notify_cb(struct notifier_block *nb,
|
||||
unsigned long action, void *ptr)
|
||||
{
|
||||
struct clk_notifier_data *data = ptr;
|
||||
|
@ -365,7 +382,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;
|
||||
|
||||
|
@ -375,7 +392,79 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb,
|
|||
tegra_devfreq_update_wmark(tegra, dev);
|
||||
}
|
||||
|
||||
actmon_write_barrier(tegra);
|
||||
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)
|
||||
{
|
||||
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 (dev_freq < actmon_dev->config->avg_dependency_threshold)
|
||||
return 0;
|
||||
|
||||
static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq);
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -385,9 +474,12 @@ 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;
|
||||
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);
|
||||
|
@ -405,45 +497,116 @@ 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;
|
||||
|
||||
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;
|
||||
|
||||
disable_irq(tegra->irq);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1,
|
||||
static int tegra_actmon_resume(struct tegra_devfreq *tegra)
|
||||
{
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
if (!tegra->devfreq->profile->polling_ms || !tegra->started)
|
||||
return 0;
|
||||
|
||||
actmon_writel(tegra, tegra->devfreq->profile->polling_ms - 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]);
|
||||
|
||||
actmon_write_barrier(tegra);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
clk_notifier_unregister(tegra->emc_clock, &tegra->clk_rate_change_nb);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
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,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
actmon_write_barrier(tegra);
|
||||
|
||||
enable_irq(tegra->irq);
|
||||
tegra_actmon_pause(tegra);
|
||||
tegra->started = false;
|
||||
}
|
||||
|
||||
static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
|
||||
|
@ -463,7 +626,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;
|
||||
|
||||
|
@ -492,7 +655,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];
|
||||
|
||||
|
@ -503,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);
|
||||
|
||||
|
@ -511,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,
|
||||
};
|
||||
|
@ -542,7 +705,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;
|
||||
}
|
||||
|
@ -551,11 +714,19 @@ 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;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
tegra_actmon_start(tegra);
|
||||
ret = tegra_actmon_start(tegra);
|
||||
break;
|
||||
|
||||
case DEVFREQ_GOV_STOP:
|
||||
|
@ -563,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);
|
||||
|
@ -570,11 +756,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 = {
|
||||
|
@ -582,14 +768,16 @@ 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)
|
||||
{
|
||||
struct tegra_devfreq *tegra;
|
||||
struct tegra_devfreq_device *dev;
|
||||
struct tegra_devfreq *tegra;
|
||||
struct devfreq *devfreq;
|
||||
unsigned int i;
|
||||
unsigned long rate;
|
||||
long rate;
|
||||
int err;
|
||||
|
||||
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
|
||||
|
@ -618,12 +806,22 @@ 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;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -636,8 +834,13 @@ 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;
|
||||
tegra->cur_freq = clk_get_rate(tegra->emc_clock) / 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->max_freq = rate / KHZ;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) {
|
||||
dev = tegra->devices + i;
|
||||
|
@ -648,7 +851,14 @@ 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);
|
||||
|
||||
err = dev_pm_opp_add(&pdev->dev, rate, 0);
|
||||
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;
|
||||
|
@ -657,49 +867,33 @@ 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);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to register rate change notifier\n");
|
||||
goto remove_opps;
|
||||
}
|
||||
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);
|
||||
|
||||
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 = 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);
|
||||
goto remove_governor;
|
||||
}
|
||||
tegra_devfreq_profile.initial_freq /= KHZ;
|
||||
|
||||
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;
|
||||
devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile,
|
||||
"tegra_actmon", NULL);
|
||||
if (IS_ERR(devfreq)) {
|
||||
err = PTR_ERR(devfreq);
|
||||
goto remove_governor;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
remove_devfreq:
|
||||
devfreq_remove_device(tegra->devfreq);
|
||||
|
||||
remove_governor:
|
||||
devfreq_remove_governor(&tegra_devfreq_governor);
|
||||
|
||||
unreg_notifier:
|
||||
clk_notifier_unregister(tegra->emc_clock, &tegra->rate_change_nb);
|
||||
|
||||
remove_opps:
|
||||
dev_pm_opp_remove_all_dynamic(&pdev->dev);
|
||||
|
||||
|
@ -716,7 +910,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->rate_change_nb);
|
||||
dev_pm_opp_remove_all_dynamic(&pdev->dev);
|
||||
|
||||
reset_control_reset(tegra->reset);
|
||||
|
|
|
@ -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 <l.luba@partner.samsung.com>
|
||||
*/
|
||||
|
||||
#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
|
Loading…
Reference in New Issue