522 lines
14 KiB
C

/*
* Tegra Graphics Host Actmon support for T114
*
* Copyright (c) 2012-2013, NVIDIA Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/nvhost.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include "dev.h"
#include "chip_support.h"
#include "host1x_actmon.h"
/*
* host1x_actmon_update_sample_period_safe(host)
*
* This function updates frequency specific values on actmon using the current
* host1x frequency. The function should be called only when host1x is active.
*
* Actmon takes samples 3d activity every clock cycle. If 3d is active,
* the internal counter is increased by one. Depending on value of
* host1x_sync_actmon_status_gr3d_mon_act_f(x), these internal values
* grow either to 255 (1) or 65535 (0). After growing up to this value,
* the actual counter is increased by one.
*
* Each period takes a number of counter increments. If we wish a period
* to be a known time, we get the number of counter increments by:
*
* f_{host}
* clks = --------- * t_{sample_period}.
* 256
*
* This is used as a base value for determining a proper value for raw sample
* period value.
*/
static void host1x_actmon_update_sample_period_safe(
struct host1x_actmon *actmon)
{
struct nvhost_master *host = actmon->host;
void __iomem *sync_regs = host->sync_aperture;
long freq_mhz, clks_per_sample;
struct nvhost_device_data *pdata = platform_get_drvdata(host->dev);
/* We use MHz and us instead of Hz and s due to numerical limitations */
freq_mhz = clk_get_rate(pdata->clk[0]) / 1000000;
clks_per_sample = (freq_mhz * actmon->usecs_per_sample) / 256;
actmon->clks_per_sample = clks_per_sample;
writel(host1x_sync_actmon_status_sample_period_f(clks_per_sample)
| host1x_sync_actmon_status_status_source_f(
host1x_sync_actmon_status_status_source_usec_v()),
sync_regs + host1x_sync_actmon_status_r());
}
static int host1x_actmon_init(struct host1x_actmon *actmon)
{
struct nvhost_master *host = actmon->host;
void __iomem *sync_regs = host->sync_aperture;
u32 val;
unsigned long timeout = jiffies + msecs_to_jiffies(25);
if (actmon->init == ACTMON_READY)
return 0;
nvhost_module_busy(host->dev);
if (actmon->init == ACTMON_OFF) {
actmon->usecs_per_sample = 160;
actmon->above_wmark = 0;
actmon->below_wmark = 0;
actmon->k = 6;
}
/* Initialize average */
writel(0, sync_regs + host1x_sync_actmon_init_avg_r());
/* Default count weight - 1 for per unit actmons */
writel(1, sync_regs + host1x_sync_actmon_count_weight_r());
/* Wait for actmon to be disabled */
do {
val = readl(sync_regs + host1x_sync_actmon_status_r());
} while (!time_after(jiffies, timeout) &&
val & host1x_sync_actmon_status_gr3d_mon_act_f(1));
WARN_ON(time_after(jiffies, timeout));
/* Write (normalised) sample period. */
host1x_actmon_update_sample_period_safe(actmon);
/* Clear interrupt status */
writel(0xffffffff, sync_regs + host1x_sync_actmon_intr_status_r());
val = readl(sync_regs + host1x_sync_actmon_ctrl_r());
/* Enable periodic mode */
val |= host1x_sync_actmon_ctrl_enb_periodic_f(1);
/* Moving avg IIR filter window size 2^6=128 */
val |= host1x_sync_actmon_ctrl_k_val_f(actmon->k);
/* Enable ACTMON */
val |= host1x_sync_actmon_ctrl_enb_f(1);
writel(val, sync_regs + host1x_sync_actmon_ctrl_r());
actmon->init = ACTMON_READY;
nvhost_module_idle(host->dev);
return 0;
}
static void host1x_actmon_deinit(struct host1x_actmon *actmon)
{
struct nvhost_master *host = actmon->host;
void __iomem *sync_regs = host->sync_aperture;
u32 val;
if (actmon->init != ACTMON_READY)
return;
nvhost_module_busy(host->dev);
/* Disable actmon */
val = readl(sync_regs + host1x_sync_actmon_ctrl_r());
val &= ~host1x_sync_actmon_ctrl_enb_m();
val &= ~host1x_sync_actmon_ctrl_enb_periodic_m();
val &= ~host1x_sync_actmon_ctrl_avg_above_wmark_en_m();
val &= ~host1x_sync_actmon_ctrl_avg_below_wmark_en_m();
writel(val, sync_regs + host1x_sync_actmon_ctrl_r());
/* Write sample period */
writel(host1x_sync_actmon_status_sample_period_f(0)
| host1x_sync_actmon_status_status_source_f(
host1x_sync_actmon_status_status_source_usec_v()),
sync_regs + host1x_sync_actmon_status_r());
/* Clear interrupt status */
writel(0xffffffff, sync_regs + host1x_sync_actmon_intr_status_r());
actmon->init = ACTMON_SLEEP;
nvhost_module_idle(host->dev);
}
static int host1x_actmon_avg(struct host1x_actmon *actmon, u32 *val)
{
struct nvhost_master *host = actmon->host;
void __iomem *sync_regs = host->sync_aperture;
if (actmon->init != ACTMON_READY) {
*val = 0;
return 0;
}
nvhost_module_busy(host->dev);
*val = readl(sync_regs + host1x_sync_actmon_avg_count_r());
nvhost_module_idle(host->dev);
rmb();
return 0;
}
static int host1x_actmon_avg_norm(struct host1x_actmon *actmon, u32 *avg)
{
struct nvhost_master *host = actmon->host;
void __iomem *sync_regs = host->sync_aperture;
long val;
if (actmon->init != ACTMON_READY) {
*avg = 0;
return 0;
}
nvhost_module_busy(host->dev);
/* Read load from hardware */
val = readl(sync_regs + host1x_sync_actmon_avg_count_r());
/* Undocumented feature: AVG value is not scaled. */
*avg = (val * 1000) / ((1 + actmon->clks_per_sample) * 256);
nvhost_module_idle(host->dev);
rmb();
return 0;
}
static int host1x_actmon_above_wmark_count(struct host1x_actmon *actmon)
{
return actmon->above_wmark;
}
static int host1x_actmon_below_wmark_count(struct host1x_actmon *actmon)
{
return actmon->below_wmark;
}
static void host1x_actmon_update_sample_period(struct host1x_actmon *actmon)
{
void __iomem *sync_regs = actmon->host->sync_aperture;
long old_clks_per_sample;
long ratio, avg;
u32 val;
unsigned long timeout = jiffies + msecs_to_jiffies(25);
/* No sense to update actmon if actmon is inactive */
if (actmon->init != ACTMON_READY)
return;
nvhost_module_busy(actmon->host->dev);
/* Disable actmon */
val = readl(sync_regs + host1x_sync_actmon_ctrl_r());
val &= ~host1x_sync_actmon_ctrl_enb_m();
val &= ~host1x_sync_actmon_ctrl_enb_periodic_m();
writel(val, sync_regs + host1x_sync_actmon_ctrl_r());
/* Calculate ratio between old and new clks_per_sample */
old_clks_per_sample = actmon->clks_per_sample;
host1x_actmon_update_sample_period_safe(actmon);
ratio = (actmon->clks_per_sample * 1000) / old_clks_per_sample;
/* Scale the avg to match new clock */
avg = readl(sync_regs + host1x_sync_actmon_avg_count_r());
avg *= ratio;
avg /= 1000;
writel(avg, sync_regs + host1x_sync_actmon_init_avg_r());
/* Wait for actmon to be disabled */
do {
val = readl(sync_regs + host1x_sync_actmon_status_r());
} while (!time_after(jiffies, timeout) &&
val & host1x_sync_actmon_status_gr3d_mon_act_f(1));
/* Re-enable actmon - this will latch the init value to avg reg */
val |= host1x_sync_actmon_ctrl_enb_m();
val |= host1x_sync_actmon_ctrl_enb_periodic_m();
writel(val, sync_regs + host1x_sync_actmon_ctrl_r());
nvhost_module_idle(actmon->host->dev);
}
static void host1x_actmon_set_sample_period_norm(struct host1x_actmon *actmon,
long usecs)
{
actmon->usecs_per_sample = usecs;
host1x_actmon_update_sample_period(actmon);
}
static void host1x_actmon_set_k(struct host1x_actmon *actmon, u32 k)
{
void __iomem *sync_regs = actmon->host->sync_aperture;
long val;
actmon->k = k;
val = readl(sync_regs + host1x_sync_actmon_ctrl_r());
val &= ~(host1x_sync_actmon_ctrl_k_val_m());
val |= host1x_sync_actmon_ctrl_k_val_f(actmon->k);
writel(val, sync_regs + host1x_sync_actmon_ctrl_r());
}
static u32 host1x_actmon_get_k(struct host1x_actmon *actmon)
{
return actmon->k;
}
static long host1x_actmon_get_sample_period(struct host1x_actmon *actmon)
{
return actmon->clks_per_sample;
}
static long host1x_actmon_get_sample_period_norm(struct host1x_actmon *actmon)
{
return actmon->usecs_per_sample;
}
/*
* Debugfs functions
*/
static int actmon_below_wmark_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
seq_printf(s, "%d\n", host1x_actmon_below_wmark_count(actmon));
return 0;
}
static int actmon_below_wmark_open(struct inode *inode, struct file *file)
{
return single_open(file, actmon_below_wmark_show, inode->i_private);
}
static const struct file_operations actmon_below_wmark_fops = {
.open = actmon_below_wmark_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int actmon_above_wmark_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
seq_printf(s, "%d\n", host1x_actmon_above_wmark_count(actmon));
return 0;
}
static int actmon_above_wmark_open(struct inode *inode, struct file *file)
{
return single_open(file, actmon_above_wmark_show, inode->i_private);
}
static const struct file_operations actmon_above_wmark_fops = {
.open = actmon_above_wmark_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int actmon_avg_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
u32 avg;
int err;
err = host1x_actmon_avg(actmon, &avg);
if (!err)
seq_printf(s, "%d\n", avg);
return err;
}
static int actmon_avg_open(struct inode *inode, struct file *file)
{
return single_open(file, actmon_avg_show, inode->i_private);
}
static const struct file_operations actmon_avg_fops = {
.open = actmon_avg_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int actmon_avg_norm_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
u32 avg;
int err;
err = host1x_actmon_avg_norm(actmon, &avg);
if (!err)
seq_printf(s, "%d\n", avg);
return err;
}
static int actmon_avg_norm_open(struct inode *inode, struct file *file)
{
return single_open(file, actmon_avg_norm_show, inode->i_private);
}
static const struct file_operations actmon_avg_norm_fops = {
.open = actmon_avg_norm_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int actmon_sample_period_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
long period = host1x_actmon_get_sample_period(actmon);
seq_printf(s, "%ld\n", period);
return 0;
}
static int actmon_sample_period_open(struct inode *inode, struct file *file)
{
return single_open(file, actmon_sample_period_show, inode->i_private);
}
static const struct file_operations actmon_sample_period_fops = {
.open = actmon_sample_period_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int actmon_sample_period_norm_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
long period = host1x_actmon_get_sample_period_norm(actmon);
seq_printf(s, "%ld\n", period);
return 0;
}
static int actmon_sample_period_norm_open(struct inode *inode,
struct file *file)
{
return single_open(file, actmon_sample_period_norm_show,
inode->i_private);
}
static ssize_t actmon_sample_period_norm_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct host1x_actmon *actmon = s->private;
char buffer[40];
int buf_size;
unsigned long period;
memset(buffer, 0, sizeof(buffer));
buf_size = min(count, (sizeof(buffer)-1));
if (copy_from_user(buffer, user_buf, buf_size))
return -EFAULT;
if (kstrtoul(buffer, 10, &period))
return -EINVAL;
host1x_actmon_set_sample_period_norm(actmon, period);
return count;
}
static const struct file_operations actmon_sample_period_norm_fops = {
.open = actmon_sample_period_norm_open,
.read = seq_read,
.write = actmon_sample_period_norm_write,
.llseek = seq_lseek,
.release = single_release,
};
static int actmon_k_show(struct seq_file *s, void *unused)
{
struct host1x_actmon *actmon = s->private;
long period = host1x_actmon_get_k(actmon);
seq_printf(s, "%ld\n", period);
return 0;
}
static int actmon_k_open(struct inode *inode, struct file *file)
{
return single_open(file, actmon_k_show, inode->i_private);
}
static ssize_t actmon_k_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct host1x_actmon *actmon = s->private;
char buffer[40];
int buf_size;
unsigned long k;
memset(buffer, 0, sizeof(buffer));
buf_size = min(count, (sizeof(buffer)-1));
if (copy_from_user(buffer, user_buf, buf_size))
return -EFAULT;
if (kstrtoul(buffer, 10, &k))
return -EINVAL;
host1x_actmon_set_k(actmon, k);
return count;
}
static const struct file_operations actmon_k_fops = {
.open = actmon_k_open,
.read = seq_read,
.write = actmon_k_write,
.llseek = seq_lseek,
.release = single_release,
};
static void host1x_actmon_debug_init(struct host1x_actmon *actmon,
struct dentry *de)
{
BUG_ON(!actmon);
debugfs_create_file("actmon_k", S_IRUGO, de,
actmon, &actmon_k_fops);
debugfs_create_file("actmon_sample_period", S_IRUGO, de,
actmon, &actmon_sample_period_fops);
debugfs_create_file("actmon_sample_period_norm", S_IRUGO, de,
actmon, &actmon_sample_period_norm_fops);
debugfs_create_file("actmon_avg_norm", S_IRUGO, de,
actmon, &actmon_avg_norm_fops);
debugfs_create_file("actmon_avg", S_IRUGO, de,
actmon, &actmon_avg_fops);
debugfs_create_file("actmon_above_wmark", S_IRUGO, de,
actmon, &actmon_above_wmark_fops);
debugfs_create_file("actmon_below_wmark", S_IRUGO, de,
actmon, &actmon_below_wmark_fops);
}
static const struct nvhost_actmon_ops host1x_actmon_ops = {
.init = host1x_actmon_init,
.deinit = host1x_actmon_deinit,
.read_avg = host1x_actmon_avg,
.above_wmark_count = host1x_actmon_above_wmark_count,
.below_wmark_count = host1x_actmon_below_wmark_count,
.read_avg_norm = host1x_actmon_avg_norm,
.update_sample_period = host1x_actmon_update_sample_period,
.set_sample_period_norm = host1x_actmon_set_sample_period_norm,
.get_sample_period_norm = host1x_actmon_get_sample_period_norm,
.get_sample_period = host1x_actmon_get_sample_period,
.get_k = host1x_actmon_get_k,
.set_k = host1x_actmon_set_k,
.debug_init = host1x_actmon_debug_init,
};