Merge branch 'pm-devfreq'

* pm-devfreq: (26 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:
Rafael J. Wysocki 2019-11-26 10:27:17 +01:00
commit fa6a599eb3
8 changed files with 394 additions and 130 deletions

View File

@ -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)>;
};
};
};

View File

@ -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:

View File

@ -3532,7 +3532,7 @@ BUS FREQUENCY DRIVER FOR SAMSUNG EXYNOS
M: Chanwoo Choi <cw00.choi@samsung.com>
L: linux-pm@vger.kernel.org
L: linux-samsung-soc@vger.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mzx/devfreq.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux.git
S: Maintained
F: drivers/devfreq/exynos-bus.c
F: Documentation/devicetree/bindings/devfreq/exynos-bus.txt
@ -4760,9 +4760,9 @@ F: include/linux/devcoredump.h
DEVICE FREQUENCY (DEVFREQ)
M: MyungJoo Ham <myungjoo.ham@samsung.com>
M: Kyungmin Park <kyungmin.park@samsung.com>
R: Chanwoo Choi <cw00.choi@samsung.com>
M: Chanwoo Choi <cw00.choi@samsung.com>
L: linux-pm@vger.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mzx/devfreq.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux.git
S: Maintained
F: drivers/devfreq/
F: include/linux/devfreq.h
@ -4772,10 +4772,11 @@ F: include/trace/events/devfreq.h
DEVICE FREQUENCY EVENT (DEVFREQ-EVENT)
M: Chanwoo Choi <cw00.choi@samsung.com>
L: linux-pm@vger.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mzx/devfreq.git
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/

View File

@ -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++)

View File

@ -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]);

View File

@ -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);

View File

@ -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 += 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)
@ -354,8 +371,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;
@ -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);

View File

@ -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