// SPDX-License-Identifier: GPL-2.0
/*
* NVIDIA Tegra410 C2C PMU driver.
*
* Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/cpumask.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/property.h>
/* The C2C interface types in Tegra410. */
#define C2C_TYPE_NVLINK 0x0
#define C2C_TYPE_NVCLINK 0x1
#define C2C_TYPE_NVDLINK 0x2
#define C2C_TYPE_COUNT 0x3
/* The type of the peer device connected to the C2C interface. */
#define C2C_PEER_TYPE_CPU 0x0
#define C2C_PEER_TYPE_GPU 0x1
#define C2C_PEER_TYPE_CXLMEM 0x2
#define C2C_PEER_TYPE_COUNT 0x3
/* The number of peer devices can be connected to the C2C interface. */
#define C2C_NR_PEER_CPU 0x1
#define C2C_NR_PEER_GPU 0x2
#define C2C_NR_PEER_CXLMEM 0x1
#define C2C_NR_PEER_MAX 0x2
/* Number of instances on each interface. */
#define C2C_NR_INST_NVLINK 14
#define C2C_NR_INST_NVCLINK 12
#define C2C_NR_INST_NVDLINK 16
#define C2C_NR_INST_MAX 16
/* Register offsets. */
#define C2C_CTRL 0x864
#define C2C_IN_STATUS 0x868
#define C2C_CYCLE_CNTR 0x86c
#define C2C_IN_RD_CUM_OUTS_CNTR 0x874
#define C2C_IN_RD_REQ_CNTR 0x87c
#define C2C_IN_WR_CUM_OUTS_CNTR 0x884
#define C2C_IN_WR_REQ_CNTR 0x88c
#define C2C_OUT_STATUS 0x890
#define C2C_OUT_RD_CUM_OUTS_CNTR 0x898
#define C2C_OUT_RD_REQ_CNTR 0x8a0
#define C2C_OUT_WR_CUM_OUTS_CNTR 0x8a8
#define C2C_OUT_WR_REQ_CNTR 0x8b0
/* C2C_IN_STATUS register field. */
#define C2C_IN_STATUS_CYCLE_OVF BIT(0)
#define C2C_IN_STATUS_IN_RD_CUM_OUTS_OVF BIT(1)
#define C2C_IN_STATUS_IN_RD_REQ_OVF BIT(2)
#define C2C_IN_STATUS_IN_WR_CUM_OUTS_OVF BIT(3)
#define C2C_IN_STATUS_IN_WR_REQ_OVF BIT(4)
/* C2C_OUT_STATUS register field. */
#define C2C_OUT_STATUS_OUT_RD_CUM_OUTS_OVF BIT(0)
#define C2C_OUT_STATUS_OUT_RD_REQ_OVF BIT(1)
#define C2C_OUT_STATUS_OUT_WR_CUM_OUTS_OVF BIT(2)
#define C2C_OUT_STATUS_OUT_WR_REQ_OVF BIT(3)
/* Events. */
#define C2C_EVENT_CYCLES 0x0
#define C2C_EVENT_IN_RD_CUM_OUTS 0x1
#define C2C_EVENT_IN_RD_REQ 0x2
#define C2C_EVENT_IN_WR_CUM_OUTS 0x3
#define C2C_EVENT_IN_WR_REQ 0x4
#define C2C_EVENT_OUT_RD_CUM_OUTS 0x5
#define C2C_EVENT_OUT_RD_REQ 0x6
#define C2C_EVENT_OUT_WR_CUM_OUTS 0x7
#define C2C_EVENT_OUT_WR_REQ 0x8
#define C2C_NUM_EVENTS 0x9
#define C2C_MASK_EVENT 0xFF
#define C2C_MAX_ACTIVE_EVENTS 32
#define C2C_ACTIVE_CPU_MASK 0x0
#define C2C_ASSOCIATED_CPU_MASK 0x1
/*
* Maximum poll count for reading counter value using high-low-high sequence.
*/
#define HILOHI_MAX_POLL 1000
static unsigned long nv_c2c_pmu_cpuhp_state;
/* PMU descriptor. */
/* C2C type information. */
struct nv_c2c_pmu_data {
unsigned int c2c_type;
unsigned int nr_inst;
const char *name_fmt;
};
static const struct nv_c2c_pmu_data nv_c2c_pmu_data[] = {
[C2C_TYPE_NVLINK] = {
.c2c_type = C2C_TYPE_NVLINK,
.nr_inst = C2C_NR_INST_NVLINK,
.name_fmt = "nvidia_nvlink_c2c_pmu_%u",
},
[C2C_TYPE_NVCLINK] = {
.c2c_type = C2C_TYPE_NVCLINK,
.nr_inst = C2C_NR_INST_NVCLINK,
.name_fmt = "nvidia_nvclink_pmu_%u",
},
[C2C_TYPE_NVDLINK] = {
.c2c_type = C2C_TYPE_NVDLINK,
.nr_inst = C2C_NR_INST_NVDLINK,
.name_fmt = "nvidia_nvdlink_pmu_%u",
},
};
/* Tracks the events assigned to the PMU for a given logical index. */
struct nv_c2c_pmu_hw_events {
/* The events that are active. */
struct perf_event *events[C2C_MAX_ACTIVE_EVENTS];
/*
* Each bit indicates a logical counter is being used (or not) for an
* event.
*/
DECLARE_BITMAP(used_ctrs, C2C_MAX_ACTIVE_EVENTS);
};
struct nv_c2c_pmu {
struct pmu pmu;
struct device *dev;
struct acpi_device *acpi_dev;
const char *name;
const char *identifier;
const struct nv_c2c_pmu_data *data;
unsigned int peer_type;
unsigned int socket;
unsigned int nr_peer;
unsigned long peer_insts[C2C_NR_PEER_MAX][BITS_TO_LONGS(C2C_NR_INST_MAX)];
u32 filter_default;
struct nv_c2c_pmu_hw_events hw_events;
cpumask_t associated_cpus;
cpumask_t active_cpu;
struct hlist_node cpuhp_node;
const struct attribute_group **attr_groups;
void __iomem *base_broadcast;
void __iomem *base[C2C_NR_INST_MAX];
};
#define to_c2c_pmu(p) (container_of(p, struct nv_c2c_pmu, pmu))
/* Get event type from perf_event. */
static inline u32 get_event_type(struct perf_event *event)
{
return (event->attr.config) & C2C_MASK_EVENT;
}
static inline u32 get_filter_mask(struct perf_event *event)
{
u32 filter;
struct nv_c2c_pmu *c2c_pmu = to_c2c_pmu(event->pmu);
filter = ((u32)event->attr.config1) & c2c_pmu->filter_default;
if (filter == 0)
filter = c2c_pmu->filter_default;
return filter;
}
/* PMU operations. */
static int nv_c2c_pmu_get_event_idx(struct nv_c2c_pmu_hw_events *hw_events,
struct perf_event *event)
{
u32 idx;
idx = find_first_zero_bit(hw_events->used_ctrs, C2C_MAX_ACTIVE_EVENTS);
if (idx >= C2C_MAX_ACTIVE_EVENTS)
return -EAGAIN;
set_bit(idx, hw_events->used_ctrs);
return idx;
}
static bool
nv_c2c_pmu_validate_event(struct pmu *pmu,
struct nv_c2c_pmu_hw_events *hw_events