From 7287441d465d95f4445cc7044b2340233fa1200d Mon Sep 17 00:00:00 2001 From: Sai Gurrappadi Date: Wed, 18 Jun 2014 15:13:59 -0700 Subject: ARM: tegra: Tegra13 Simon graders for GPU and CPU Simon graders that grade the CPU and GPU based on the simon state for the specific simon domain. Bug 1511506 Change-Id: I054ab8895e9d1773460c7ae9ba5f73191dd45e56 Signed-off-by: Sai Gurrappadi Reviewed-on: http://git-master/r/425051 Reviewed-by: Thomas Cherry --- drivers/platform/tegra/tegra13_simon_graders.c | 536 +++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 drivers/platform/tegra/tegra13_simon_graders.c (limited to 'drivers/platform') diff --git a/drivers/platform/tegra/tegra13_simon_graders.c b/drivers/platform/tegra/tegra13_simon_graders.c new file mode 100644 index 000000000000..4f713b18f358 --- /dev/null +++ b/drivers/platform/tegra/tegra13_simon_graders.c @@ -0,0 +1,536 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#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); -- cgit v1.2.3