Jetpack/kernel/nvidia/drivers/platform/tegra/tegra19x-mce.c

439 lines
9.9 KiB
C

/*
* Copyright (c) 2017-2019, 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.
*/
#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/t194_nvg.h>
#include <linux/tegra-mce.h>
#include <asm/smp_plat.h>
#include <soc/tegra/chip-id.h>
#include "tegra19x-mce.h"
#include "dmce_perfmon.h"
/* Issue a NVG request with data */
static noinline notrace void nvg_send_req_data(uint64_t req, uint64_t data)
{
asm volatile (
"msr s3_0_c15_c1_2, %0 \n"
"msr s3_0_c15_c1_3, %1 \n"
:: "r" (req), "r" (data));
}
/* Issue a NVG request with no data */
static noinline notrace void nvg_send_req(uint64_t req)
{
asm volatile ("msr s3_0_c15_c1_2, %0 \n" :: "r" (req));
}
/* Issue a NVG request to read the command response */
static noinline notrace uint64_t nvg_get_response(void)
{
uint64_t ret;
asm volatile ("mrs %0, s3_0_c15_c1_3" : "=r" (ret));
return ret;
}
int tegra19x_mce_enter_cstate(u32 state, u32 wake_time)
{
/* use PSCI interface instead */
return 0;
}
int tegra19x_mce_update_cstate_info(u32 cluster, u32 ccplex, u32 system,
u8 force, u32 wake_mask, bool valid)
{
nvg_cstate_info_channel_t cstate_info = { 0 };
/* disable preemption */
preempt_disable();
/* update CLUSTER_CSTATE? */
if (cluster) {
cstate_info.bits.cluster_state = cluster;
cstate_info.bits.update_cluster = 1;
}
/* update CCPLEX_CSTATE? */
if (ccplex) {
cstate_info.bits.cg_cstate = ccplex;
cstate_info.bits.update_cg = 1;
}
/* update SYSTEM_CSTATE? */
if (system) {
cstate_info.bits.system_cstate = system;
cstate_info.bits.update_system = 1;
}
/* update wake mask value? */
if (valid)
cstate_info.bits.update_wake_mask = 1;
/* set the wake mask */
cstate_info.bits.wake_mask = wake_mask;
/* set the updated cstate info */
nvg_send_req_data(TEGRA_NVG_CHANNEL_CSTATE_INFO,
cstate_info.flat);
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_update_crossover_time(u32 type, u32 time)
{
if ((type != TEGRA_NVG_CHANNEL_CROSSOVER_C6_LOWER_BOUND) &&
(type != TEGRA_NVG_CHANNEL_CROSSOVER_CC6_LOWER_BOUND) &&
(type != TEGRA_NVG_CHANNEL_CROSSOVER_CG7_LOWER_BOUND)) {
pr_err("%s: unknown crossover type (%d)\n", __func__, type);
return -EINVAL;
}
/* disable pre-emption*/
preempt_disable();
nvg_send_req_data(type, (uint64_t)time);
/* enable pre-emption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_cstate_stats(u32 state, u64 *stats)
{
if (!stats)
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req_data(TEGRA_NVG_CHANNEL_CSTATE_STAT_QUERY_REQUEST,
(uint64_t)state);
nvg_send_req(TEGRA_NVG_CHANNEL_CSTATE_STAT_QUERY_VALUE);
*stats = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_cc3_ctrl(u32 ndiv, u32 vindex, u8 enable)
{
nvg_cc3_control_channel_t cc3_ctrl;
/* disable preemption */
preempt_disable();
/*
* If the enable bit is cleared, Auto-CC3 will be disabled by setting
* the SW visible frequency request registers for all non
* floorswept cores valid independent of StandbyWFI and disabling
* the IDLE frequency request register. If set, Auto-CC3
* will be enabled by setting the ARM SW visible frequency
* request registers for all non floorswept cores to be enabled by
* StandbyWFI or the equivalent signal, and always keeping the IDLE
* frequency request register enabled.
*/
cc3_ctrl.bits.freq_req = ndiv;
cc3_ctrl.bits.enable = !!enable;
nvg_send_req_data(TEGRA_NVG_CHANNEL_CC3_CTRL, cc3_ctrl.flat);
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_versions(u32 *major, u32 *minor)
{
uint64_t version;
if (!major || !minor)
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req(TEGRA_NVG_CHANNEL_VERSION);
version = nvg_get_response();
*minor = (u32)version;
*major = (u32)(version >> 32);
/* enable preemption */
preempt_enable();
return 0;
}
/* Check for valid dda channel id.*/
static int tegra19x_check_dda_channel_id(u32 index) {
if ((index < TEGRA_NVG_CHANNEL_DDA_SNOC_MCF) ||
(index > TEGRA_NVG_CHANNEL_DDA_SNOC_CLIENT_REPLENTISH_CTRL)) {
pr_err("mce: invalid dda channel id: %u\n", index);
return -EINVAL;
}
return 0;
}
int tegra19x_mce_write_dda_ctrl(u32 index, u64 value)
{
if (tegra19x_check_dda_channel_id(index))
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req_data(index, value);
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_dda_ctrl(u32 index, u64* value)
{
if (tegra19x_check_dda_channel_id(index))
return -EINVAL;
if (!value)
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req(index);
*value = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_l3_cache_ways(u64 *value)
{
/* disable preemption */
preempt_disable();
nvg_send_req(TEGRA_NVG_CHANNEL_CCPLEX_CACHE_CONTROL);
*value = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_write_l3_cache_ways(u64 data, u64 *value)
{
/* disable preemption */
preempt_disable();
nvg_send_req_data(TEGRA_NVG_CHANNEL_CCPLEX_CACHE_CONTROL, data);
*value = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_rt_safe_mask(u64 *rt_safe_mask)
{
if (!rt_safe_mask)
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req(TEGRA_NVG_CHANNEL_RT_SAFE_MASK);
*rt_safe_mask = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_write_rt_safe_mask(u64 rt_safe_mask)
{
/* disable preemption */
preempt_disable();
nvg_send_req_data(TEGRA_NVG_CHANNEL_RT_SAFE_MASK, rt_safe_mask);
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_rt_window_us(u64 *rt_window_us)
{
if (!rt_window_us)
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req(TEGRA_NVG_CHANNEL_RT_WINDOW_US);
*rt_window_us = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_write_rt_window_us(u64 rt_window_us)
{
/* disable preemption */
preempt_disable();
nvg_send_req_data(TEGRA_NVG_CHANNEL_RT_WINDOW_US, rt_window_us);
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_read_rt_fwd_progress_us(u64 *rt_fwd_progress_us)
{
if (!rt_fwd_progress_us)
return -EINVAL;
/* disable preemption */
preempt_disable();
nvg_send_req(TEGRA_NVG_CHANNEL_RT_FWD_PROGRESS_US);
*rt_fwd_progress_us = nvg_get_response();
/* enable preemption */
preempt_enable();
return 0;
}
int tegra19x_mce_write_rt_fwd_progress_us(u64 rt_fwd_progress_us)
{
/* disable preemption */
preempt_disable();
nvg_send_req_data(TEGRA_NVG_CHANNEL_RT_FWD_PROGRESS_US,
rt_fwd_progress_us);
/* enable preemption */
preempt_enable();
return 0;
}
#ifdef CONFIG_DEBUG_FS
/* Dummy functions below */
int tegra19x_mce_features_get(void *data, u64 *val) { return -ENOTSUPP; }
int tegra19x_mce_enable_latic_set(void *data, u64 val) { return -ENOTSUPP; }
int tegra19x_mce_coresight_cg_set(void *data, u64 val) { return -ENOTSUPP; }
int tegra19x_mce_edbgreq_set(void *data, u64 val) { return -ENOTSUPP; }
#define NVG_STAT_MAX_ENTRIES 10
#define MCE_STAT_ID_SHIFT 16UL
#define UNITGROUP_IGNORED 0
#define UNITGROUP_CORE 1
#define UNITGROUP_CLUSTER 2
#define UNITGROUP_CLUSTERGROUP 3
#define CSTAT_ENTRY(stat) NVG_STAT_QUERY_##stat
struct cstats_info {
char *name; /* name of the cstats */
int id; /* NVG id */
int units;/* No of cores; No of clusters; No of cluster groups */
int unit_group; /* 0:Ignored; 1:core; 2:cluster; 3:cluster group */
};
static struct cstats_info cstats_table[] = {
{ "SC7_ENTRIES", CSTAT_ENTRY(SC7_ENTRIES), 1, UNITGROUP_IGNORED},
{ "SC7_RESIDENCY_SUM",
CSTAT_ENTRY(SC7_RESIDENCY_SUM), 1, UNITGROUP_IGNORED},
{ "CG7_ENTRIES", CSTAT_ENTRY(CG7_ENTRIES), 2, UNITGROUP_CLUSTERGROUP},
{ "CG7_RESIDENCY_SUM",
CSTAT_ENTRY(CG7_RESIDENCY_SUM), 2, UNITGROUP_CLUSTERGROUP},
{ "CC6_ENTRIES", CSTAT_ENTRY(CC6_ENTRIES), 4, UNITGROUP_CLUSTER},
{ "CC6_RESIDENCY_SUM",
CSTAT_ENTRY(CC6_RESIDENCY_SUM), 4, UNITGROUP_CLUSTER},
{ "C7_ENTRIES", CSTAT_ENTRY(C7_ENTRIES), 8, UNITGROUP_CORE},
{ "C7_RESIDENCY_SUM", CSTAT_ENTRY(C7_RESIDENCY_SUM), 8, UNITGROUP_CORE},
{ "C6_ENTRIES", CSTAT_ENTRY(C6_ENTRIES), 8, UNITGROUP_CORE},
{ "C6_RESIDENCY_SUM", CSTAT_ENTRY(C6_RESIDENCY_SUM), 8, UNITGROUP_CORE},
};
int tegra19x_mce_dbg_cstats_show(struct seq_file *s, void *data)
{
int nr_cpus = num_present_cpus();
int st, unit;
u64 val;
u32 mce_index;
seq_printf(s, "%-25s%-15s%-10s\n", "name", "unit-id", "count/time");
seq_puts(s, "---------------------------------------------------\n");
for (st = 0; st < NVG_STAT_MAX_ENTRIES; st++) {
switch (cstats_table[st].unit_group) {
case UNITGROUP_IGNORED:
cstats_table[st].units = 1;
break;
case UNITGROUP_CLUSTERGROUP:
cstats_table[st].units = 2;
break;
case UNITGROUP_CLUSTER:
/* Divide by 2 to get num of clusters
* as t19x has 2 cores per cluster
*/
cstats_table[st].units = nr_cpus / 2;
break;
case UNITGROUP_CORE:
cstats_table[st].units = nr_cpus;
break;
default:
return -EINVAL;
}
for (unit = 0; unit < cstats_table[st].units; unit++) {
mce_index = ((u32)cstats_table[st].id <<
MCE_STAT_ID_SHIFT) + (u32)unit;
if (tegra19x_mce_read_cstate_stats(mce_index, &val))
pr_err("mce: failed to read cstat: %s, %x\n",
cstats_table[st].name, mce_index);
else
seq_printf(s, "%-25s%-15d%-20lld\n",
cstats_table[st].name, unit, val);
}
}
return 0;
}
#endif /* CONFIG_DEBUG_FS */