blob: 4f713b18f35877f91d1b9441bc928a99fa60ee0a [file] [log] [blame]
/*
* drivers/platform/tegra/tegra13_simon_graders.c
*
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/tegra-fuse.h>
#include <linux/delay.h>
#include "tegra_ism.h"
#include "tegra_apb2jtag.h"
#include "tegra_simon.h"
#define CCROC_MINI_CORE0_TOP_T132_ID 0xC0
#define CCROC_MINI_CORE0_TOP_T132_WIDTH 284
#define CCROC_MINI_CORE0_TOP_T132_CHIPLET_SEL 3
#define CCROC_MINI_CORE0_TOP_T132_ROSC_BIN_CPU_DENVER_FEU_BIN 206
#define E_TPC0_CLUSTER_BIN_T132_ID 0xC8
#define E_TPC0_CLUSTER_BIN_T132_WIDTH 46
#define E_TPC0_CLUSTER_BIN_T132_CHIPLET_SEL 2
#define E_TPC0_CLUSTER_BIN_T132_ROSC_BIN_GPU_ET0TX0A_GPCCLK_TEX_P00 2
#define FUSE_CTRL 0x0
#define FUSE_CTRL_STATE_OFFSET 16
#define FUSE_CTRL_STATE_MASK 0x1F
#define FUSE_CTRL_STATE_IDLE 0x4
#define FUSE_CTRL_CMD_OFFSET 0
#define FUSE_CTRL_CMD_MASK 0x3
#define FUSE_CTRL_CMD_READ 0x1
#define FUSE_ADDR 0x4
#define FUSE_DATA 0x8
#define FUSE_SIMON_STATE 116
#define INITIAL_SHIFT_MASK 0x1F
#define CPU_INITIAL_SHIFT_OFFSET 5
#define GPU_INITIAL_SHIFT_OFFSET 0
#define FUSE_CP_REV 0x190
#define FIXED_SCALE 14
#define TEMP_COEFF_A -49 /* -0.003 * (1 << FIXED_SCALE) */
#define TEMP_COEFF_B 17449 /* 1.065 * (1 << FIXED_SCALE) */
#define TEGRA_SIMON_THRESHOLD 54067 /* 3.3% */
DEFINE_MUTEX(tegra_simon_fuse_lock);
static void initialize_cpu_isms(void)
{
u32 buf[3];
memset(buf, 0, sizeof(buf));
/* FCCPLEX jtag_reset_clamp_en */
apb2jtag_read(0x0C, 85, 3, buf);
set_buf_bits(buf, 1, 83, 83);
apb2jtag_write(0x0C, 85, 3, buf);
}
static void reset_cpu_isms(void)
{
u32 buf[3];
memset(buf, 0, sizeof(buf));
/* FCCPLEX jtag_reset_clamp_en */
apb2jtag_read(0x0C, 85, 3, buf);
set_buf_bits(buf, 0, 83, 83);
apb2jtag_write(0x0C, 85, 3, buf);
}
/*
* Reads the CPU0 ROSC BIN ISM frequency.
* Assumes VDD_CPU is ON.
*/
static u32 read_cpu0_ism(u32 mode, u32 duration, u32 div, u32 sel)
{
u32 ret = 0;
initialize_cpu_isms();
ret = read_ism(mode, duration, div, sel,
CCROC_MINI_CORE0_TOP_T132_ROSC_BIN_CPU_DENVER_FEU_BIN,
CCROC_MINI_CORE0_TOP_T132_CHIPLET_SEL,
CCROC_MINI_CORE0_TOP_T132_WIDTH,
CCROC_MINI_CORE0_TOP_T132_ID);
reset_cpu_isms();
return ret;
}
static void initialize_gpu_isms(void)
{
u32 buf[2];
memset(buf, 0, sizeof(buf));
/* A_GPU0 power_reset_n, get the reg out of reset */
apb2jtag_read(0x25, 33, 2, buf);
set_buf_bits(buf, 1, 1, 1);
apb2jtag_write(0x25, 33, 2, buf);
}
static void reset_gpu_isms(void)
{
u32 buf[2];
memset(buf, 0, sizeof(buf));
/*
* A_GPU0 power_reset_n, put back the reg in reset otherwise
* it will be in a bad state after the domain unpowergates
*/
apb2jtag_read(0x25, 33, 2, buf);
set_buf_bits(buf, 0, 1, 1);
apb2jtag_write(0x25, 33, 2, buf);
}
/*
* Reads the GPU ROSC BIN ISM frequency.
* Assumes VDD_GPU is ON.
*/
static u32 read_gpu_ism(u32 mode, u32 duration, u32 div, u32 sel)
{
u32 ret = 0;
initialize_gpu_isms();
ret = read_ism(mode, duration, div, sel,
E_TPC0_CLUSTER_BIN_T132_ROSC_BIN_GPU_ET0TX0A_GPCCLK_TEX_P00,
E_TPC0_CLUSTER_BIN_T132_CHIPLET_SEL,
E_TPC0_CLUSTER_BIN_T132_WIDTH,
E_TPC0_CLUSTER_BIN_T132_ID);
reset_gpu_isms();
return ret;
}
struct volt_scale_entry {
int mv;
int scale;
};
static struct volt_scale_entry volt_scale_table[] = {
[0] = {
.mv = 800,
.scale = 16384, /* 1 << FIXED_SCALE */
},
[1] = {
.mv = 900,
.scale = 11469, /* 0.7 * (1 << FIXED_SCALE) */
},
[2] = {
.mv = 1000,
.scale = 8356, /* 0.51 * (1 << FIXED_SCALE) */
},
};
static s64 scale_voltage(int mv, s64 num)
{
int i;
s64 scale;
for (i = 1; i < ARRAY_SIZE(volt_scale_table); i++) {
if (volt_scale_table[i].mv >= mv)
break;
}
/* Invalid voltage */
WARN_ON(i == ARRAY_SIZE(volt_scale_table));
if (i == ARRAY_SIZE(volt_scale_table)) {
do_div(num, volt_scale_table[i - 1].scale);
return num;
}
/* Interpolate/Extrapolate for exacte scale value */
scale = (volt_scale_table[i].scale - volt_scale_table[i - 1].scale) /
(volt_scale_table[i].mv - volt_scale_table[i - 1].mv);
scale = scale * (mv - volt_scale_table[i - 1].mv) +
volt_scale_table[i - 1].scale;
do_div(num, scale);
return num;
}
static s64 scale_temp(int temperature_mc, s64 num)
{
/* num / (a * T + b) */
int sign = 1;
s64 scale = TEMP_COEFF_A;
scale = scale * temperature_mc;
if (scale < 0) {
sign = -1;
scale = scale * sign;
}
do_div(scale, 1000);
scale = scale * sign;
scale += TEMP_COEFF_B;
do_div(num, scale);
return num;
}
#define FUSE_TIMEOUT 20
static u32 get_tegra_simon_fuse(void)
{
u32 ctrl_state = ~FUSE_CTRL_STATE_IDLE;
int timeout = 0;
u32 reg;
mutex_lock(&tegra_simon_fuse_lock);
/* Wait for fuse controller to go idle */
while (ctrl_state != FUSE_CTRL_STATE_IDLE && timeout < FUSE_TIMEOUT) {
ctrl_state = tegra_fuse_readl(FUSE_CTRL);
ctrl_state >>= FUSE_CTRL_STATE_OFFSET;
ctrl_state &= FUSE_CTRL_STATE_MASK;
msleep(50);
timeout++;
}
if (timeout == FUSE_TIMEOUT) {
mutex_unlock(&tegra_simon_fuse_lock);
return 0;
}
/* Setup fuse to read */
tegra_fuse_writel(FUSE_SIMON_STATE, FUSE_ADDR);
reg = tegra_fuse_readl(FUSE_CTRL);
reg = reg & ~(FUSE_CTRL_CMD_MASK << FUSE_CTRL_CMD_OFFSET);
reg = reg | (FUSE_CTRL_CMD_READ << FUSE_CTRL_CMD_OFFSET);
tegra_fuse_writel(reg, FUSE_CTRL);
/* Wait for read to complete */
ctrl_state = ~FUSE_CTRL_STATE_IDLE;
timeout = 0;
while (ctrl_state != FUSE_CTRL_STATE_IDLE && timeout < FUSE_TIMEOUT) {
ctrl_state = tegra_fuse_readl(FUSE_CTRL);
ctrl_state >>= FUSE_CTRL_STATE_OFFSET;
ctrl_state &= FUSE_CTRL_STATE_MASK;
msleep(50);
timeout++;
}
if (timeout == FUSE_TIMEOUT) {
mutex_unlock(&tegra_simon_fuse_lock);
return 0;
}
reg = tegra_fuse_readl(FUSE_DATA);
mutex_unlock(&tegra_simon_fuse_lock);
return reg;
}
static bool is_rev_valid(void)
{
u32 reg = tegra_fuse_readl(FUSE_CP_REV);
u32 major = (reg >> 5) & 0x3f;
u32 minor = reg & 0x1f;
if (major || minor >= 12)
return true;
return false;
}
static s64 get_current_threshold(s64 ro29, s64 ro30, s64 initial_shift, int mv,
int temperature)
{
s64 shift = ro30;
shift = (shift << FIXED_SCALE) * 100;
do_div(shift, ro29);
/* Normalize for voltage */
shift = (shift << FIXED_SCALE);
shift = scale_voltage(mv, shift);
/* Normalize for temperature */
shift = (shift << FIXED_SCALE);
shift = scale_temp(temperature, shift);
if (initial_shift) {
initial_shift = (initial_shift << FIXED_SCALE) * 8;
do_div(initial_shift, 31);
initial_shift = initial_shift - (4 << FIXED_SCALE);
}
return shift - initial_shift;
}
static int grade_gpu_simon_domain(int domain, int mv, int temperature)
{
u32 ro29, ro30;
s64 cur_shift, initial_shift;
if (domain != TEGRA_SIMON_DOMAIN_GPU)
return 0;
/* Older rev = Eng boards */
if (!is_rev_valid())
return 1;
initial_shift = get_tegra_simon_fuse();
/* Invalid fuse */
if (!initial_shift) {
pr_err("%s: Invalid fuse\n", __func__);
return 0;
}
initial_shift = (initial_shift >> GPU_INITIAL_SHIFT_OFFSET) &
INITIAL_SHIFT_MASK;
ro29 = read_gpu_ism(0, 600, 3, 29);
ro30 = read_gpu_ism(2, 3000, 0, 30);
if (!ro29)
return 0;
cur_shift = get_current_threshold(ro29, ro30, initial_shift, mv,
temperature);
return cur_shift < TEGRA_SIMON_THRESHOLD;
}
static int grade_cpu_simon_domain(int domain, int mv, int temperature)
{
u32 ro29, ro30;
s64 cur_shift, initial_shift;
if (domain != TEGRA_SIMON_DOMAIN_CPU)
return 0;
/* Older rev = Eng boards */
if (!is_rev_valid())
return 1;
initial_shift = get_tegra_simon_fuse();
/* Invalid fuse */
if (!initial_shift) {
pr_err("%s: Invalid fuse\n", __func__);
return 0;
}
initial_shift = (initial_shift >> CPU_INITIAL_SHIFT_OFFSET) &
INITIAL_SHIFT_MASK;
ro29 = read_cpu0_ism(0, 600, 3, 29);
ro30 = read_cpu0_ism(2, 3000, 0, 30);
if (!ro29)
return 0;
cur_shift = get_current_threshold(ro29, ro30, initial_shift, mv,
temperature);
return cur_shift < TEGRA_SIMON_THRESHOLD;
}
static struct tegra_simon_grader_desc gpu_grader_desc = {
.domain = TEGRA_SIMON_DOMAIN_GPU,
.grading_mv_max = 850,
.grading_temperature_min = 20000,
.settle_us = 3000,
.grade_simon_domain = grade_gpu_simon_domain,
};
static struct tegra_simon_grader_desc cpu_grader_desc = {
.domain = TEGRA_SIMON_DOMAIN_CPU,
.grading_rate_max = 850000000,
.grading_temperature_min = 20000,
.settle_us = 3000,
.grade_simon_domain = grade_cpu_simon_domain,
};
#ifdef CONFIG_DEBUG_FS
static int fuse_show(struct seq_file *s, void *data)
{
char buf[150];
int ret;
u32 fuse = get_tegra_simon_fuse();
ret = snprintf(buf, sizeof(buf),
"GPU Fuse: %u\nCPU Fuse: %u\nRaw: 0x%x\n",
(fuse >> GPU_INITIAL_SHIFT_OFFSET) & INITIAL_SHIFT_MASK,
(fuse >> CPU_INITIAL_SHIFT_OFFSET) &
INITIAL_SHIFT_MASK,
fuse);
if (ret < 0)
return ret;
seq_write(s, buf, ret);
return 0;
}
static int fuse_open(struct inode *inode, struct file *file)
{
return single_open(file, fuse_show, inode->i_private);
}
static const struct file_operations fuse_fops = {
.open = fuse_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int cpu_ism_show(struct seq_file *s, void *data)
{
char buf[150];
u32 ro29, ro30, ro30_2;
int ret;
ro29 = read_cpu0_ism(0, 600, 3, 29);
ro30 = read_cpu0_ism(0, 600, 3, 30);
ro30_2 = read_cpu0_ism(2, 3000, 0, 30);
ret = snprintf(buf, sizeof(buf),
"RO29: %u RO30: %u RO30_2: %u diff: %d\n",
ro29, ro30, ro30_2, ro29 - ro30);
if (ret < 0)
return ret;
seq_write(s, buf, ret);
return 0;
}
static int cpu_ism_open(struct inode *inode, struct file *file)
{
return single_open(file, cpu_ism_show, inode->i_private);
}
static const struct file_operations cpu_ism_fops = {
.open = cpu_ism_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int gpu_ism_show(struct seq_file *s, void *data)
{
char buf[150];
u32 ro29, ro30, ro30_2;
int ret;
ro29 = read_gpu_ism(0, 600, 3, 29);
ro30 = read_gpu_ism(0, 600, 3, 30);
ro30_2 = read_gpu_ism(2, 3000, 0, 30);
ret = snprintf(buf, sizeof(buf),
"RO29: %u RO30: %u RO30_2: %u diff: %d\n",
ro29, ro30, ro30_2, ro29 - ro30);
if (ret < 0)
return ret;
seq_write(s, buf, ret);
return 0;
}
static int gpu_ism_open(struct inode *inode, struct file *file)
{
return single_open(file, gpu_ism_show, inode->i_private);
}
static const struct file_operations gpu_ism_fops = {
.open = gpu_ism_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init debugfs_init(void)
{
struct dentry *dfs_file, *dfs_dir;
dfs_dir = debugfs_create_dir("tegra13_simon", NULL);
if (!dfs_dir)
return -ENOMEM;
dfs_file = debugfs_create_file("cpu_ism", 0644, dfs_dir, NULL,
&cpu_ism_fops);
if (!dfs_file)
goto err;
dfs_file = debugfs_create_file("gpu_ism", 0644, dfs_dir, NULL,
&gpu_ism_fops);
if (!dfs_file)
goto err;
dfs_file = debugfs_create_file("fuses", 0644, dfs_dir, NULL,
&fuse_fops);
if (!dfs_file)
goto err;
return 0;
err:
debugfs_remove_recursive(dfs_dir);
return -ENOMEM;
}
#endif
static int __init tegra13_simon_graders_init(void)
{
tegra_simon_add_grader(&gpu_grader_desc);
tegra_simon_add_grader(&cpu_grader_desc);
#ifdef CONFIG_DEBUG_FS
debugfs_init();
#endif
return 0;
}
late_initcall_sync(tegra13_simon_graders_init);