[RFC PATCH 07/22] perf: ti_k3_ddr: Add TI K3 DDR performance monitor driver
From: MANNURU VENKATESWARLU
Date: Tue Jul 14 2026 - 08:57:07 EST
From: Aarya Chaumal <a-chaumal@xxxxxx>
Add a PMU driver for the DDR subsystem performance counters on TI K3
SoCs (J721E, J721S2, J7200, J784S4, AM62, AM62A, AM62P, AM64).
The Cadence DDR controller provides 4 hardware counters, each
programmable to count one of 36 events: read/write commands, activates,
refreshes, precharges, power-down/self-refresh cycles, bus turnarounds,
address collisions, and FIFO utilization cycles.
Each DDR controller registers as a separate PMU named k3_ddr<N> and
exposes an identifier sysfs attribute that jevents uses to select the
correct JSON event table for the SoC.
Signed-off-by: Aarya Chaumal <a-chaumal@xxxxxx>
Signed-off-by: MANNURU VENKATESWARLU <v-mannuru@xxxxxx>
---
drivers/perf/Kconfig | 11 +
drivers/perf/Makefile | 1 +
drivers/perf/ti_k3_ddr_perf.c | 532 ++++++++++++++++++++++++++++++++++
3 files changed, 544 insertions(+)
create mode 100644 drivers/perf/ti_k3_ddr_perf.c
diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
index 245e7bb763b99..821fc6f81bef0 100644
--- a/drivers/perf/Kconfig
+++ b/drivers/perf/Kconfig
@@ -273,6 +273,17 @@ config ALIBABA_UNCORE_DRW_PMU
source "drivers/perf/hisilicon/Kconfig"
+config TI_K3_DDR_PMU
+ tristate "Texas Instruments K3 DDR perf monitor"
+ depends on MFD_TI_DDRSS
+ help
+ Say Y here to enable the DDR performance monitor on TI K3 SoCs.
+ Exposes DDR read/write and other memory throughput events via perf.
+ Supports J721E, J721S2, J7200, J784S4, AM62, AM62A, AM62P, AM64.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ti_k3_ddr_perf.
+
config MARVELL_CN10K_DDR_PMU
tristate "Enable MARVELL CN10K DRAM Subsystem(DSS) PMU Support"
depends on ARCH_THUNDER || (COMPILE_TEST && 64BIT)
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index eb8a022dad9a7..c11c0013cbfd9 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_ARM_DMC620_PMU) += arm_dmc620_pmu.o
obj-$(CONFIG_MARVELL_CN10K_TAD_PMU) += marvell_cn10k_tad_pmu.o
obj-$(CONFIG_MARVELL_CN10K_DDR_PMU) += marvell_cn10k_ddr_pmu.o
obj-$(CONFIG_MARVELL_PEM_PMU) += marvell_pem_pmu.o
+obj-$(CONFIG_TI_K3_DDR_PMU) += ti_k3_ddr_perf.o
obj-$(CONFIG_APPLE_M1_CPU_PMU) += apple_m1_cpu_pmu.o
obj-$(CONFIG_ALIBABA_UNCORE_DRW_PMU) += alibaba_uncore_drw_pmu.o
obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
diff --git a/drivers/perf/ti_k3_ddr_perf.c b/drivers/perf/ti_k3_ddr_perf.c
new file mode 100644
index 0000000000000..3be4e40adf46f
--- /dev/null
+++ b/drivers/perf/ti_k3_ddr_perf.c
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI DDR pmu driver
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ */
+
+#include <linux/cpuhotplug.h>
+#include <linux/device.h>
+#include <linux/hrtimer.h>
+#include <linux/io.h>
+#include <linux/mfd/ti-ddrss.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/perf_event.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+
+#define NUM_COUNTERS 4
+#define TIMER_PERIOD 1000000000u
+#define to_k3_ddr_pmu(p) container_of(p, struct k3_ddr_pmu, pmu)
+
+/* Maximum event config value from the hardware event table */
+#define K3_DDR_PMU_MAX_EVENT 0x23
+
+#define DDR_PERF_DEV_NAME "k3_ddr"
+#define DDR_CPUHP_CB_NAME DDR_PERF_DEV_NAME "_perf_pmu"
+
+static DEFINE_IDA(k3_ddr_ida);
+
+/**
+ * struct k3_ddr_pmu - a struct containing all information needed by pmu driver
+ * @pmu: pmu instance
+ * @cntrl_base: base address of the control register for the counters
+ * @counter_base: base address of the first counter
+ * @cpu: cpu to which pmu is attached to
+ * @dev: reference to the struct device
+ * @events: array of perf events registered by the driver
+ * @active_events: number of active perf events
+ * @cpuhp_state: cpu hotplug state
+ * @node: node for the cpu hotplug state
+ * @identifier: SoC identifier string for /sys/.../identifier
+ * @hrtimer: hrtimer for sampling events accurately
+ * @id: driver id
+ *
+ * PMU driver related information
+ */
+struct k3_ddr_pmu {
+ struct pmu pmu;
+ void __iomem *cntrl_base;
+ void __iomem *counter_base;
+ unsigned int cpu;
+ struct device *dev;
+ struct perf_event *events[NUM_COUNTERS];
+ unsigned int active_events;
+ enum cpuhp_state cpuhp_state;
+ struct hlist_node node;
+ const char *identifier;
+ struct hrtimer hrtimer;
+ int id;
+};
+
+static ssize_t k3_ddr_perf_identifier_show(struct device *dev,
+ struct device_attribute *attr,
+ char *page)
+{
+ struct k3_ddr_pmu *pmu = dev_get_drvdata(dev);
+
+ if (!pmu || !pmu->identifier)
+ return 0;
+
+ return sysfs_emit(page, "%s\n", pmu->identifier);
+}
+
+static umode_t k3_ddr_perf_identifier_attr_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct k3_ddr_pmu *pmu = dev_get_drvdata(dev);
+
+ if (!pmu->identifier)
+ return 0;
+
+ return attr->mode;
+}
+
+static struct device_attribute k3_ddr_perf_identifier_attr =
+ __ATTR(identifier, 0444, k3_ddr_perf_identifier_show, NULL);
+
+static struct attribute *k3_ddr_perf_identifier_attrs[] = {
+ &k3_ddr_perf_identifier_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group k3_ddr_perf_identifier_attr_group = {
+ .attrs = k3_ddr_perf_identifier_attrs,
+ .is_visible = k3_ddr_perf_identifier_attr_visible,
+};
+
+static ssize_t k3_ddr_perf_cpumask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct k3_ddr_pmu *pmu = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu)));
+}
+
+static struct device_attribute k3_ddr_perf_cpumask_attr =
+ __ATTR(cpumask, 0444, k3_ddr_perf_cpumask_show, NULL);
+
+static struct attribute *k3_ddr_perf_cpumask_attrs[] = {
+ &k3_ddr_perf_cpumask_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group k3_ddr_perf_cpumask_attr_group = {
+ .attrs = k3_ddr_perf_cpumask_attrs,
+};
+
+static ssize_t
+k3_ddr_pmu_event_show(struct device *dev, struct device_attribute *attr,
+ char *page)
+{
+ struct perf_pmu_events_attr *pmu_attr;
+
+ pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
+
+ return sysfs_emit(page, "event=0x%02llx\n", pmu_attr->id);
+}
+
+#define K3_DDR_PMU_EVENT_ATTR(_name, _id) \
+ PMU_EVENT_ATTR_ID(_name, k3_ddr_pmu_event_show, _id)
+
+static struct attribute *k3_ddr_perf_events_attrs[] = {
+ K3_DDR_PMU_EVENT_ATTR(write, 0x00),
+ K3_DDR_PMU_EVENT_ATTR(read, 0x01),
+ K3_DDR_PMU_EVENT_ATTR(read_rmw, 0x02),
+ K3_DDR_PMU_EVENT_ATTR(activate, 0x03),
+ K3_DDR_PMU_EVENT_ATTR(precharge, 0x04),
+ K3_DDR_PMU_EVENT_ATTR(precharge_all, 0x05),
+ K3_DDR_PMU_EVENT_ATTR(mode_reg_read, 0x06),
+ K3_DDR_PMU_EVENT_ATTR(mode_reg_write, 0x07),
+ K3_DDR_PMU_EVENT_ATTR(per_bank_refresh, 0x08),
+ K3_DDR_PMU_EVENT_ATTR(auto_refresh, 0x09),
+ K3_DDR_PMU_EVENT_ATTR(zq_calib_long, 0x0a),
+ K3_DDR_PMU_EVENT_ATTR(zq_calib_short, 0x0b),
+ K3_DDR_PMU_EVENT_ATTR(bus_turn_around, 0x0c),
+ K3_DDR_PMU_EVENT_ATTR(write_write_collision, 0x0d),
+ K3_DDR_PMU_EVENT_ATTR(write_read_collision, 0x0e),
+ K3_DDR_PMU_EVENT_ATTR(read_write_collision, 0x0f),
+ K3_DDR_PMU_EVENT_ATTR(read_read_collision, 0x10),
+ K3_DDR_PMU_EVENT_ATTR(exit_power_down_self_refresh, 0x11),
+ K3_DDR_PMU_EVENT_ATTR(entry_power_down_self_refresh, 0x12),
+ K3_DDR_PMU_EVENT_ATTR(power_down_self_refresh_cycles, 0x13),
+ K3_DDR_PMU_EVENT_ATTR(exit_power_down, 0x14),
+ K3_DDR_PMU_EVENT_ATTR(entry_power_down, 0x15),
+ K3_DDR_PMU_EVENT_ATTR(power_down_cycles, 0x16),
+ K3_DDR_PMU_EVENT_ATTR(exit_self_refresh, 0x17),
+ K3_DDR_PMU_EVENT_ATTR(entry_self_refresh, 0x18),
+ K3_DDR_PMU_EVENT_ATTR(self_refresh_cycles, 0x19),
+ /* 0x1a and 0x1b are reserved in the hardware event table */
+ K3_DDR_PMU_EVENT_ATTR(command_queue_full_cycles, 0x1c),
+ K3_DDR_PMU_EVENT_ATTR(info_fifo_full_cycles, 0x1d),
+ K3_DDR_PMU_EVENT_ATTR(write_latency_fifo_full_cycles, 0x1e),
+ K3_DDR_PMU_EVENT_ATTR(port_command_fifo_full_cycles, 0x1f),
+ K3_DDR_PMU_EVENT_ATTR(write_response_fifo_full_cycles, 0x20),
+ K3_DDR_PMU_EVENT_ATTR(port_write_fifo_full_cycles, 0x21),
+ K3_DDR_PMU_EVENT_ATTR(core_read_fifo_full_cycles, 0x22),
+ K3_DDR_PMU_EVENT_ATTR(port_read_fifo_full_cycles, 0x23),
+ NULL,
+};
+
+static const struct attribute_group k3_ddr_perf_events_attr_group = {
+ .name = "events",
+ .attrs = k3_ddr_perf_events_attrs,
+};
+
+PMU_FORMAT_ATTR(event, "config:0-5");
+
+static struct attribute *k3_ddr_perf_format_attrs[] = {
+ &format_attr_event.attr,
+ NULL,
+};
+
+static const struct attribute_group k3_ddr_perf_format_attr_group = {
+ .name = "format",
+ .attrs = k3_ddr_perf_format_attrs,
+};
+
+static const struct attribute_group *attr_groups[] = {
+ &k3_ddr_perf_events_attr_group,
+ &k3_ddr_perf_format_attr_group,
+ &k3_ddr_perf_cpumask_attr_group,
+ &k3_ddr_perf_identifier_attr_group,
+ NULL,
+};
+
+static int k3_ddr_perf_alloc_counter(struct k3_ddr_pmu *pmu)
+{
+ for (int i = 0; i < NUM_COUNTERS; i++) {
+ if (!pmu->events[i]) {
+ pmu->active_events++;
+ return i;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static void k3_ddr_perf_free_counter(struct k3_ddr_pmu *pmu, int counter)
+{
+ pmu->active_events--;
+ pmu->events[counter] = NULL;
+}
+
+static u32 k3_ddr_perf_read_counter(struct k3_ddr_pmu *pmu, int counter)
+{
+ void __iomem *base = pmu->counter_base;
+
+ return readl_relaxed(base + counter * 4);
+}
+
+static int k3_ddr_perf_event_init(struct perf_event *event)
+{
+ struct k3_ddr_pmu *pmu = to_k3_ddr_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+
+ if (event->attr.type != event->pmu->type)
+ return -ENOENT;
+
+ if (event->attr.config > K3_DDR_PMU_MAX_EVENT)
+ return -EINVAL;
+
+ if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK)
+ return -EOPNOTSUPP;
+
+ if (event->cpu < 0)
+ return -EOPNOTSUPP;
+
+ if (event->group_leader->pmu != event->pmu &&
+ !is_software_event(event->group_leader))
+ return -EINVAL;
+
+ event->cpu = pmu->cpu;
+ hwc->idx = -1;
+
+ return 0;
+}
+
+static void k3_ddr_perf_counter_enable(struct k3_ddr_pmu *pmu, int config,
+ int counter, bool enable)
+{
+ u32 val;
+ u8 pos = counter * 8;
+
+ /*
+ * Each CNT_SEL field is 6 bits wide with 2 reserved bits above it.
+ * Bit layout: CNT1[5:0], rsvd[7:6], CNT2[13:8], rsvd[15:14], ...
+ * There is no hardware enable/disable - writing config=0 counts writes.
+ */
+ val = readl_relaxed(pmu->cntrl_base);
+ val &= ~(0x3FU << pos);
+ if (enable)
+ val |= ((u32)(config & 0x3F)) << pos;
+ writel_relaxed(val, pmu->cntrl_base);
+}
+
+static void k3_ddr_perf_event_update(struct perf_event *event)
+{
+ struct k3_ddr_pmu *pmu = to_k3_ddr_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ u64 new_raw_count, old_raw_count, actual_count;
+ int counter = hwc->idx;
+
+ do {
+ old_raw_count = local64_read(&hwc->prev_count);
+ new_raw_count = k3_ddr_perf_read_counter(pmu, counter);
+ } while (local64_cmpxchg(&hwc->prev_count, old_raw_count,
+ new_raw_count) != old_raw_count);
+
+ actual_count = (new_raw_count - old_raw_count) & 0xffffffff;
+
+ local64_add(actual_count, &event->count);
+}
+
+static void k3_ddr_perf_event_start(struct perf_event *event, int flags)
+{
+ struct k3_ddr_pmu *pmu = to_k3_ddr_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ int counter = hwc->idx;
+ u64 initial_raw_count;
+
+ k3_ddr_perf_counter_enable(pmu, event->attr.config, counter, true);
+ initial_raw_count = k3_ddr_perf_read_counter(pmu, counter);
+ local64_set(&hwc->prev_count, initial_raw_count);
+
+ /* Only arm the timer when the first event becomes active */
+ if (pmu->active_events == 1)
+ hrtimer_start(&pmu->hrtimer, ns_to_ktime(TIMER_PERIOD),
+ HRTIMER_MODE_REL_PINNED);
+
+ hwc->state = 0;
+}
+
+static int k3_ddr_perf_event_add(struct perf_event *event, int flags)
+{
+ struct k3_ddr_pmu *pmu = to_k3_ddr_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ int counter;
+
+ counter = k3_ddr_perf_alloc_counter(pmu);
+ if (counter < 0) {
+ dev_dbg(pmu->dev, "There are not enough counters\n");
+ return -EOPNOTSUPP;
+ }
+
+ pmu->events[counter] = event;
+
+ hwc->idx = counter;
+ local64_set(&hwc->prev_count, k3_ddr_perf_read_counter(pmu, counter));
+ hwc->state |= PERF_HES_STOPPED;
+
+ if (flags & PERF_EF_START)
+ k3_ddr_perf_event_start(event, flags);
+
+ return 0;
+}
+
+static void k3_ddr_perf_event_stop(struct perf_event *event, int flags)
+{
+ struct k3_ddr_pmu *pmu = to_k3_ddr_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ int counter = hwc->idx;
+
+ k3_ddr_perf_counter_enable(pmu, event->attr.config, counter, false);
+ k3_ddr_perf_event_update(event);
+
+ hwc->state |= PERF_HES_STOPPED;
+}
+
+static void k3_ddr_perf_event_del(struct perf_event *event, int flags)
+{
+ struct k3_ddr_pmu *pmu = to_k3_ddr_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ int counter = hwc->idx;
+
+ k3_ddr_perf_event_stop(event, PERF_EF_UPDATE);
+
+ k3_ddr_perf_free_counter(pmu, counter);
+
+ if (!pmu->active_events)
+ hrtimer_cancel(&pmu->hrtimer);
+
+ hwc->idx = -1;
+}
+
+static void k3_ddr_perf_overflow_handler(struct k3_ddr_pmu *pmu)
+{
+ for (int i = 0; i < NUM_COUNTERS; i++) {
+ struct perf_event *event = pmu->events[i];
+
+ if (event)
+ k3_ddr_perf_event_update(event);
+ }
+}
+
+static enum hrtimer_restart k3_ddr_perf_timer_handler(struct hrtimer *hrtimer)
+{
+ struct k3_ddr_pmu *pmu = container_of(hrtimer, struct k3_ddr_pmu, hrtimer);
+
+ k3_ddr_perf_overflow_handler(pmu);
+ hrtimer_forward_now(hrtimer, ns_to_ktime(TIMER_PERIOD));
+
+ return HRTIMER_RESTART;
+}
+
+static int k3_ddr_perf_init(struct k3_ddr_pmu *pmu, void __iomem *base,
+ struct device *dev)
+{
+ *pmu = (struct k3_ddr_pmu) {
+ .pmu = (struct pmu) {
+ .module = THIS_MODULE,
+ .capabilities = PERF_PMU_CAP_NO_EXCLUDE,
+ .task_ctx_nr = perf_invalid_context,
+ .attr_groups = attr_groups,
+ .event_init = k3_ddr_perf_event_init,
+ .add = k3_ddr_perf_event_add,
+ .del = k3_ddr_perf_event_del,
+ .start = k3_ddr_perf_event_start,
+ .stop = k3_ddr_perf_event_stop,
+ .read = k3_ddr_perf_event_update,
+ },
+ .cntrl_base = base,
+ .counter_base = base + 4,
+ .dev = dev,
+ };
+
+ pmu->id = ida_alloc(&k3_ddr_ida, GFP_KERNEL);
+ return pmu->id;
+}
+
+static int k3_ddr_perf_offline_cpu(unsigned int cpu, struct hlist_node *node)
+{
+ struct k3_ddr_pmu *pmu = hlist_entry_safe(node, struct k3_ddr_pmu, node);
+ int target;
+
+ if (cpu != pmu->cpu)
+ return 0;
+
+ target = cpumask_any_but(cpu_online_mask, cpu);
+ if (target >= nr_cpu_ids)
+ return 0;
+
+ perf_pmu_migrate_context(&pmu->pmu, cpu, target);
+ pmu->cpu = target;
+
+ return 0;
+}
+
+static int k3_ddr_perf_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ti_ddrss_dev *parent_dev = dev_get_drvdata(dev->parent);
+ u32 pmu_offset = 0x100;
+ struct k3_ddr_pmu *pmu;
+ void __iomem *base;
+ char *name;
+ int num;
+ int ret;
+
+ if (!parent_dev)
+ return dev_err_probe(dev, -ENODEV, "Parent DDR subsystem not found\n");
+
+ if (!parent_dev->sscfg)
+ return dev_err_probe(dev, -EINVAL, "Parent SSCFG not mapped\n");
+
+ /*
+ * PMU registers are in SSCFG space. The pmu DT node uses 2-cell
+ * addressing (#address-cells = 2), so cell index 1 is the low word
+ * of the 64-bit address, which is the byte offset within SSCFG.
+ */
+ of_property_read_u32_index(dev->of_node, "reg", 1, &pmu_offset);
+ base = parent_dev->sscfg + pmu_offset;
+
+ pmu = devm_kzalloc(dev, sizeof(*pmu), GFP_KERNEL);
+ if (!pmu)
+ return -ENOMEM;
+
+ num = k3_ddr_perf_init(pmu, base, dev);
+ if (num < 0)
+ return dev_err_probe(dev, num, "Failed to allocate PMU id\n");
+
+ name = devm_kasprintf(dev, GFP_KERNEL, DDR_PERF_DEV_NAME "%d", num);
+ if (!name) {
+ ret = -ENOMEM;
+ goto cpuhp_state_err;
+ }
+
+ pmu->identifier = parent_dev->cfg->identifier;
+ platform_set_drvdata(pdev, pmu);
+
+ hrtimer_setup(&pmu->hrtimer, k3_ddr_perf_timer_handler, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+
+ pmu->cpu = raw_smp_processor_id();
+ ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
+ DDR_CPUHP_CB_NAME,
+ NULL,
+ k3_ddr_perf_offline_cpu);
+ if (ret < 0) {
+ dev_err(dev, "cpuhp_setup_state_multi failed: %d\n", ret);
+ goto cpuhp_state_err;
+ }
+
+ pmu->cpuhp_state = ret;
+
+ ret = cpuhp_state_add_instance_nocalls(pmu->cpuhp_state, &pmu->node);
+ if (ret)
+ goto cpuhp_instance_err;
+
+ ret = perf_pmu_register(&pmu->pmu, name, -1);
+ if (ret)
+ goto k3_ddr_perf_err;
+
+ return 0;
+
+k3_ddr_perf_err:
+ cpuhp_state_remove_instance_nocalls(pmu->cpuhp_state, &pmu->node);
+cpuhp_instance_err:
+ cpuhp_remove_multi_state(pmu->cpuhp_state);
+cpuhp_state_err:
+ ida_free(&k3_ddr_ida, pmu->id);
+
+ return ret;
+}
+
+static void k3_ddr_perf_remove(struct platform_device *pdev)
+{
+ struct k3_ddr_pmu *pmu = platform_get_drvdata(pdev);
+
+ perf_pmu_unregister(&pmu->pmu);
+
+ cpuhp_state_remove_instance_nocalls(pmu->cpuhp_state, &pmu->node);
+ cpuhp_remove_multi_state(pmu->cpuhp_state);
+
+ hrtimer_cancel(&pmu->hrtimer);
+
+ ida_free(&k3_ddr_ida, pmu->id);
+}
+
+static const struct of_device_id ti_k3_ddr_pmu_of_match[] = {
+ { .compatible = "ti,k3-ddr-pmu" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ti_k3_ddr_pmu_of_match);
+
+static struct platform_driver ti_k3_ddr_pmu_driver = {
+ .driver = {
+ .name = "ti-k3-ddr-pmu",
+ .of_match_table = ti_k3_ddr_pmu_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = k3_ddr_perf_probe,
+ .remove = k3_ddr_perf_remove,
+};
+module_platform_driver(ti_k3_ddr_pmu_driver);
+
+MODULE_DESCRIPTION("PMU driver for TI K3 DDR Controller");
+MODULE_AUTHOR("Texas Instruments Inc");
+MODULE_LICENSE("GPL");
--
2.34.1