541 lines
14 KiB
C
541 lines
14 KiB
C
/*
|
|
* Tegra Graphics Host Actmon support for T124
|
|
*
|
|
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* 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/host1x_actmon.h"
|
|
|
|
void actmon_writel(struct host1x_actmon *actmon, u32 val, u32 reg)
|
|
{
|
|
nvhost_dbg(dbg_reg, " r=0x%x v=0x%x", reg, val);
|
|
writel(val, actmon->regs + reg);
|
|
}
|
|
|
|
u32 actmon_readl(struct host1x_actmon *actmon, u32 reg)
|
|
{
|
|
u32 val = readl(actmon->regs + reg);
|
|
nvhost_dbg(dbg_reg, " r=0x%x v=0x%x", reg, val);
|
|
return val;
|
|
}
|
|
/*
|
|
* actmon_update_sample_period_safe(host)
|
|
*
|
|
* This function updates frequency specific values on actmon using the current
|
|
* actmon frequency. The function should be called only when host1x is active.
|
|
*
|
|
*/
|
|
|
|
static void actmon_update_sample_period_safe(struct host1x_actmon *actmon)
|
|
{
|
|
long freq_mhz, clks_per_sample;
|
|
u32 val;
|
|
|
|
/* We use MHz and us instead of Hz and s due to numerical limitations */
|
|
freq_mhz = clk_get_rate(actmon->clk) / 1000000;
|
|
clks_per_sample = freq_mhz * actmon->usecs_per_sample;
|
|
actmon->clks_per_sample = clks_per_sample;
|
|
|
|
val = actmon_readl(actmon, actmon_ctrl_r());
|
|
val &= ~actmon_ctrl_sample_period_m();
|
|
val |= actmon_ctrl_sample_period_f(clks_per_sample);
|
|
actmon_writel(actmon, val, actmon_ctrl_r());
|
|
|
|
/* AVG value depends on sample period => clear it */
|
|
actmon_writel(actmon, 0, actmon_init_avg_r());
|
|
}
|
|
|
|
static int host1x_actmon_init(struct host1x_actmon *actmon)
|
|
{
|
|
struct platform_device *pdev = actmon->host->dev;
|
|
struct nvhost_device_data *pdata =
|
|
(struct nvhost_device_data *)platform_get_drvdata(pdev);
|
|
u32 val;
|
|
|
|
if (actmon->init == ACTMON_READY)
|
|
return 0;
|
|
|
|
if (actmon->init == ACTMON_OFF) {
|
|
actmon->usecs_per_sample = 10;
|
|
actmon->above_wmark = 0;
|
|
actmon->below_wmark = 0;
|
|
actmon->k = 1;
|
|
}
|
|
|
|
actmon->clk = pdata->clk[1];
|
|
if (!actmon->clk)
|
|
return -ENODEV;
|
|
|
|
nvhost_module_busy(actmon->host->dev);
|
|
|
|
/* Clear average and control registers */
|
|
actmon_writel(actmon, 0, actmon_init_avg_r());
|
|
actmon_writel(actmon, 0, actmon_ctrl_r());
|
|
|
|
/* Write (normalised) sample period. */
|
|
actmon_update_sample_period_safe(actmon);
|
|
|
|
/* Clear interrupt status */
|
|
actmon_writel(actmon, 0xffffffff, actmon_intr_status_r());
|
|
|
|
val = actmon_readl(actmon, actmon_ctrl_r());
|
|
val |= actmon_ctrl_enb_periodic_f(1);
|
|
val |= actmon_ctrl_k_val_f(actmon->k);
|
|
val |= actmon_ctrl_actmon_enable_f(1);
|
|
actmon_writel(actmon, val, actmon_ctrl_r());
|
|
|
|
nvhost_module_idle(actmon->host->dev);
|
|
|
|
actmon->init = ACTMON_READY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void host1x_actmon_deinit(struct host1x_actmon *actmon)
|
|
{
|
|
if (actmon->init != ACTMON_READY)
|
|
return;
|
|
|
|
actmon->init = ACTMON_SLEEP;
|
|
|
|
/* Disable actmon and clear interrupt status */
|
|
nvhost_module_busy(actmon->host->dev);
|
|
actmon_writel(actmon, 0, actmon_ctrl_r());
|
|
actmon_writel(actmon, 0xffffffff, actmon_intr_status_r());
|
|
nvhost_module_idle(actmon->host->dev);
|
|
}
|
|
|
|
static int host1x_actmon_avg(struct host1x_actmon *actmon, u32 *val)
|
|
{
|
|
if (actmon->init != ACTMON_READY) {
|
|
*val = 0;
|
|
return 0;
|
|
}
|
|
|
|
nvhost_module_busy(actmon->host->dev);
|
|
*val = actmon_readl(actmon, actmon_avg_count_r());
|
|
nvhost_module_idle(actmon->host->dev);
|
|
rmb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int host1x_actmon_avg_norm(struct host1x_actmon *actmon, u32 *avg)
|
|
{
|
|
long val;
|
|
|
|
if (!(actmon->init == ACTMON_READY && actmon->clks_per_sample > 0)) {
|
|
*avg = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Read load from hardware */
|
|
nvhost_module_busy(actmon->host->dev);
|
|
val = actmon_readl(actmon, actmon_avg_count_r());
|
|
nvhost_module_idle(actmon->host->dev);
|
|
rmb();
|
|
|
|
*avg = (val * 1000) / actmon->clks_per_sample;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int host1x_actmon_count(struct host1x_actmon *actmon, u32 *val)
|
|
{
|
|
if (actmon->init != ACTMON_READY) {
|
|
*val = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Read load from hardware */
|
|
nvhost_module_busy(actmon->host->dev);
|
|
*val = actmon_readl(actmon, actmon_count_r());
|
|
nvhost_module_idle(actmon->host->dev);
|
|
rmb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int host1x_actmon_count_norm(struct host1x_actmon *actmon, u32 *avg)
|
|
{
|
|
long val;
|
|
|
|
if (actmon->init != ACTMON_READY) {
|
|
*avg = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Read load from hardware */
|
|
nvhost_module_busy(actmon->host->dev);
|
|
val = actmon_readl(actmon, actmon_count_r());
|
|
nvhost_module_idle(actmon->host->dev);
|
|
rmb();
|
|
|
|
*avg = (val * 1000) / actmon->clks_per_sample;
|
|
|
|
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)
|
|
{
|
|
/* No sense to update actmon if actmon is inactive */
|
|
if (actmon->init != ACTMON_READY)
|
|
return;
|
|
|
|
nvhost_module_busy(actmon->host->dev);
|
|
actmon_update_sample_period_safe(actmon);
|
|
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)
|
|
{
|
|
long val;
|
|
|
|
actmon->k = k;
|
|
|
|
nvhost_module_busy(actmon->host->dev);
|
|
val = actmon_readl(actmon, actmon_ctrl_r());
|
|
val &= ~(actmon_ctrl_k_val_m());
|
|
val |= actmon_ctrl_k_val_f(actmon->k);
|
|
actmon_writel(actmon, val, actmon_ctrl_r());
|
|
nvhost_module_idle(actmon->host->dev);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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_count_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct host1x_actmon *actmon = s->private;
|
|
u32 avg;
|
|
int err;
|
|
|
|
err = host1x_actmon_count(actmon, &avg);
|
|
if (!err)
|
|
seq_printf(s, "%d\n", avg);
|
|
return err;
|
|
}
|
|
|
|
static int actmon_count_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, actmon_count_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations actmon_count_fops = {
|
|
.open = actmon_count_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int actmon_count_norm_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct host1x_actmon *actmon = s->private;
|
|
u32 avg;
|
|
int err;
|
|
|
|
err = host1x_actmon_count_norm(actmon, &avg);
|
|
if (!err)
|
|
seq_printf(s, "%d\n", avg);
|
|
return err;
|
|
}
|
|
|
|
static int actmon_count_norm_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, actmon_count_norm_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations actmon_count_norm_fops = {
|
|
.open = actmon_count_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_count", S_IRUGO, de,
|
|
actmon, &actmon_count_fops);
|
|
debugfs_create_file("actmon_count_norm", S_IRUGO, de,
|
|
actmon, &actmon_count_norm_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,
|
|
};
|