/* * Copyright (c) 2017-2018, 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 #include #include #include #include #include #include #include "tegra18x-mce.h" #define SMC_SIP_INVOKE_MCE 0xC2FFFF00 #define NR_SMC_REGS 6 /* MCE command enums for SMC calls */ enum { MCE_SMC_ENTER_CSTATE = 0, MCE_SMC_UPDATE_CSTATE_INFO = 1, MCE_SMC_UPDATE_XOVER_TIME = 2, MCE_SMC_READ_CSTATE_STATS = 3, MCE_SMC_WRITE_CSTATE_STATS = 4, MCE_SMC_IS_SC7_ALLOWED = 5, MCE_SMC_ONLINE_CORE = 6, MCE_SMC_CC3_CTRL = 7, MCE_SMC_ECHO_DATA = 8, MCE_SMC_READ_VERSIONS = 9, MCE_SMC_ENUM_FEATURES = 10, MCE_SMC_ROC_FLUSH_CACHE = 11, MCE_SMC_ENUM_READ_MCA = 12, MCE_SMC_ENUM_WRITE_MCA = 13, MCE_SMC_ROC_FLUSH_CACHE_ONLY = 14, MCE_SMC_ROC_CLEAN_CACHE_ONLY = 15, MCE_SMC_ENABLE_LATIC = 16, MCE_SMC_UNCORE_PERFMON_REQ = 17, MCE_SMC_MISC_CCPLEX = 18, MCE_SMC_ENUM_MAX = 0xFF, /* enums cannot exceed this value */ }; struct tegra_mce_regs { u64 args[NR_SMC_REGS]; }; static noinline notrace int __send_smc(u8 func, struct tegra_mce_regs *regs) { u32 ret = SMC_SIP_INVOKE_MCE | (func & MCE_SMC_ENUM_MAX); asm volatile ( " mov x0, %0\n" " ldp x1, x2, [%1, #16 * 0]\n" " ldp x3, x4, [%1, #16 * 1]\n" " ldp x5, x6, [%1, #16 * 2]\n" " isb\n" " smc #0\n" " mov %0, x0\n" " stp x0, x1, [%1, #16 * 0]\n" " stp x2, x3, [%1, #16 * 1]\n" : "+r" (ret) : "r" (regs) : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17"); return ret; } #define send_smc(func, regs) \ ({ \ int __ret = __send_smc(func, regs); \ \ if (__ret) \ pr_err("%s: failed (ret=%d)\n", __func__, __ret); \ __ret; \ }) int tegra18x_mce_enter_cstate(u32 state, u32 wake_time) { struct tegra_mce_regs regs; regs.args[0] = state; regs.args[1] = wake_time; return send_smc(MCE_SMC_ENTER_CSTATE, ®s); } int tegra18x_mce_update_cstate_info(u32 cluster, u32 ccplex, u32 system, u8 force, u32 wake_mask, bool valid) { struct tegra_mce_regs regs; regs.args[0] = cluster; regs.args[1] = ccplex; regs.args[2] = system; regs.args[3] = force; regs.args[4] = wake_mask; regs.args[5] = valid; return send_smc(MCE_SMC_UPDATE_CSTATE_INFO, ®s); } int tegra18x_mce_update_crossover_time(u32 type, u32 time) { struct tegra_mce_regs regs; regs.args[0] = type; regs.args[1] = time; return send_smc(MCE_SMC_UPDATE_XOVER_TIME, ®s); } int tegra18x_mce_read_cstate_stats(u32 state, u64 *stats) { struct tegra_mce_regs regs; regs.args[0] = state; send_smc(MCE_SMC_READ_CSTATE_STATS, ®s); *stats = regs.args[2]; return 0; } int tegra18x_mce_write_cstate_stats(u32 state, u32 stats) { struct tegra_mce_regs regs; regs.args[0] = state; regs.args[1] = stats; return send_smc(MCE_SMC_WRITE_CSTATE_STATS, ®s); } int tegra18x_mce_is_sc7_allowed(u32 state, u32 wake, u32 *allowed) { struct tegra_mce_regs regs; regs.args[0] = state; regs.args[1] = wake; send_smc(MCE_SMC_IS_SC7_ALLOWED, ®s); *allowed = (u32)regs.args[3]; return 0; } int tegra18x_mce_online_core(int cpu) { struct tegra_mce_regs regs; regs.args[0] = cpu_logical_map(cpu); return send_smc(MCE_SMC_ONLINE_CORE, ®s); } int tegra18x_mce_cc3_ctrl(u32 ndiv, u32 vindex, u8 enable) { struct tegra_mce_regs regs; regs.args[0] = ndiv; regs.args[1] = vindex; regs.args[2] = enable; return send_smc(MCE_SMC_CC3_CTRL, ®s); } int tegra18x_mce_echo_data(u32 data, int *matched) { struct tegra_mce_regs regs; regs.args[0] = data; send_smc(MCE_SMC_ECHO_DATA, ®s); *matched = (u32)regs.args[2]; return 0; } int tegra18x_mce_read_versions(u32 *major, u32 *minor) { struct tegra_mce_regs regs; send_smc(MCE_SMC_READ_VERSIONS, ®s); *major = (u32)regs.args[1]; *minor = (u32)regs.args[2]; return 0; } int tegra18x_mce_enum_features(u64 *features) { struct tegra_mce_regs regs; send_smc(MCE_SMC_ENUM_FEATURES, ®s); *features = (u32)regs.args[1]; return 0; } int tegra18x_mce_read_uncore_mca(mca_cmd_t cmd, u64 *data, u32 *error) { struct tegra_mce_regs regs; regs.args[0] = cmd.data; regs.args[1] = 0; send_smc(MCE_SMC_ENUM_READ_MCA, ®s); *data = regs.args[2]; *error = (u32)regs.args[3]; return 0; } int tegra18x_mce_write_uncore_mca(mca_cmd_t cmd, u64 data, u32 *error) { struct tegra_mce_regs regs; regs.args[0] = cmd.data; regs.args[1] = data; send_smc(MCE_SMC_ENUM_WRITE_MCA, ®s); *error = (u32)regs.args[3]; return 0; } int tegra18x_mce_read_uncore_perfmon(u32 req, u32 *data) { struct tegra_mce_regs regs; u32 status; if (data == NULL) return -EINVAL; regs.args[0] = req; status = send_smc(MCE_SMC_UNCORE_PERFMON_REQ, ®s); *data = (u32)regs.args[1]; return status; } int tegra18x_mce_write_uncore_perfmon(u32 req, u32 data) { struct tegra_mce_regs regs; u32 status = 0; regs.args[0] = req; regs.args[1] = data; status = send_smc(MCE_SMC_UNCORE_PERFMON_REQ, ®s); return status; } int tegra18x_mce_enable_latic(void) { struct tegra_mce_regs regs; return send_smc(MCE_SMC_ENABLE_LATIC, ®s); } #ifdef CONFIG_DEBUG_FS int tegra18x_mce_features_get(void *data, u64 *val) { return tegra_mce_enum_features(val); } int tegra18x_mce_enable_latic_set(void *data, u64 val) { if (tegra_mce_enable_latic()) return -EINVAL; return 0; } /* Enable/disable coresight clock gating */ int tegra18x_mce_coresight_cg_set(void *data, u64 val) { struct tegra_mce_regs regs; /* Enable - 1, disable - 0 are the only valid values */ if (val > 1) { pr_err("mce: invalid enable value.\n"); return -EINVAL; } regs.args[0] = TEGRA_ARI_MISC_CCPLEX_CORESIGHT_CG_CTRL; regs.args[1] = (u32)val; send_smc(MCE_SMC_MISC_CCPLEX, ®s); return 0; } /* Enable external debug on MCA */ int tegra18x_mce_edbgreq_set(void *data, u64 val) { struct tegra_mce_regs regs; regs.args[0] = TEGRA_ARI_MISC_CCPLEX_EDBGREQ; send_smc(MCE_SMC_MISC_CCPLEX, ®s); return 0; } #define CSTAT_ENTRY(stat)[TEGRA_ARI_CSTATE_STATS_##stat] = #stat static const char * const cstats_table[] = { CSTAT_ENTRY(SC7_ENTRIES), CSTAT_ENTRY(A57_CC6_ENTRIES), CSTAT_ENTRY(A57_CC7_ENTRIES), CSTAT_ENTRY(D15_CC6_ENTRIES), CSTAT_ENTRY(D15_CC7_ENTRIES), CSTAT_ENTRY(D15_0_C6_ENTRIES), CSTAT_ENTRY(D15_1_C6_ENTRIES), CSTAT_ENTRY(D15_0_C7_ENTRIES), CSTAT_ENTRY(D15_1_C7_ENTRIES), CSTAT_ENTRY(A57_0_C7_ENTRIES), CSTAT_ENTRY(A57_1_C7_ENTRIES), CSTAT_ENTRY(A57_2_C7_ENTRIES), CSTAT_ENTRY(A57_3_C7_ENTRIES), CSTAT_ENTRY(LAST_CSTATE_ENTRY_D15_0), CSTAT_ENTRY(LAST_CSTATE_ENTRY_D15_1), CSTAT_ENTRY(LAST_CSTATE_ENTRY_A57_0), CSTAT_ENTRY(LAST_CSTATE_ENTRY_A57_1), CSTAT_ENTRY(LAST_CSTATE_ENTRY_A57_2), CSTAT_ENTRY(LAST_CSTATE_ENTRY_A57_3), }; int tegra18x_mce_dbg_cstats_show(struct seq_file *s, void *data) { int st; u64 val; seq_printf(s, "%-30s%-10s\n", "name", "count"); seq_puts(s, "----------------------------------------\n"); for (st = 1; st <= TEGRA_ARI_CSTATE_STATS_MAX; st++) { if (!cstats_table[st]) continue; if (tegra18x_mce_read_cstate_stats(st, &val)) pr_err("mce: failed to read cstat: %d\n", st); else seq_printf(s, "%-30s%-10lld\n", cstats_table[st], val); } return 0; } #endif /* CONFIG_DEBUG_FS */ /* Tegra18x Cache functions */ __always_inline int tegra18x_roc_flush_cache(void) { struct tegra_mce_regs regs; return send_smc(MCE_SMC_ROC_FLUSH_CACHE, ®s); } __always_inline int tegra18x_roc_flush_cache_only(void) { struct tegra_mce_regs regs; return send_smc(MCE_SMC_ROC_FLUSH_CACHE_ONLY, ®s); } __always_inline int tegra18x_roc_clean_cache(void) { struct tegra_mce_regs regs; return send_smc(MCE_SMC_ROC_CLEAN_CACHE_ONLY, ®s); }