M7350/kernel/drivers/gpu/msm/kgsl_pwrscale_idlestats.c
2024-09-09 08:52:07 +00:00

233 lines
6.1 KiB
C

/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/idle_stats_device.h>
#include <linux/cpufreq.h>
#include <linux/notifier.h>
#include <linux/cpumask.h>
#include <linux/tick.h>
#include "kgsl.h"
#include "kgsl_pwrscale.h"
#include "kgsl_device.h"
#define MAX_CORES 4
struct _cpu_info {
spinlock_t lock;
struct notifier_block cpu_nb;
u64 start[MAX_CORES];
u64 end[MAX_CORES];
int curr_freq[MAX_CORES];
int max_freq[MAX_CORES];
};
struct idlestats_priv {
char name[32];
struct msm_idle_stats_device idledev;
struct kgsl_device *device;
struct msm_idle_pulse pulse;
struct _cpu_info cpu_info;
};
static int idlestats_cpufreq_notifier(
struct notifier_block *nb,
unsigned long val, void *data)
{
struct _cpu_info *cpu = container_of(nb,
struct _cpu_info, cpu_nb);
struct cpufreq_freqs *freq = data;
if (val != CPUFREQ_POSTCHANGE)
return 0;
spin_lock(&cpu->lock);
if (freq->cpu < num_possible_cpus())
cpu->curr_freq[freq->cpu] = freq->new / 1000;
spin_unlock(&cpu->lock);
return 0;
}
static void idlestats_get_sample(struct msm_idle_stats_device *idledev,
struct msm_idle_pulse *pulse)
{
struct kgsl_power_stats stats;
struct idlestats_priv *priv = container_of(idledev,
struct idlestats_priv, idledev);
struct kgsl_device *device = priv->device;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
mutex_lock(&device->mutex);
/* If the GPU is asleep, don't wake it up - assume that we
are idle */
if (device->state == KGSL_STATE_ACTIVE) {
device->ftbl->power_stats(device, &stats);
pulse->busy_start_time = pwr->time - stats.busy_time;
pulse->busy_interval = stats.busy_time;
} else {
pulse->busy_start_time = pwr->time;
pulse->busy_interval = 0;
}
pulse->wait_interval = 0;
mutex_unlock(&device->mutex);
}
static void idlestats_busy(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv = pwrscale->priv;
struct kgsl_power_stats stats;
int i, busy, nr_cpu = 1;
if (priv->pulse.busy_start_time != 0) {
priv->pulse.wait_interval = 0;
/* Calculate the total CPU busy time for this GPU pulse */
for (i = 0; i < num_possible_cpus(); i++) {
spin_lock(&priv->cpu_info.lock);
if (cpu_online(i)) {
priv->cpu_info.end[i] =
(u64)ktime_to_us(ktime_get()) -
get_cpu_idle_time_us(i, NULL);
busy = priv->cpu_info.end[i] -
priv->cpu_info.start[i];
/* Normalize the busy time by frequency */
busy = priv->cpu_info.curr_freq[i] *
(busy / priv->cpu_info.max_freq[i]);
priv->pulse.wait_interval += busy;
nr_cpu++;
}
spin_unlock(&priv->cpu_info.lock);
}
priv->pulse.wait_interval /= nr_cpu;
/* This is called from within a mutex protected function, so
no additional locking required */
device->ftbl->power_stats(device, &stats);
/* If total_time is zero, then we don't have
any interesting statistics to store */
if (stats.total_time == 0) {
priv->pulse.busy_start_time = 0;
return;
}
priv->pulse.busy_interval = stats.busy_time;
msm_idle_stats_idle_end(&priv->idledev, &priv->pulse);
}
priv->pulse.busy_start_time = ktime_to_us(ktime_get());
}
static void idlestats_idle(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
int i, nr_cpu;
struct idlestats_priv *priv = pwrscale->priv;
nr_cpu = num_possible_cpus();
for (i = 0; i < nr_cpu; i++)
if (cpu_online(i))
priv->cpu_info.start[i] =
(u64)ktime_to_us(ktime_get()) -
get_cpu_idle_time_us(i, NULL);
msm_idle_stats_idle_start(&priv->idledev);
}
static void idlestats_sleep(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv = pwrscale->priv;
msm_idle_stats_update_event(&priv->idledev,
MSM_IDLE_STATS_EVENT_IDLE_TIMER_EXPIRED);
}
static void idlestats_wake(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
/* Use highest perf level on wake-up from
sleep for better performance */
kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO);
}
static int idlestats_init(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv;
struct cpufreq_policy cpu_policy;
int ret, i;
priv = pwrscale->priv = kzalloc(sizeof(struct idlestats_priv),
GFP_KERNEL);
if (pwrscale->priv == NULL)
return -ENOMEM;
snprintf(priv->name, sizeof(priv->name), "idle_stats_%s",
device->name);
priv->device = device;
priv->idledev.name = (const char *) priv->name;
priv->idledev.get_sample = idlestats_get_sample;
spin_lock_init(&priv->cpu_info.lock);
priv->cpu_info.cpu_nb.notifier_call =
idlestats_cpufreq_notifier;
ret = cpufreq_register_notifier(&priv->cpu_info.cpu_nb,
CPUFREQ_TRANSITION_NOTIFIER);
if (ret)
goto err;
for (i = 0; i < num_possible_cpus(); i++) {
cpufreq_frequency_table_cpuinfo(&cpu_policy,
cpufreq_frequency_get_table(i));
priv->cpu_info.max_freq[i] = cpu_policy.max / 1000;
priv->cpu_info.curr_freq[i] = cpu_policy.max / 1000;
}
ret = msm_idle_stats_register_device(&priv->idledev);
err:
if (ret) {
kfree(pwrscale->priv);
pwrscale->priv = NULL;
}
return ret;
}
static void idlestats_close(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv = pwrscale->priv;
if (pwrscale->priv == NULL)
return;
cpufreq_unregister_notifier(&priv->cpu_info.cpu_nb,
CPUFREQ_TRANSITION_NOTIFIER);
msm_idle_stats_deregister_device(&priv->idledev);
kfree(pwrscale->priv);
pwrscale->priv = NULL;
}
struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats = {
.name = "idlestats",
.init = idlestats_init,
.idle = idlestats_idle,
.busy = idlestats_busy,
.sleep = idlestats_sleep,
.wake = idlestats_wake,
.close = idlestats_close
};