diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index 88178fbf..61280203 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. obj-m += bad.o @@ -18,3 +18,4 @@ obj-m += tegra-fsicom.o obj-m += cvnas/ obj-m += hwpm/ +obj-m += mce/ diff --git a/drivers/platform/tegra/mce/Makefile b/drivers/platform/tegra/mce/Makefile new file mode 100644 index 00000000..b603dcea --- /dev/null +++ b/drivers/platform/tegra/mce/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +GCOV_PROFILE := y +ccflags-y += -Werror + +obj-m += tegra23x-mce.o tegra-mce.o diff --git a/drivers/platform/tegra/mce/tegra-mce.c b/drivers/platform/tegra/mce/tegra-mce.c new file mode 100644 index 00000000..1c63311d --- /dev/null +++ b/drivers/platform/tegra/mce/tegra-mce.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include + +static struct tegra_mce_ops *mce_ops; + +void tegra_mce_set_ops(struct tegra_mce_ops *tegra_mce_plat_ops) +{ + mce_ops = tegra_mce_plat_ops; +} +EXPORT_SYMBOL_GPL(tegra_mce_set_ops); + +/** + * Specify power state and wake time for entering upon STANDBYWFI + * + * @state: requested core power state + * @wake_time: wake time in TSC ticks + * + * Returns 0 if success. + */ +int tegra_mce_enter_cstate(u32 state, u32 wake_time) +{ + if (mce_ops && mce_ops->enter_cstate) + return mce_ops->enter_cstate(state, wake_time); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_enter_cstate); + +/** + * Specify deepest cluster/ccplex/system states allowed. + * + * @cluster: deepest cluster-wide state + * @ccplex: deepest ccplex-wide state + * @system: deepest system-wide state + * @force: forced system state + * @wake_mask: wake mask to be updated + * @valid: is wake_mask applicable? + * + * Returns 0 if success. + */ +int tegra_mce_update_cstate_info(u32 cluster, u32 ccplex, u32 system, + u8 force, u32 wake_mask, bool valid) +{ + if (mce_ops && mce_ops->update_cstate_info) + return mce_ops->update_cstate_info(cluster, ccplex, system, + force, wake_mask, valid); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_update_cstate_info); + +/** + * Update threshold for one specific c-state crossover + * + * @type: type of state crossover. + * @time: idle time threshold. + * + * Returns 0 if success. + */ +int tegra_mce_update_crossover_time(u32 type, u32 time) +{ + if (mce_ops && mce_ops->update_crossover_time) + return mce_ops->update_crossover_time(type, time); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_update_crossover_time); + +/** + * Query the runtime stats of a specific cstate + * + * @state: c-state of the stats. + * @stats: output integer to hold the stats. + * + * Returns 0 if success. + */ +int tegra_mce_read_cstate_stats(u32 state, u64 *stats) +{ + if (mce_ops && mce_ops->read_cstate_stats) + return mce_ops->read_cstate_stats(state, stats); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_cstate_stats); + +/** + * Overwrite the runtime stats of a specific c-state + * + * @state: c-state of the stats. + * @stats: integer represents the new stats. + * + * Returns 0 if success. + */ +int tegra_mce_write_cstate_stats(u32 state, u32 stats) +{ + if (mce_ops && mce_ops->write_cstate_stats) + return mce_ops->write_cstate_stats(state, stats); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_cstate_stats); + +/** + * Query MCE to determine if SC7 is allowed + * given a target core's C-state and wake time + * + * @state: c-state of the stats. + * @stats: integer represents the new stats. + * @allowed: pointer to result + * + * Returns 0 if success. + */ +int tegra_mce_is_sc7_allowed(u32 state, u32 wake, u32 *allowed) +{ + if (mce_ops && mce_ops->is_sc7_allowed) + return mce_ops->is_sc7_allowed(state, wake, allowed); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_is_sc7_allowed); + +/** + * Bring another offlined core back online to C0 state. + * + * @cpu: logical cpuid from smp_processor_id() + * + * Returns 0 if success. + */ +int tegra_mce_online_core(int cpu) +{ + if (mce_ops && mce_ops->online_core) + return mce_ops->online_core(cpu); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_online_core); + +/** + * Program Auto-CC3 feature. + * + * @ndiv: ndiv of IDLE voltage/freq register + * @vindex: vindex of IDLE voltage/freq register + * (Not used on tegra19x) + * @enable: enable bit for Auto-CC3 + * + * Returns 0 if success. + */ +int tegra_mce_cc3_ctrl(u32 ndiv, u32 vindex, u8 enable) +{ + if (mce_ops && mce_ops->cc3_ctrl) + return mce_ops->cc3_ctrl(ndiv, vindex, enable); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_cc3_ctrl); + +/** + * Send data to MCE which echoes it back. + * + * @data: data to be sent to MCE. + * @out: output data to hold the response. + * @matched: pointer to matching result + * + * Returns 0 if success. + */ +int tegra_mce_echo_data(u64 data, u64 *matched) +{ + if (mce_ops && mce_ops->echo_data) + return mce_ops->echo_data(data, matched); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_echo_data); + +/** + * Read out MCE API major/minor versions + * + * @major: output for major number. + * @minor: output for minor number. + * + * Returns 0 if success. + */ +int tegra_mce_read_versions(u32 *major, u32 *minor) +{ + if (mce_ops && mce_ops->read_versions) + return mce_ops->read_versions(major, minor); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_versions); + +/** + * Read out RT Safe Mask + * + * @rt_safe_mask: output for rt safe mask. + * + * Returns 0 if success. + */ +int tegra_mce_read_rt_safe_mask(u64 *rt_safe_mask) +{ + if (mce_ops && mce_ops->read_rt_safe_mask) + return mce_ops->read_rt_safe_mask(rt_safe_mask); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_rt_safe_mask); + +/** + * Write RT Safe Mask + * + * @rt_safe_mask: rt safe mask value to be written + * + * Returns 0 if success. + */ +int tegra_mce_write_rt_safe_mask(u64 rt_safe_mask) +{ + if (mce_ops && mce_ops->write_rt_safe_mask) + return mce_ops->write_rt_safe_mask(rt_safe_mask); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_rt_safe_mask); + +/** + * Read out RT Window US + * + * @rt_window_us: output for rt window us + * + * Returns 0 if success. + */ +int tegra_mce_read_rt_window_us(u64 *rt_window_us) +{ + if (mce_ops && mce_ops->read_rt_window_us) + return mce_ops->read_rt_window_us(rt_window_us); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_rt_window_us); + + +/** + * Write RT Window US + * + * @rt_window_us: rt window us value to be written + * + * Returns 0 if success. + */ +int tegra_mce_write_rt_window_us(u64 rt_window_us) +{ + if (mce_ops && mce_ops->write_rt_window_us) + return mce_ops->write_rt_window_us(rt_window_us); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_rt_window_us); + + +/** + * Read out RT Fwd Progress US + * + * @rt_fwd_progress_us: output for rt fwd progress us + * + * Returns 0 if success. + */ +int tegra_mce_read_rt_fwd_progress_us(u64 *rt_fwd_progress_us) +{ + if (mce_ops && mce_ops->read_rt_fwd_progress_us) + return mce_ops->read_rt_fwd_progress_us(rt_fwd_progress_us); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_rt_fwd_progress_us); + + +/** + * Write RT Fwd Progress US + * + * @rt_fwd_progress_us: rt fwd progress us value to be written + * + * Returns 0 if success. + */ +int tegra_mce_write_rt_fwd_progress_us(u64 rt_fwd_progress_us) +{ + if (mce_ops && mce_ops->write_rt_fwd_progress_us) + return mce_ops->write_rt_fwd_progress_us(rt_fwd_progress_us); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_rt_fwd_progress_us); + + +/** + * Enumerate MCE API features + * + * @features: output feature vector (4bits each) + * + * Returns 0 if success. + */ +int tegra_mce_enum_features(u64 *features) +{ + if (mce_ops && mce_ops->enum_features) + return mce_ops->enum_features(features); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_enum_features); + +/** + * Read uncore MCA errors. + * + * @cmd: MCA command + * @data: output data for the command + * @error: error from MCA + * + * Returns 0 if success. + */ +int tegra_mce_read_uncore_mca(mca_cmd_t cmd, u64 *data, u32 *error) +{ + if (mce_ops && mce_ops->read_uncore_mca) + return mce_ops->read_uncore_mca(cmd, data, error); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_uncore_mca); + +/** + * Write uncore MCA errors. + * + * @cmd: MCA command + * @data: input data for the command + * @error: error from MCA + * + * Returns 0 if success. + */ +int tegra_mce_write_uncore_mca(mca_cmd_t cmd, u64 data, u32 *error) +{ + if (mce_ops && mce_ops->write_uncore_mca) + return mce_ops->write_uncore_mca(cmd, data, error); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_uncore_mca); + +/** + * Query PMU for uncore perfmon counter + * + * @req input command and counter index + * @data output counter value + * + * Returns status of read request. + */ +int tegra_mce_read_uncore_perfmon(u32 req, u32 *data) +{ + if (mce_ops && mce_ops->read_uncore_perfmon) + return mce_ops->read_uncore_perfmon(req, data); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_uncore_perfmon); + +/** + * Write PMU reg for uncore perfmon counter + * + * @req input command and counter index + * @data data to be written + * + * Returns status of write request. + */ +int tegra_mce_write_uncore_perfmon(u32 req, u32 data) +{ + if (mce_ops && mce_ops->write_uncore_perfmon) + return mce_ops->write_uncore_perfmon(req, data); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_uncore_perfmon); + +int tegra_mce_enable_latic(void) +{ + if (mce_ops && mce_ops->enable_latic) + return mce_ops->enable_latic(); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_enable_latic); + +/** + * Write to NVG DDA registers + * + * @index: NVG communication channel id + * @value: Register value to be written + * + * Returns 0 on success + */ +int tegra_mce_write_dda_ctrl(u32 index, u64 value) +{ + if(mce_ops && mce_ops->write_dda_ctrl) + return mce_ops->write_dda_ctrl(index, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_dda_ctrl); + +/** + * Read NVG DDA registers + * + * @index: NVG communication channel id + * @value: Associated register value read + * + * Returns 0 on success + */ +int tegra_mce_read_dda_ctrl(u32 index, u64 *value) +{ + if(mce_ops && mce_ops->read_dda_ctrl) + return mce_ops->read_dda_ctrl(index, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_dda_ctrl); + +/** + * Read NVG L3 cache control register + * + * @value: Fill L3 cache ways + * + * Returns 0 on success + */ +int tegra_mce_read_l3_cache_ways(u64 *value) +{ + if (mce_ops && mce_ops->read_l3_cache_ways) + return mce_ops->read_l3_cache_ways(value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_read_l3_cache_ways); + +/** + * Write L3 cache ways and read back the l3 cache ways written + * + * @data: L3 cache ways to be writtein + * @value: L3 cache ways returrned back + * + * Returns 0 on success + */ +int tegra_mce_write_l3_cache_ways(u64 data, u64 *value) +{ + if (mce_ops && mce_ops->write_l3_cache_ways) + return mce_ops->write_l3_cache_ways(data, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_mce_write_l3_cache_ways); + +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/tegra/mce/tegra23x-mce.c b/drivers/platform/tegra/mce/tegra23x-mce.c new file mode 100644 index 00000000..e2c48701 --- /dev/null +++ b/drivers/platform/tegra/mce/tegra23x-mce.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CPUS 12U +#define MAX_CORES_PER_CLUSTER 4U +#define ARI_TIMEOUT_MAX 2000U /* msec */ + +/* Register offsets for ARI request/results*/ +#define ARI_REQUEST 0x0U +#define ARI_REQUEST_EVENT_MASK 0x8U +#define ARI_STATUS 0x10U +#define ARI_REQUEST_DATA_LO 0x18U +#define ARI_REQUEST_DATA_HI 0x20U +#define ARI_RESPONSE_DATA_LO 0x28U +#define ARI_RESPONSE_DATA_HI 0x30U + +/* Status values for the current request */ +#define ARI_REQ_PENDING 1U +#define ARI_REQ_ONGOING 3U +#define ARI_REQUEST_VALID_BIT (1U << 8U) +#define ARI_REQUEST_NS_BIT (1U << 31U) + +/* Write Enable bit */ +#define CACHE_WAYS_WRITE_EN_BIT (1U << 15U) + +static void __iomem *ari_bar_array[MAX_CPUS]; + +static inline void ari_mmio_write_32(void __iomem *ari_base, + u32 val, u32 reg) +{ + writel(val, ari_base + reg); +} + +static inline u32 ari_mmio_read_32(void __iomem *ari_base, u32 reg) +{ + return readl(ari_base + reg); +} + +static inline u32 ari_get_response_low(void __iomem *ari_base) +{ + return ari_mmio_read_32(ari_base, ARI_RESPONSE_DATA_LO); +} + +static inline u32 ari_get_response_high(void __iomem *ari_base) +{ + return ari_mmio_read_32(ari_base, ARI_RESPONSE_DATA_HI); +} + +static inline void ari_clobber_response(void __iomem *ari_base) +{ + ari_mmio_write_32(ari_base, 0, ARI_RESPONSE_DATA_LO); + ari_mmio_write_32(ari_base, 0, ARI_RESPONSE_DATA_HI); +} + +static int32_t ari_send_request(void __iomem *ari_base, u32 evt_mask, + u32 req, u32 lo, u32 hi) +{ + uint32_t timeout = ARI_TIMEOUT_MAX; + uint32_t status; + int32_t ret = 0; + + /* clobber response */ + ari_mmio_write_32(ari_base, 0, ARI_RESPONSE_DATA_LO); + ari_mmio_write_32(ari_base, 0, ARI_RESPONSE_DATA_HI); + + /* send request */ + ari_mmio_write_32(ari_base, lo, ARI_REQUEST_DATA_LO); + ari_mmio_write_32(ari_base, hi, ARI_REQUEST_DATA_HI); + ari_mmio_write_32(ari_base, evt_mask, ARI_REQUEST_EVENT_MASK); + ari_mmio_write_32(ari_base, + req | ARI_REQUEST_VALID_BIT | ARI_REQUEST_NS_BIT, ARI_REQUEST); + + while (timeout) { + status = ari_mmio_read_32(ari_base, ARI_STATUS); + if (!(status & (ARI_REQ_ONGOING | ARI_REQ_PENDING))) + break; + mdelay(1); + timeout--; + } + + if (!timeout) + ret = -ETIMEDOUT; + + return ret; +} + +static uint32_t get_ari_address_index(void) +{ + uint64_t mpidr; + uint32_t core_id, cluster_id; + + mpidr = read_cpuid_mpidr(); + cluster_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); + core_id = MPIDR_AFFINITY_LEVEL(mpidr, 1); + + return ((cluster_id * MAX_CORES_PER_CLUSTER) + core_id); +} + +static int tegra23x_mce_read_versions(u32 *major, u32 *minor) +{ + uint32_t cpu_idx; + int32_t ret = 0; + + if (IS_ERR_OR_NULL(major) || IS_ERR_OR_NULL(minor)) + return -EINVAL; + + preempt_disable(); + cpu_idx = get_ari_address_index(); + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_VERSION, 0U, 0U); + if (ret) + return ret; + *major = ari_get_response_low(ari_bar_array[cpu_idx]); + *minor = ari_get_response_high(ari_bar_array[cpu_idx]); + preempt_enable(); + + return 0; +} + +/* + * echo copies data from req_low to resp_low and + * data from req_high to resp_high. + */ +static int tegra23x_mce_echo_data(u64 data, u64 *matched) +{ + uint32_t cpu_idx; + u32 input1 = (u32)(data & 0xFFFFFFFF); + u32 input2 = (u32)(data >> 32); + u64 out1, out2; + int32_t ret = 0; + + if (IS_ERR_OR_NULL(matched)) + return -EINVAL; + + preempt_disable(); + cpu_idx = get_ari_address_index(); + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_ECHO, input1, input2); + if (ret) + return ret; + out1 = (u64)ari_get_response_low(ari_bar_array[cpu_idx]); + out2 = (u64)ari_get_response_high(ari_bar_array[cpu_idx]); + *matched = ((out2 << 32) | out1); + preempt_enable(); + + if (data == *matched) + return 0; + else + return -ENOMSG; +} + +static int tegra23x_mce_read_l4_cache_ways(u64 *value) +{ + uint32_t cpu_idx; + u64 out; + int32_t ret = 0; + + preempt_disable(); + cpu_idx = get_ari_address_index(); + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_CCPLEX_CACHE_CONTROL, 0U, 0U); + if (ret) + return ret; + + out = (u64)ari_get_response_low(ari_bar_array[cpu_idx]); + + *value = out; + preempt_enable(); + + return 0; +} + +static int tegra23x_mce_write_l4_cache_ways(u64 data, u64 *value) +{ + uint32_t cpu_idx; + u32 input = (u32)(data & 0x00001F1F); + u64 out; + int32_t ret = 0; + + if (IS_ERR_OR_NULL(value)) + return -EINVAL; + + preempt_disable(); + cpu_idx = get_ari_address_index(); + input |= CACHE_WAYS_WRITE_EN_BIT; + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_CCPLEX_CACHE_CONTROL, input, 0U); + if (ret) + return ret; + out = (u64)ari_get_response_low(ari_bar_array[cpu_idx]); + *value = out; + preempt_enable(); + + return 0; +} + +static int tegra23x_mce_read_uncore_perfmon(u32 req, u32 *data) +{ + uint32_t cpu_idx; + u32 out_lo, out_hi; + int32_t ret = 0; + + if (IS_ERR_OR_NULL(data)) + return -EINVAL; + + preempt_disable(); + + cpu_idx = get_ari_address_index(); + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_PERFMON, req, 0U); + + if (ret) + return ret; + + out_lo = ari_get_response_low(ari_bar_array[cpu_idx]); + out_hi = ari_get_response_high(ari_bar_array[cpu_idx]); + + pr_debug("%s: read status = %u\n", __func__, out_lo); + + if (out_lo != 0) + return -out_lo; + + *data = out_hi; + + preempt_enable(); + + return 0; +} + +static int tegra23x_mce_write_uncore_perfmon(u32 req, u32 data) +{ + uint32_t cpu_idx; + u32 out_lo, out_hi; + int32_t ret = 0; + + preempt_disable(); + + cpu_idx = get_ari_address_index(); + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_PERFMON, req, data); + + if (ret) + return ret; + + out_lo = ari_get_response_low(ari_bar_array[cpu_idx]); + out_hi = ari_get_response_high(ari_bar_array[cpu_idx]); + + pr_debug("%s: write status = %u\n", __func__, out_lo); + + if (out_lo != 0) + return -out_lo; + + preempt_enable(); + + return 0; +} + +static int tegra23x_mce_read_cstate_stats(u32 state, u64 *stats) +{ + uint32_t cpu_idx; + int32_t ret = 0; + + if (IS_ERR_OR_NULL(stats)) + return -EINVAL; + + preempt_disable(); + cpu_idx = get_ari_address_index(); + ret = ari_send_request(ari_bar_array[cpu_idx], 0U, + (u32)TEGRA_ARI_CSTATE_STAT_QUERY, state, 0U); + if (ret) + return ret; + *stats = ari_get_response_low(ari_bar_array[cpu_idx]); + preempt_enable(); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +static struct dentry *mce_debugfs; + +static int tegra23x_mce_versions_get(void *data, u64 *val) +{ + u32 major = 0; + u32 minor = 0; + u64 version = 0; + int ret; + + *val = 0; + ret = tegra_mce_read_versions(&major, &minor); + if (!ret) { + version = (u64)major; + *val = (version << 32) | minor; + } + return ret; +} + +static int tegra23x_mce_echo_set(void *data, u64 val) +{ + u64 matched = 0; + int ret; + + ret = tegra_mce_echo_data(val, &matched); + if (ret) + return ret; + return 0; +} + +static void tegra_get_cpu_mpidr(void *mpidr) +{ + *((u64 *)mpidr) = read_cpuid_mpidr() & MPIDR_HWID_BITMASK; +} + +#define MCE_STAT_ID_SHIFT 16UL +#define MAX_CSTATE_ENTRIES 3U +#define MAX_CLUSTERS 3U + +struct cstats_req { + char *name; + uint32_t id; +}; + +struct cstats_resp { + uint32_t stats[3]; /* entries, entry_time_sum, exit_time_sum */ + uint32_t log_id; +}; + +static struct cstats_req core_req[MAX_CSTATE_ENTRIES] = { + { "C7_ENTRIES", TEGRA_ARI_STAT_QUERY_C7_ENTRIES}, + { "C7_ENTRY_TIME_SUM", TEGRA_ARI_STAT_QUERY_C7_ENTRY_TIME_SUM}, + { "C7_EXIT_TIME_SUM", TEGRA_ARI_STAT_QUERY_C7_EXIT_TIME_SUM}, +}; + +static struct cstats_req cluster_req[MAX_CSTATE_ENTRIES] = { + { "CC7_ENTRIES", TEGRA_ARI_STAT_QUERY_CC7_ENTRIES}, + { "CC7_ENTRY_TIME_SUM", TEGRA_ARI_STAT_QUERY_CC7_ENTRY_TIME_SUM}, + { "CC7_EXIT_TIME_SUM", TEGRA_ARI_STAT_QUERY_CC7_EXIT_TIME_SUM}, +}; + +static struct cstats_req system_req[MAX_CSTATE_ENTRIES] = { + { "SC7_ENTRIES", TEGRA_ARI_STAT_QUERY_SC7_ENTRIES}, + { "SC7_CCPLEX_ENTRY_TIME_SUM", TEGRA_ARI_STAT_QUERY_SC7_ENTRY_TIME_SUM}, + { "SC7_CCPLEX_EXIT_TIME_SUM", TEGRA_ARI_STAT_QUERY_SC7_EXIT_TIME_SUM}, +}; + +static int tegra23x_mce_dbg_cstats_show(struct seq_file *s, void *data) +{ + u64 val; + u32 mce_index; + uint32_t cpu, mpidr_core, mpidr_cl, mpidr_lin, i, j; + struct cstats_resp core_resp[MAX_CPUS] = { 0 }; + struct cstats_resp cl_resp[MAX_CLUSTERS] = { 0 }; + struct cstats_resp sys_resp = { 0 }; + u64 mpidr = 0; + + for_each_online_cpu(cpu) { + smp_call_function_single(cpu, tegra_get_cpu_mpidr, &mpidr, true); + mpidr_cl = MPIDR_AFFINITY_LEVEL(mpidr, 2); + mpidr_core = MPIDR_AFFINITY_LEVEL(mpidr, 1); + mpidr_lin = ((mpidr_cl * MAX_CORES_PER_CLUSTER) + mpidr_core); + + /* core cstats */ + for (i = 0; i < MAX_CSTATE_ENTRIES; i++) { + mce_index = (core_req[i].id << + MCE_STAT_ID_SHIFT) + mpidr_lin; + if (tegra23x_mce_read_cstate_stats(mce_index, &val)) + pr_err("mce: failed to read cstat: %x\n", mce_index); + else { + core_resp[mpidr_lin].stats[i] = val; + core_resp[mpidr_lin].log_id = cpu; + } + } + + /* + * cluster cstats + * for multiple cores in the same cluster we end up calling + * more than once. Optimize this later + */ + for (i = 0; i < MAX_CSTATE_ENTRIES; i++) { + mce_index = (cluster_req[i].id << + MCE_STAT_ID_SHIFT) + mpidr_cl; + if (tegra23x_mce_read_cstate_stats(mce_index, &val)) + pr_err("mce: failed to read cstat: %x\n", mce_index); + else + cl_resp[mpidr_cl].stats[i] = val; + } + } + + /* system cstats */ + for (i = 0; i < MAX_CSTATE_ENTRIES; i++) { + mce_index = (system_req[i].id << MCE_STAT_ID_SHIFT); + if (tegra23x_mce_read_cstate_stats(mce_index, &val)) + pr_err("mce: failed to read cstat: %x\n", mce_index); + else + sys_resp.stats[i] = val; + } + + seq_puts(s, "System Power States\n"); + seq_puts(s, "---------------------------------------------------\n"); + seq_printf(s, "%-25s%-15s\n", "name", "count/time"); + seq_puts(s, "---------------------------------------------------\n"); + for (i = 0; i < MAX_CSTATE_ENTRIES; i++) + seq_printf(s, "%-25s%-20u\n", + system_req[i].name, sys_resp.stats[i]); + + seq_puts(s, "\nCluster Power States\n"); + seq_puts(s, "---------------------------------------------------\n"); + seq_printf(s, "%-25s%-15s%-15s\n", "name", "phy-id", "count/time"); + seq_puts(s, "---------------------------------------------------\n"); + for (j = 0; j < MAX_CLUSTERS; j++) { + for (i = 0; i < MAX_CSTATE_ENTRIES; i++) + seq_printf(s, "%-25s%-15d%-20u\n", + cluster_req[i].name, j, cl_resp[j].stats[i]); + } + + seq_puts(s, "\nCore Power States\n"); + seq_puts(s, "-------------------------------------------------------------------\n"); + seq_printf(s, "%-25s%-15s%-15s%-15s\n", "name", "mpidr-lin", "log-id", "count/time"); + seq_puts(s, "-------------------------------------------------------------------\n"); + for (j = 0; j < MAX_CPUS; j++) { + for (i = 0; i < MAX_CSTATE_ENTRIES; i++) + seq_printf(s, "%-25s%-15d%-15u%-20u\n", + core_req[i].name, j, core_resp[j].log_id, + core_resp[j].stats[i]); + } + + return 0; +} + +static int tegra23x_mce_dbg_cstats_open(struct inode *inode, struct file *file) +{ + int (*f)(struct seq_file *s, void *data); + + f = tegra23x_mce_dbg_cstats_show; + return single_open(file, f, inode->i_private); +} + +static const struct file_operations tegra23x_mce_cstats_fops = { + .open = tegra23x_mce_dbg_cstats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +DEFINE_SIMPLE_ATTRIBUTE(tegra23x_mce_versions_fops, tegra23x_mce_versions_get, + NULL, "%llx\n"); +DEFINE_SIMPLE_ATTRIBUTE(tegra23x_mce_echo_fops, NULL, + tegra23x_mce_echo_set, "%llx\n"); + +struct debugfs_entry { + const char *name; + const struct file_operations *fops; + mode_t mode; +}; + +/* Make sure to put an NULL entry at the end of each group */ +static struct debugfs_entry tegra23x_mce_attrs[] = { + { "versions", &tegra23x_mce_versions_fops, 0444 }, + { "echo", &tegra23x_mce_echo_fops, 0200 }, + { "cstats", &tegra23x_mce_cstats_fops, 0444 }, + { NULL, NULL, 0 } +}; + +static struct debugfs_entry *tegra_mce_attrs = tegra23x_mce_attrs; + +static int tegra23x_mce_init(void) +{ + struct debugfs_entry *fent; + struct dentry *dent; + int ret; + + mce_debugfs = debugfs_create_dir("tegra_mce", NULL); + if (!mce_debugfs) + return -ENOMEM; + + for (fent = tegra_mce_attrs; fent->name; fent++) { + dent = debugfs_create_file(fent->name, fent->mode, + mce_debugfs, NULL, fent->fops); + if (IS_ERR_OR_NULL(dent)) { + ret = dent ? PTR_ERR(dent) : -EINVAL; + pr_err("%s: failed to create debugfs (%s): %d\n", + __func__, fent->name, ret); + goto err; + } + } + + pr_debug("%s: init finished\n", __func__); + + return 0; + +err: + debugfs_remove_recursive(mce_debugfs); + + return ret; +} + +static void tegra23x_mce_exit(void) +{ + debugfs_remove_recursive(mce_debugfs); +} +#endif /* CONFIG_DEBUG_FS */ + +static struct tegra_mce_ops t23x_mce_ops = { + .read_versions = tegra23x_mce_read_versions, + .read_l3_cache_ways = tegra23x_mce_read_l4_cache_ways, + .write_l3_cache_ways = tegra23x_mce_write_l4_cache_ways, + .echo_data = tegra23x_mce_echo_data, + .read_uncore_perfmon = tegra23x_mce_read_uncore_perfmon, + .write_uncore_perfmon = tegra23x_mce_write_uncore_perfmon, + .read_cstate_stats = tegra23x_mce_read_cstate_stats, +}; + +static int t23x_mce_probe(struct platform_device *pdev) +{ + unsigned int cpu; + struct resource *res; + + /* this ARI NS mapping applies to Split, Lock-step and FS */ + for (cpu = 0; cpu < MAX_CPUS; cpu++) { + res = platform_get_resource(pdev, IORESOURCE_MEM, cpu); + ari_bar_array[cpu] = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ari_bar_array[cpu])) { + dev_err(&pdev->dev, "mapping ARI failed for %d\n", + cpu); + return PTR_ERR(ari_bar_array[cpu]); + } + } + tegra_mce_set_ops(&t23x_mce_ops); +#ifdef CONFIG_DEBUG_FS + tegra23x_mce_init(); +#endif + + return 0; +} + +static int t23x_mce_remove(struct platform_device *pdev) +{ +#ifdef CONFIG_DEBUG_FS + tegra23x_mce_exit(); +#endif + return 0; +} + +static const struct of_device_id t23x_mce_of_match[] = { + { .compatible = "nvidia,t23x-mce", .data = NULL }, + { }, +}; +MODULE_DEVICE_TABLE(of, t23x_mce_of_match); + +static struct platform_driver t23x_mce_driver = { + .probe = t23x_mce_probe, + .remove = t23x_mce_remove, + .driver = { + .owner = THIS_MODULE, + .name = "t23x-mce", + .of_match_table = of_match_ptr(t23x_mce_of_match), + }, +}; + +module_platform_driver(t23x_mce_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra23x MCE driver"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform/tegra/t23x_ari.h b/include/linux/platform/tegra/t23x_ari.h new file mode 100644 index 00000000..97fc1c63 --- /dev/null +++ b/include/linux/platform/tegra/t23x_ari.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef T23X_ARI_H +#define T23X_ARI_H + +/* ARI Version numbers */ +#define TEGRA_ARI_VERSION_MAJOR 8UL +#define TEGRA_ARI_VERSION_MINOR 1UL + + +/* + * ARI Request IDs + * + * TODO: RENUMBER range before finalization + * NOTE: for documentation purposes, only documenting gaps + * in ranges, to indicate that we know about the missing ids + * + * Require NO LAB Locks + * range from 0 - 31 + */ +#define TEGRA_ARI_VERSION 0UL +#define TEGRA_ARI_ECHO 1UL +#define TEGRA_ARI_NUM_CORES 2UL +#define TEGRA_ARI_CSTATE_STAT_QUERY 3UL +/* Undefined 4 - 28 */ +/* Debug Only ARIs at the end of the NO LAB Lock Range */ +#define TEGRA_ARI_CORE_DEBUG_RECOVERY 29UL +#define TEGRA_ARI_DSU_DEBUG_RECOVERY 30UL +#define TEGRA_ARI_CLUSTER_WARM_RESET 31UL + +/* + * Require CORE LAB Lock -- obtained by MTM from ARI + * range from 32 - 63 + */ +/* UNDEFINED 32 */ +/* UNDEFINED 33 */ +#define TEGRA_ARI_ONLINE_CORE 34UL +#define TEGRA_ARI_ENTER_CSTATE 35UL +/* UNDEFINED 36 */ +#define TEGRA_ARI_TRIGGER_ONLINE_IST 37UL +/* + * Require CLUSTER and CORE LAB Lock -- obtained by MTM from ARI + * range from 64 - 95 + */ +/* UNDEFINED 64 */ +#define TEGRA_ARI_NVFREQ_REQ 65UL +#define TEGRA_ARI_NVFREQ_FEEDBACK 66UL +#define TEGRA_ARI_CLUSTER_ATCLKEN 67UL +/* + * Require CCPLEX, CLUSTER and CORE LAB Lock -- obtained by MTM from ARI + * range from 96 - 127 + */ +#define TEGRA_ARI_CCPLEX_CACHE_CONTROL 96UL +#define TEGRA_ARI_CCPLEX_CACHE_CLEAN 97UL +/* UNDEFINED 98 */ +#define TEGRA_ARI_CCPLEX_LATIC_ON 99UL +#define TEGRA_ARI_UPDATE_CROSSOVER 100UL +/* UNDEFINED 101 */ +#define TEGRA_ARI_CCPLEX_SHUTDOWN 102UL +/* UNDEFINED 103 */ +#define TEGRA_ARI_CSTATE_INFO 104UL +#define TEGRA_ARI_IS_SC7_ALLOWED 105UL +/* UNDEFINED 106 */ +/* UNDEFINED 107 */ +#define TEGRA_ARI_SECURITY_CONFIG 108UL +#define TEGRA_ARI_UPDATE_CCPLEX_CARVEOUTS 109UL +#define TEGRA_ARI_DDA_CONTROL 110UL +#define TEGRA_ARI_PERFMON 111UL +#define TEGRA_ARI_DEBUG_CONFIG 112UL +#define TEGRA_ARI_CCPLEX_ERROR_RECOVERY_RESET 114UL + +/* Values for ARI CSTATE STAT QUERY */ +#define TEGRA_ARI_STAT_QUERY_SC7_ENTRIES 1UL +#define TEGRA_ARI_STAT_QUERY_CC7_ENTRIES 6UL +#define TEGRA_ARI_STAT_QUERY_C7_ENTRIES 14UL +#define TEGRA_ARI_STAT_QUERY_SC7_ENTRY_TIME_SUM 60UL +#define TEGRA_ARI_STAT_QUERY_CC7_ENTRY_TIME_SUM 61UL +#define TEGRA_ARI_STAT_QUERY_C7_ENTRY_TIME_SUM 64UL +#define TEGRA_ARI_STAT_QUERY_SC7_EXIT_TIME_SUM 70UL +#define TEGRA_ARI_STAT_QUERY_CC7_EXIT_TIME_SUM 71UL +#define TEGRA_ARI_STAT_QUERY_C7_EXIT_TIME_SUM 74UL + +/* Values for ARI UPDATE CROSSOVER */ +#define TEGRA_ARI_CROSSOVER_C7_LOWER_BOUND 0UL +#define TEGRA_ARI_CROSSOVER_CC7_LOWER_BOUND 1UL + +/* Values for ARI UPDATE CCPLEX CARVEOUTS */ +#define TEGRA_ARI_UPDATE_CCPLEX_CARVEOUTS_ALL 0UL + +#endif /* T23X_ARI_H */ diff --git a/include/linux/platform/tegra/tegra-mce.h b/include/linux/platform/tegra/tegra-mce.h new file mode 100644 index 00000000..dd852862 --- /dev/null +++ b/include/linux/platform/tegra/tegra-mce.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#ifndef _LINUX_TEGRA_MCE_H +#define _LINUX_TEGRA_MCE_H + +/* NOTE: + * For correct version validation, below two defines need to be + * updated whenever there is a new ARI implementation. + */ +#define CUR_ARI_VER_MAJOR 1 +#define CUR_ARI_VER_MINOR 2 + +enum { + TEGRA_MCE_XOVER_C1_C6, /* Only valid for Denver */ + TEGRA_MCE_XOVER_CC1_CC6, + TEGRA_MCE_XOVER_CC1_CC7, + + TEGRA_MCE_XOVER_MAX = TEGRA_MCE_XOVER_CC1_CC7 +}; + +enum { + TEGRA_MCE_CSTATS_CLEAR, + + TEGRA_MCE_CSTATS_ENTRIES_SC7, + TEGRA_MCE_CSTATS_ENTRIES_SC4, + TEGRA_MCE_CSTATS_ENTRIES_SC3, + TEGRA_MCE_CSTATS_ENTRIES_SC2, + TEGRA_MCE_CSTATS_ENTRIES_CCP3, + TEGRA_MCE_CSTATS_ENTRIES_A57_CC6, + TEGRA_MCE_CSTATS_ENTRIES_A57_CC7, + TEGRA_MCE_CSTATS_ENTRIES_D15_CC6, + TEGRA_MCE_CSTATS_ENTRIES_D15_CC7, + TEGRA_MCE_CSTATS_ENTRIES_D15_CORE0_C6, + TEGRA_MCE_CSTATS_ENTRIES_D15_CORE1_C6, + /* RESV: 12-13 */ + TEGRA_MCE_CSTATS_ENTRIES_D15_CORE0_C7 = 14, + TEGRA_MCE_CSTATS_ENTRIES_D15_CORE1_C7, + /* RESV: 16-17 */ + TEGRA_MCE_CSTATS_ENTRIES_A57_CORE0_C7 = 18, + TEGRA_MCE_CSTATS_ENTRIES_A57_CORE1_C7, + TEGRA_MCE_CSTATS_ENTRIES_A57_CORE2_C7, + TEGRA_MCE_CSTATS_ENTRIES_A57_CORE3_C7, + TEGRA_MCE_CSTATS_LAST_ENTRY_D15_CORE0, + TEGRA_MCE_CSTATS_LAST_ENTRY_D15_CORE1, + /* RESV: 24-25 */ + TEGRA_MCE_CSTATS_LAST_ENTRY_A57_CORE0, + TEGRA_MCE_CSTATS_LAST_ENTRY_A57_CORE1, + TEGRA_MCE_CSTATS_LAST_ENTRY_A57_CORE2, + TEGRA_MCE_CSTATS_LAST_ENTRY_A57_CORE3, + + TEGRA_MCE_CSTATS_MAX = TEGRA_MCE_CSTATS_LAST_ENTRY_A57_CORE3, +}; + +enum { + TEGRA_MCE_ENUM_D15_CORE0, + TEGRA_MCE_D15_CORE1, + /* RESV: 2-3 */ + TEGRA_MCE_ENUM_A57_0 = 4, + TEGRA_MCE_ENUM_A57_1, + TEGRA_MCE_ENUM_A57_2, + TEGRA_MCE_ENUM_A57_3, + + TEGRA_MCE_ENUM_MAX = TEGRA_MCE_ENUM_A57_3, +}; + +enum { + TEGRA_MCE_FEATURE_CCP3, +}; + +/* MCA support */ +typedef union { + struct { + u8 cmd; + u8 subidx; + u8 idx; + u8 inst; + }; + struct { + u32 low; + u32 high; + }; + u64 data; +} mca_cmd_t; + +/* NOTE: These functions will return -ENOTSUPP if no implementation */ +int tegra_mce_enter_cstate(u32 state, u32 wake_time); +int tegra_mce_update_cstate_info(u32 cluster, u32 ccplex, u32 system, + u8 force, u32 wake_mask, bool valid); +int tegra_mce_update_crossover_time(u32 type, u32 time); +int tegra_mce_read_cstate_stats(u32 state, u64 *stats); +int tegra_mce_write_cstate_stats(u32 state, u32 stats); +int tegra_mce_is_sc7_allowed(u32 state, u32 wake, u32 *allowed); +int tegra_mce_online_core(int cpu); +int tegra_mce_cc3_ctrl(u32 ndiv, u32 vindex, u8 enable); +int tegra_mce_echo_data(u64 data, u64 *matched); +int tegra_mce_read_versions(u32 *major, u32 *minor); +int tegra_mce_enum_features(u64 *features); +int tegra_mce_read_uncore_mca(mca_cmd_t cmd, u64 *data, u32 *error); +int tegra_mce_write_uncore_mca(mca_cmd_t cmd, u64 data, u32 *error); +int tegra_mce_read_uncore_perfmon(u32 req, u32 *data); +int tegra_mce_write_uncore_perfmon(u32 req, u32 data); +int tegra_mce_enable_latic(void); +int tegra_mce_write_dda_ctrl(u32 index, u64 value); +int tegra_mce_read_dda_ctrl(u32 index, u64 *value); + +/* L3 cache ways read/write functions */ +int tegra_mce_read_l3_cache_ways(u64 *value); +int tegra_mce_write_l3_cache_ways(u64 data, u64 *value); + +int tegra_mce_read_rt_safe_mask(u64 *); +int tegra_mce_write_rt_safe_mask(u64); +int tegra_mce_read_rt_window_us(u64 *); +int tegra_mce_write_rt_window_us(u64); +int tegra_mce_read_rt_fwd_progress_us(u64 *); +int tegra_mce_write_rt_fwd_progress_us(u64); + +struct tegra_mce_ops { + int (*enter_cstate)(u32, u32); + int (*update_cstate_info)(u32, u32, u32, u8, u32, bool); + int (*update_crossover_time)(u32, u32); + int (*read_cstate_stats)(u32, u64 *); + int (*write_cstate_stats)(u32, u32); + int (*is_sc7_allowed)(u32, u32, u32 *); + int (*online_core)(int); + int (*cc3_ctrl)(u32, u32, u8); + int (*echo_data)(u64, u64*); + int (*read_versions)(u32 *, u32 *); + int (*enum_features)(u64 *); + int (*read_uncore_mca)(mca_cmd_t, u64 *, u32 *); + int (*write_uncore_mca)(mca_cmd_t, u64, u32 *); + int (*read_uncore_perfmon)(u32, u32 *); + int (*write_uncore_perfmon)(u32, u32); + int (*enable_latic)(void); + int (*write_dda_ctrl)(u32 index, u64 value); + int (*read_dda_ctrl)(u32 index, u64 *value); + int (*read_l3_cache_ways)(u64 *value); + int (*write_l3_cache_ways)(u64 data, u64 *value); + int (*read_rt_safe_mask)(u64 *); + int (*write_rt_safe_mask)(u64); + int (*read_rt_window_us)(u64 *); + int (*write_rt_window_us)(u64); + int (*read_rt_fwd_progress_us)(u64 *); + int (*write_rt_fwd_progress_us)(u64); +}; + +void tegra_mce_set_ops(struct tegra_mce_ops *); +#endif /* _LINUX_TEGRA_MCE_H */