Add Samsung PPMU driver support in the Linux perf subsystem.
PPMU24 driver configures the PPMU24 hardware which is found
in the Samsung SoCs like Tesla FSD.
Signed-off-by: Ravi Patel <ravi.patel@samsung.com>
Signed-off-by: Vivek Yadav <vivek.2311@samsung.com>
---
drivers/perf/Kconfig | 2 +
drivers/perf/Makefile | 1 +
drivers/perf/samsung/Kconfig | 13 +
drivers/perf/samsung/Makefile | 2 +
drivers/perf/samsung/ppmu.c | 494 +++++++++++++++++++++++++++
drivers/perf/samsung/ppmu_platform.c | 338 ++++++++++++++++++
drivers/perf/samsung/samsung_ppmu.h | 128 +++++++
7 files changed, 978 insertions(+)
create mode 100644 drivers/perf/samsung/Kconfig
create mode 100644 drivers/perf/samsung/Makefile
create mode 100644 drivers/perf/samsung/ppmu.c
create mode 100644 drivers/perf/samsung/ppmu_platform.c
create mode 100644 drivers/perf/samsung/samsung_ppmu.h
diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
index 4e268de351c4..0852bcae6cd2 100644
--- a/drivers/perf/Kconfig
+++ b/drivers/perf/Kconfig
@@ -271,6 +271,8 @@ source "drivers/perf/arm_cspmu/Kconfig"
source "drivers/perf/amlogic/Kconfig"
+source "drivers/perf/samsung/Kconfig"
+
config CXL_PMU
tristate "CXL Performance Monitoring Unit"
depends on CXL_BUS
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index de71d2574857..5f764d9fc601 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
+obj-$(CONFIG_SAMSUNG_PPMU24) += samsung/
diff --git a/drivers/perf/samsung/Kconfig b/drivers/perf/samsung/Kconfig
new file mode 100644
index 000000000000..2088e2b7299d
--- /dev/null
+++ b/drivers/perf/samsung/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Programmable Performance Monitoring Unit (PPMU) driver
+#
+
+config SAMSUNG_PPMU24
+ tristate "Samsung PPMU24 drivers"
+ depends on ARM64 && ARCH_EXYNOS
+ help
+ The Platform Performance Measuring Unit (PPMU) is an AMBA-compliant
+ performance measurement tool designed to provide observability into
+ system-level operations. It provides performance statistics such as
+ as bandwidth, read and write request, transactions count for AXI masters.
diff --git a/drivers/perf/samsung/Makefile b/drivers/perf/samsung/Makefile
new file mode 100644
index 000000000000..c9ed1e1a986e
--- /dev/null
+++ b/drivers/perf/samsung/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SAMSUNG_PPMU24) += ppmu_platform.o ppmu.o
diff --git a/drivers/perf/samsung/ppmu.c b/drivers/perf/samsung/ppmu.c
new file mode 100644
index 000000000000..cacb9cdec79f
--- /dev/null
+++ b/drivers/perf/samsung/ppmu.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung Platform Performance Measuring Unit (PPMU) driver
+ *
+ * Copyright (c) 2024-25 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Vivek Yadav <vivek.2311@samsung.com>
+ * Ravi Patel <ravi.patel@samsung.com>
+ */
+
+#include <linux/idr.h>
+#include <linux/of.h>
+#include <linux/perf_event.h>
+#include <linux/platform_device.h>
+#include "samsung_ppmu.h"
+
+#define PPMU_CLEAR_FLAG (GENMASK(3, 0) | BIT(31))
+
+#define PPMU_PMCNT3_HIGH_VAL (3)
+#define PPMU_PMCNT2_HIGH_VAL (2)
+#define PPMU_RESET_CCNT BIT(2)
+#define PPMU_RESET_PMCNT BIT(1)
+
+#define PPMU_PMNC_OP_MODE_MASK (GENMASK(21, 20))
+#define PPMU_PMNC_OP_MODE_OFF (20)
+#define PPMU_MANUAL_MODE_VAL (0x0)
+#define PPMU_PMNC_GLB_CNT_EN_VAL (BIT(0))
+#define PPMU_PMNC_RESET_PMCNT_VAL (BIT(1))
+#define PPMU_PMNC_RESET_CCNT_VAL (BIT(2))
+
+#define PPMU_V24_IDENTIFIER (0x45)
+
+#define PPMU_CCNT_IDX (4)
+#define PPMU_CCNT_POS_OFF (31)
+#define PPMU_VERSION_CHECK (GENMASK(19, 12))
+
+#define PPMU_SM_ENABLE_ALL_CNT (0xf)
+#define PPMU_ENABLE_CCNT BIT(31)
+#define PPMU_FILTER_MASK (0x7)
+
+/* ID of event type */
+enum {
+ PPMU_EVENT_READ_CHANNEL_ACTIVE = (0x00),
+ PPMU_EVENT_WRITE_CHANNEL_ACTIVE = (0x01),
+ PPMU_EVENT_READ_REQUEST = (0x02),
+ PPMU_EVENT_WRITE_REQUEST = (0x03),
+ PPMU_EVENT_READ_DATA = (0x04),
+ PPMU_EVENT_WRITE_DATA = (0x05),
+ PPMU_EVENT_WRITE_RESPONSE = (0x06),
+ PPMU_EVENT_LAST_READ_DATA = (0x07),
+ PPMU_EVENT_LAST_WRITE_DATA = (0x08),
+ PPMU_EVENT_READ_REQUEST_BOLCKING = (0x10),
+ PPMU_EVENT_WRITE_REQUEST_BOLCKING = (0x11),
+ PPMU_EVENT_READ_DATA_BLOCKING = (0x12),
+ PPMU_EVENT_WRITE_DATA_BLOCKING = (0x13),
+ PPMU_EVENT_WRITE_RESPONSE_BLOCKING = (0x14),
+ PPMU_EVENT_READ_BURST_LENGTH = (0x2a),
+ PPMU_EVENT_WRITE_BURST_LENGTH = (0x2b),
+ PPMU_EVENT_CCNT = (0xfe),
+ PPMU_EVENT_MAX = (0xff),
+};
+
+/* Register offsets */
+enum ppmu_reg {
+ PPMU_VERSION = (0x0000),
+ PPMU_PMNC = (0x0004),
+ PPMU_CNTENS = (0x0008),
+ PPMU_CNTENC = (0x000c),
+ PPMU_INTENS = (0x0010),
+ PPMU_INTENC = (0x0014),
+ PPMU_FLAG = (0x0018),
+ PPMU_CIG_CFG0 = (0x001c),
+ PPMU_CIG_CFG1 = (0x0020),
+ PPMU_CIG_CFG2 = (0x0024),
+ PPMU_CIG_RESULT = (0x0028),
+ PPMU_CNT_RESET = (0x002c),
+ PPMU_CNT_AUTO = (0x0030),
+ PPMU_PMCNT0 = (0x0034),
+ PPMU_PMCNT1 = (0x0038),
+ PPMU_PMCNT2 = (0x003c),
+ PPMU_PMCNT3_LOW = (0x0040),
+ PPMU_PMCNT3_HIGH = (0x0044),
+ PPMU_CCNT = (0x0048),
+ PPMU_PMCNT2_HIGH = (0x0054),
+ PPMU_CONFIG_INFO = (0X005c),
+ PPMU_INTERRUPT_TEST = (0x0060),
+ PPMU_EVENT_EV0_TYPE = (0x0200),
+ PPMU_EVENT_EV1_TYPE = (0x0204),
+ PPMU_EVENT_EV2_TYPE = (0x0208),
+ PPMU_EVENT_EV3_TYPE = (0x020c),
+ PPMU_EVENT_SM_ID_MASK = (0x0220),
+ PPMU_EVENT_SM_ID_VALUE = (0x0224),
+ PPMU_EVENT_SM_ID_A = (0x0228),
+ PPMU_EVENT_SM_OTHERS_V = (0x022c),
+ PPMU_EVENT_SM_OTHERS_A = (0x0230),
+ PPMU_EVENT_SAMPLE_RD_ID_MASK = (0x0234),
+ PPMU_EVENT_SAMPLE_RD_ID_VALUE = (0x0238),
+ PPMU_EVENT_SAMPLE_WR_ID_MASK = (0x023c),
+ PPMU_EVENT_SAMPLE_WD_ID_VALUE = (0x0240),
+ PPMU_EVENT_AXI_CH_TYPE = (0x0244),
+ PPMU_EVENT_MO_INFO = (0x0250),
+ PPMU_EVENT_MO_INFO_SM_ID = (0x0254),
+ PPMU_EVENT_MO_INFO_SAMPLE = (0x0258),
+ PPMU_EVENT_IMPRECISE_ERR = (0x0260),
+};
+
+static const u32 ppmu_pmcnt_offset[PPMU_MAX_COUNTERS][2] = {
+ { PPMU_PMCNT0, PPMU_EVENT_EV0_TYPE },
+ { PPMU_PMCNT1, PPMU_EVENT_EV1_TYPE },
+ { PPMU_PMCNT2, PPMU_EVENT_EV2_TYPE },
+ { PPMU_PMCNT3_LOW, PPMU_EVENT_EV3_TYPE },
+ { PPMU_CCNT, 0 },
+};
+
+static DEFINE_IDR(my_idr);
+
+static void samsung_ppmu_write_evtype(struct samsung_ppmu *s_ppmu,
+ int idx, u32 type)
+{
+ /* Configure event */
+ if (idx != PPMU_CCNT_IDX)
+ writel(type, s_ppmu->base + ppmu_pmcnt_offset[idx][1]);
+}
+
+static int samsung_ppmu_get_event_idx(struct perf_event *event)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(event->pmu);
+ unsigned long *used_mask = samsung_ppmu->pmu_events.used_mask;
+ u32 num_counters = samsung_ppmu->num_counters;
+ DECLARE_BITMAP(buf, PPMU_MAX_COUNTERS);
+ int idx;
+
+ if (event->attr.config == PPMU_EVENT_CCNT)
+ *buf = *used_mask | (~BIT(PPMU_CCNT_IDX));
+ else
+ *buf = *used_mask;
+
+ idx = find_first_zero_bit(buf, num_counters);
+ if (idx == num_counters)
+ return -EAGAIN;
+
+ set_bit(idx, used_mask);
+
+ return idx;
+}
+
+static u64 samsung_ppmu_get_read_counter(struct samsung_ppmu *s_ppmu,
+ struct hw_perf_event *hwc)
+{
+ u64 counter_val, prev_overflow_val;
+ u64 high_val = 0;
+
+ counter_val = (u64)readl(s_ppmu->base + ppmu_pmcnt_offset[hwc->idx][0]);
+ prev_overflow_val = (u64)s_ppmu->counter_overflow[hwc->idx] << 32;
+
+ /*
+ * PMCNT2 and PMCNT3 are 40-bit counters
+ * its value are divided in two 32-bit registers
+ */
+ if (hwc->idx == PPMU_PMCNT3_HIGH_VAL || hwc->idx == PPMU_PMCNT2_HIGH_VAL) {
+ prev_overflow_val = prev_overflow_val << 8;
+
+ if (hwc->idx == PPMU_PMCNT3_HIGH_VAL)
+ high_val = (u64)(readl(s_ppmu->base + PPMU_PMCNT3_HIGH) & 0xFF);
+ else
+ high_val = (u64)(readl(s_ppmu->base + PPMU_PMCNT2_HIGH) & 0xFF);
+ }
+
+ counter_val = counter_val + (high_val << 32);
+
+ /* Clear overflow status */
+ s_ppmu->counter_overflow[hwc->idx] = 0;
+
+ /* Read previous counter */
+ s_ppmu->prev_counter[hwc->idx] = counter_val;
+
+ return (counter_val | prev_overflow_val);
+}
+
+static void samsung_ppmu_get_enable_counter(struct samsung_ppmu *s_ppmu,
+ struct hw_perf_event *hwc)
+{
+ u32 val;
+ int idx = hwc->idx;
+
+ if (idx == PPMU_CCNT_IDX)
+ idx = PPMU_CCNT_POS_OFF;
+
+ /* Enabling counters */
+ writel(BIT(idx), s_ppmu->base + PPMU_CNTENS);
+
+ /* Enabling interrupt */
+ writel(BIT(idx), s_ppmu->base + PPMU_INTENS);
+
+ /* Manual mode enabled */
+ val = readl(s_ppmu->base + PPMU_PMNC);
+ val &= (~PPMU_PMNC_OP_MODE_MASK);
+ val |= (PPMU_MANUAL_MODE_VAL << PPMU_PMNC_OP_MODE_OFF);
+ writel(val, s_ppmu->base + PPMU_PMNC);
+}
+
+static void samsung_ppmu_get_disable_counter(struct samsung_ppmu *s_ppmu,
+ struct hw_perf_event *hwc)
+{
+ int idx = hwc->idx;
+
+ if (idx == PPMU_CCNT_IDX)
+ idx = PPMU_CCNT_POS_OFF;
+
+ /* Disabling interrupt */
+ writel(BIT(idx), s_ppmu->base + PPMU_INTENC);
+
+ /* Disabling counter */
+ writel(BIT(idx), s_ppmu->base + PPMU_CNTENC);
+
+ /* Reset counter */
+ writel(BIT(idx), s_ppmu->base + PPMU_CNT_RESET);
+}
+
+static void samsung_ppmu_get_start_counters(struct samsung_ppmu *s_ppmu)
+{
+ u32 val;
+
+ /* Clearing all overflow status */
+ writel(PPMU_CLEAR_FLAG, s_ppmu->base + PPMU_FLAG);
+
+ /* Resetting all counters */
+ val = readl(s_ppmu->base + PPMU_PMNC);
+ val |= (PPMU_PMNC_RESET_PMCNT_VAL | PPMU_PMNC_RESET_CCNT_VAL);
+ writel(val, s_ppmu->base + PPMU_PMNC);
+
+ /* Start counters */
+ val = readl(s_ppmu->base + PPMU_PMNC);
+ val |= PPMU_PMNC_GLB_CNT_EN_VAL;
+ writel(val, s_ppmu->base + PPMU_PMNC);
+
+ s_ppmu->status = PPMU_START;
+}
+
+static void samsung_ppmu_get_stop_counters(struct samsung_ppmu *s_ppmu)
+{
+ u32 val;
+
+ /* Stop counters */
+ val = readl(s_ppmu->base + PPMU_PMNC);
+ val &= (~PPMU_PMNC_GLB_CNT_EN_VAL);
+ writel(val, s_ppmu->base + PPMU_PMNC);
+
+ s_ppmu->status = PPMU_STOP;
+}
+
+static u32 samsung_ppmu_get_int_status(struct samsung_ppmu *s_ppmu)
+{
+ u32 regvalue, i;
+
+ /* Read status register */
+ regvalue = readl(s_ppmu->base + PPMU_FLAG);
+
+ for (i = 0; i < PPMU_MAX_COUNTERS; i++) {
+ if (regvalue & BIT(i))
+ s_ppmu->counter_overflow[i] += 1;
+ }
+
+ if (regvalue & BIT(PPMU_CCNT_POS_OFF)) {
+ s_ppmu->counter_overflow[PPMU_CCNT_IDX] += 1;
+ regvalue &= (~BIT(PPMU_CCNT_POS_OFF));
+ regvalue |= BIT(PPMU_CCNT_IDX);
+ }
+
+ return regvalue;
+}
+
+static void samsung_ppmu_clear_int_status(struct samsung_ppmu *s_ppmu, int idx)
+{
+ if (idx == PPMU_CCNT_IDX)
+ idx = PPMU_CCNT_POS_OFF;
+
+ /* Clear the interrupt status */
+ writel(BIT(idx), s_ppmu->base + PPMU_FLAG);
+}
+
+static struct attribute *samsung_ppmu_format_attr[] = {
+ SAMSUNG_PPMU_FORMAT_ATTR(event, "config:0-7"),
+ NULL
+};
+
+static const struct attribute_group samsung_ppmu_format_group = {
+ .name = "format",
+ .attrs = samsung_ppmu_format_attr,
+};
+
+static struct attribute *samsung_ppmu_events_attr[] = {
+ SAMSUNG_PPMU_EVENT_ATTR(rd_channel_active, PPMU_EVENT_READ_CHANNEL_ACTIVE),
+ SAMSUNG_PPMU_EVENT_ATTR(wr_channel_active, PPMU_EVENT_WRITE_CHANNEL_ACTIVE),
+ SAMSUNG_PPMU_EVENT_ATTR(read_request, PPMU_EVENT_READ_REQUEST),
+ SAMSUNG_PPMU_EVENT_ATTR(write_request, PPMU_EVENT_WRITE_REQUEST),
+ SAMSUNG_PPMU_EVENT_ATTR(read_data, PPMU_EVENT_READ_DATA),
+ SAMSUNG_PPMU_EVENT_ATTR(write_data, PPMU_EVENT_WRITE_DATA),
+ SAMSUNG_PPMU_EVENT_ATTR(wr_response, PPMU_EVENT_WRITE_RESPONSE),
+ SAMSUNG_PPMU_EVENT_ATTR(last_rd_data, PPMU_EVENT_LAST_READ_DATA),
+ SAMSUNG_PPMU_EVENT_ATTR(last_wr_data, PPMU_EVENT_LAST_WRITE_DATA),
+ SAMSUNG_PPMU_EVENT_ATTR(rd_request_blk, PPMU_EVENT_READ_REQUEST_BOLCKING),
+ SAMSUNG_PPMU_EVENT_ATTR(wr_request_blk, PPMU_EVENT_WRITE_REQUEST_BOLCKING),
+ SAMSUNG_PPMU_EVENT_ATTR(rd_data_blk, PPMU_EVENT_READ_DATA_BLOCKING),
+ SAMSUNG_PPMU_EVENT_ATTR(wr_data_blk, PPMU_EVENT_WRITE_DATA_BLOCKING),
+ SAMSUNG_PPMU_EVENT_ATTR(wr_response_blk, PPMU_EVENT_WRITE_RESPONSE_BLOCKING),
+ SAMSUNG_PPMU_EVENT_ATTR(rd_burst_length, PPMU_EVENT_READ_BURST_LENGTH),
+ SAMSUNG_PPMU_EVENT_ATTR(wr_burst_length, PPMU_EVENT_WRITE_BURST_LENGTH),
+ SAMSUNG_PPMU_EVENT_ATTR(ccnt, PPMU_EVENT_CCNT),
+ NULL
+};
+
+static const struct attribute_group samsung_ppmu_events_group = {
+ .name = "events",
+ .attrs = samsung_ppmu_events_attr,
+};
+
+struct device_attribute dev_attr_cpumask =
+ __ATTR(cpumask, 0444, samsung_ppmu_cpumask_sysfs_show, NULL);
+
+static struct attribute *samsung_ppmu_cpumask_attrs[] = {
+ &dev_attr_cpumask.attr,
+ NULL,
+};
+
+static const struct attribute_group samsung_ppmu_cpumask_attr_group = {
+ .attrs = samsung_ppmu_cpumask_attrs,
+};
+
+static struct device_attribute samsung_ppmu_identifier_attr =
+ __ATTR(identifier, 0444, samsung_ppmu_identifier_attr_show, NULL);
+
+static struct attribute *samsung_ppmu_identifier_attrs[] = {
+ &samsung_ppmu_identifier_attr.attr,
+ NULL
+};
+
+static const struct attribute_group samsung_ppmu_identifier_group = {
+ .attrs = samsung_ppmu_identifier_attrs,
+};
+
+static const struct attribute_group *samsung_ppmu_v24_attr_groups[] = {
+ &samsung_ppmu_format_group,
+ &samsung_ppmu_events_group,
+ &samsung_ppmu_cpumask_attr_group,
+ &samsung_ppmu_identifier_group,
+ NULL
+};
+
+static struct samsung_ppmu_drv_data samsung_ppmu_v24_data = {
+ .ppmu_attr_group = samsung_ppmu_v24_attr_groups
+};
+
+static const struct samsung_ppmu_ops samsung_ppmu_plat_ops = {
+ .write_evtype = samsung_ppmu_write_evtype,
+ .get_event_idx = samsung_ppmu_get_event_idx,
+ .read_counter = samsung_ppmu_get_read_counter,
+ .enable_counter = samsung_ppmu_get_enable_counter,
+ .disable_counter = samsung_ppmu_get_disable_counter,
+ .start_counters = samsung_ppmu_get_start_counters,
+ .stop_counters = samsung_ppmu_get_stop_counters,
+ .get_int_status = samsung_ppmu_get_int_status,
+ .clear_int_status = samsung_ppmu_clear_int_status,
+};
+
+static int ppmu_clock_init(struct samsung_ppmu *s_ppmu)
+{
+ int ret = 0;
+ struct device *dev = s_ppmu->dev;
+
+ s_ppmu->clks[PPMU_ACLK].id = "aclk";
+ s_ppmu->clks[PPMU_PCLK].id = "pclk";
+
+ ret = devm_clk_bulk_get(dev, PPMU_CLK_COUNT, s_ppmu->clks);
+ if (ret) {
+ dev_err(dev, "Failed to get clocks. Err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(PPMU_CLK_COUNT, s_ppmu->clks);
+ if (ret)
+ dev_err(dev, "Clock enable failed. Err %d\n", ret);
+
+ return ret;
+}
+
+static int samsung_ppmu_probe(struct platform_device *pdev)
+{
+ struct samsung_ppmu *s_ppmu;
+ struct device *dev = &pdev->dev;
+ u32 version;
+ char *name;
+ int ret;
+
+ s_ppmu = devm_kzalloc(dev, sizeof(*s_ppmu), GFP_KERNEL);
+ if (!s_ppmu)
+ return -ENOMEM;
+
+ s_ppmu->dev = &pdev->dev;
+
+ s_ppmu->id = idr_alloc(&my_idr, dev, 0, 2, GFP_KERNEL);
+ if (s_ppmu->id < 0) {
+ dev_err(dev, "Failed to allocate ID dynamically\n");
+ return s_ppmu->id;
+ }
+
+ /* Register base address */
+ s_ppmu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(s_ppmu->base)) {
+ dev_err(dev, "IO remap failed\n");
+ return PTR_ERR(s_ppmu->base);
+ }
+
+ /* Register IRQ */
+ ret = samsung_ppmu_init_irq(s_ppmu, pdev);
+ if (ret)
+ return ret;
+
+ s_ppmu->check_event = PPMU_EVENT_MAX;
+ s_ppmu->num_counters = PPMU_MAX_COUNTERS;
+ s_ppmu->on_cpu = 0;
+ s_ppmu->identifier = PPMU_V24_IDENTIFIER;
+
+ s_ppmu->ppmu_data = device_get_match_data(&pdev->dev);
+ if (!s_ppmu->ppmu_data) {
+ dev_err(&pdev->dev, "No matching device data found\n");
+ return -ENODEV;
+ }
+
+ /* Register PPMU driver ops */
+ s_ppmu->pmu_events.attr_groups = s_ppmu->ppmu_data->ppmu_attr_group;
+ s_ppmu->ops = &samsung_ppmu_plat_ops;
+
+ /* Set private data to platform_device structure */
+ platform_set_drvdata(pdev, s_ppmu);
+
+ /* Initialize the PPMU */
+ samsung_ppmu_init(s_ppmu, THIS_MODULE);
+
+ ret = ppmu_clock_init(s_ppmu);
+ if (ret)
+ return ret;
+
+ version = readl(s_ppmu->base + PPMU_VERSION);
+ version &= PPMU_VERSION_CHECK;
+ version >>= 12;
+ s_ppmu->samsung_ppmu_version = version;
+
+ name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ppmu_v_%x_%d", version, s_ppmu->id);
+ if (!name)
+ return -ENOMEM;
+
+ ret = perf_pmu_register(&s_ppmu->pmu, name, -1);
+ if (ret) {
+ clk_bulk_disable_unprepare(PPMU_CLK_COUNT, s_ppmu->clks);
+ dev_err(dev, "Failed to register PPMU in perf. Err %d\n", ret);
+ }
+
+ return ret;
+}
+
+static void samsung_ppmu_remove(struct platform_device *pdev)
+{
+ struct samsung_ppmu *s_ppmu = platform_get_drvdata(pdev);
+
+ clk_bulk_disable_unprepare(PPMU_CLK_COUNT, s_ppmu->clks);
+
+ perf_pmu_unregister(&s_ppmu->pmu);
+
+ idr_remove(&my_idr, s_ppmu->id);
+}
+
+static const struct of_device_id ppmu_of_match[] = {
+ { .compatible = "samsung,ppmu-v2", .data = &samsung_ppmu_v24_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ppmu_of_match);
+
+static struct platform_driver samsung_ppmu_driver = {
+ .probe = samsung_ppmu_probe,
+ .remove = samsung_ppmu_remove,
+ .driver = {
+ .name = "samsung-ppmu",
+ .of_match_table = ppmu_of_match,
+ },
+};
+
+module_platform_driver(samsung_ppmu_driver);
+
+MODULE_ALIAS("perf:samsung-ppmu");
+MODULE_DESCRIPTION("Samsung Platform Performance Measuring Unit (PPMU) driver");
+MODULE_AUTHOR("Vivek Yadav <vivek.2311@samsung.com>");
+MODULE_AUTHOR("Ravi Patel <ravi.patel@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/perf/samsung/ppmu_platform.c b/drivers/perf/samsung/ppmu_platform.c
new file mode 100644
index 000000000000..ee11311d5a61
--- /dev/null
+++ b/drivers/perf/samsung/ppmu_platform.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung Platform Performance Measuring Unit (PPMU) core file
+ *
+ * Copyright (c) 2024-25 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Vivek Yadav <vivek.2311@samsung.com>
+ * Ravi Patel <ravi.patel@samsung.com>
+ */
+
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/perf_event.h>
+#include "samsung_ppmu.h"
+
+/*
+ * PMU format attributes
+ */
+ssize_t samsung_ppmu_format_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *eattr;
+
+ eattr = container_of(attr, struct dev_ext_attribute, attr);
+
+ return sysfs_emit(buf, "%s\n", (char *)eattr->var);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_format_sysfs_show);
+
+/*
+ * PMU event attributes
+ */
+ssize_t samsung_ppmu_event_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ struct dev_ext_attribute *eattr;
+
+ eattr = container_of(attr, struct dev_ext_attribute, attr);
+
+ return sysfs_emit(page, "config=0x%lx\n", (unsigned long)eattr->var);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_event_sysfs_show);
+
+/*
+ * sysfs cpumask attributes. For PPMU, we only have a single CPU to show
+ */
+ssize_t samsung_ppmu_cpumask_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(dev_get_drvdata(dev));
+
+ return sysfs_emit(buf, "%d\n", samsung_ppmu->on_cpu);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_cpumask_sysfs_show);
+
+ssize_t samsung_ppmu_identifier_attr_show(struct device *dev,
+ struct device_attribute *attr,
+ char *page)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(dev_get_drvdata(dev));
+
+ return sysfs_emit(page, "0x%08x\n", samsung_ppmu->identifier);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_identifier_attr_show);
+
+static irqreturn_t samsung_ppmu_isr(int irq, void *data)
+{
+ struct samsung_ppmu *samsung_ppmu = data;
+ unsigned long overflown;
+ int idx;
+
+ overflown = samsung_ppmu->ops->get_int_status(samsung_ppmu);
+ if (!overflown)
+ return IRQ_NONE;
+
+ /*
+ * Find the counter index which overflowed if the bit was set
+ * and handle it.
+ */
+ for_each_set_bit(idx, &overflown, samsung_ppmu->num_counters)
+ samsung_ppmu->ops->clear_int_status(samsung_ppmu, idx);
+
+ return IRQ_HANDLED;
+}
+
+int samsung_ppmu_init_irq(struct samsung_ppmu *samsung_ppmu,
+ struct platform_device *pdev)
+{
+ int irq0, irq1, ret, irq_count;
+
+ irq0 = platform_get_irq(pdev, 0);
+ if (irq0 < 0) {
+ dev_err(&pdev->dev, "Failed to get IRQ 0\n");
+ return irq0;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq0, samsung_ppmu_isr,
+ IRQF_NOBALANCING | IRQF_NO_THREAD | IRQF_SHARED,
+ dev_name(&pdev->dev), samsung_ppmu);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Fail to request IRQ: %d ret: %d.\n", irq0, ret);
+ return ret;
+ }
+
+ samsung_ppmu->irq0 = irq0;
+
+ irq_count = of_property_count_elems_of_size(pdev->dev.of_node, "interrupts", sizeof(u32));
+ if (irq_count > 1) {
+ irq1 = platform_get_irq(pdev, 1);
+ if (irq1 < 0) {
+ dev_err(&pdev->dev, "Failed to get IRQ 0\n");
+ return irq1;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq1, samsung_ppmu_isr,
+ IRQF_NOBALANCING | IRQF_NO_THREAD | IRQF_SHARED,
+ dev_name(&pdev->dev), samsung_ppmu);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Fail to request IRQ: %d ret: %d.\n", irq1, ret);
+ return ret;
+ }
+ samsung_ppmu->irq1 = irq1;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_init_irq);
+
+int samsung_ppmu_event_init(struct perf_event *event)
+{
+ struct hw_perf_event *hwc = &event->hw;
+ struct samsung_ppmu *samsung_ppmu;
+
+ if (event->attr.type != event->pmu->type)
+ return -ENOENT;
+
+ /*
+ * We do not support sampling as the counters are all
+ * shared by all CPU cores in a CPU die. Also we
+ * do not support attach to a task(per-process mode)
+ */
+ if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK)
+ return -EOPNOTSUPP;
+
+ /* PPMU counters not specific to any CPU, so cannot support per-task */
+ if (event->cpu < 0)
+ return -EINVAL;
+
+ /* Check if events in group does not exceed the available counters */
+ samsung_ppmu = to_samsung_ppmu(event->pmu);
+ if (event->attr.config > samsung_ppmu->check_event)
+ return -EINVAL;
+
+ /*
+ * We don't assign an index until we actually place the event onto
+ * hardware. Use -1 to signify that we haven't decided where to put it
+ * yet.
+ */
+ hwc->idx = -1;
+ hwc->config_base = event->attr.config;
+
+ /* Enforce to use the same CPU for all events in this PMU */
+ event->cpu = samsung_ppmu->on_cpu;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_event_init);
+
+/*
+ * Set the counter to count the event that we're interested in,
+ * and enable interrupt and counter.
+ */
+static void samsung_ppmu_enable_event(struct perf_event *event)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+
+ samsung_ppmu->ops->write_evtype(samsung_ppmu, hwc->idx,
+ SAMSUNG_PPMU_GET_EVENTID(event));
+
+ samsung_ppmu->ops->enable_counter(samsung_ppmu, hwc);
+}
+
+/*
+ * Disable counter and interrupt.
+ */
+static void samsung_ppmu_disable_event(struct perf_event *event)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+
+ samsung_ppmu->ops->disable_counter(samsung_ppmu, hwc);
+}
+
+void samsung_ppmu_event_update(struct perf_event *event)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ u64 delta, prev_raw_count, new_raw_count;
+
+ /* read previous counter value */
+ prev_raw_count = samsung_ppmu->prev_counter[hwc->idx];
+
+ /* Read the count from the counter register */
+ new_raw_count = samsung_ppmu->ops->read_counter(samsung_ppmu, hwc);
+
+ /* compute the delta */
+ delta = new_raw_count - prev_raw_count;
+
+ local64_add(delta, &event->count);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_event_update);
+
+void samsung_ppmu_start(struct perf_event *event, int flags)
+{
+ struct hw_perf_event *hwc = &event->hw;
+
+ if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED)))
+ return;
+
+ WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
+ hwc->state = 0;
+
+ samsung_ppmu_enable_event(event);
+ perf_event_update_userpage(event);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_start);
+
+void samsung_ppmu_stop(struct perf_event *event, int flags)
+{
+ struct hw_perf_event *hwc = &event->hw;
+
+ samsung_ppmu_disable_event(event);
+ WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED);
+ hwc->state |= PERF_HES_STOPPED;
+
+ if (hwc->state & PERF_HES_UPTODATE)
+ return;
+
+ /* Read hardware counter and update the perf counter statistics */
+ samsung_ppmu_event_update(event);
+ hwc->state |= PERF_HES_UPTODATE;
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_stop);
+
+int samsung_ppmu_add(struct perf_event *event, int flags)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ int idx;
+
+ hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
+
+ /* Get an available counter index for counting */
+ idx = samsung_ppmu->ops->get_event_idx(event);
+ if (idx < 0)
+ return idx;
+
+ event->hw.idx = idx;
+ samsung_ppmu->pmu_events.hw_events[idx] = event;
+
+ if (flags & PERF_EF_START)
+ samsung_ppmu_start(event, PERF_EF_RELOAD);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_add);
+
+void samsung_ppmu_del(struct perf_event *event, int flags)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+
+ samsung_ppmu_stop(event, PERF_EF_UPDATE);
+
+ samsung_ppmu->prev_counter[hwc->idx] = 0;
+
+ /* Clear the event bit */
+ clear_bit(hwc->idx, samsung_ppmu->pmu_events.used_mask);
+ perf_event_update_userpage(event);
+ samsung_ppmu->pmu_events.hw_events[hwc->idx] = NULL;
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_del);
+
+void samsung_ppmu_read(struct perf_event *event)
+{
+ /* Read hardware counter and update the perf counter statistics */
+ samsung_ppmu_event_update(event);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_read);
+
+void samsung_ppmu_enable(struct pmu *pmu)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(pmu);
+ bool enabled = !bitmap_empty(samsung_ppmu->pmu_events.used_mask,
+ samsung_ppmu->num_counters);
+
+ if (!enabled)
+ return;
+
+ samsung_ppmu->ops->start_counters(samsung_ppmu);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_enable);
+
+void samsung_ppmu_disable(struct pmu *pmu)
+{
+ struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(pmu);
+
+ samsung_ppmu->ops->stop_counters(samsung_ppmu);
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_disable);
+
+void samsung_ppmu_init(struct samsung_ppmu *s_ppmu, struct module *module)
+{
+ struct pmu *pmu = &s_ppmu->pmu;
+
+ pmu->module = module;
+ pmu->task_ctx_nr = perf_invalid_context;
+ pmu->event_init = samsung_ppmu_event_init;
+ pmu->pmu_enable = samsung_ppmu_enable;
+ pmu->pmu_disable = samsung_ppmu_disable;
+ pmu->add = samsung_ppmu_add;
+ pmu->del = samsung_ppmu_del;
+ pmu->start = samsung_ppmu_start;
+ pmu->stop = samsung_ppmu_stop;
+ pmu->read = samsung_ppmu_read;
+ pmu->attr_groups = s_ppmu->pmu_events.attr_groups;
+ pmu->capabilities = PERF_PMU_CAP_NO_EXCLUDE;
+}
+EXPORT_SYMBOL_GPL(samsung_ppmu_init);
+
+MODULE_ALIAS("perf:samsung-ppmu-core");
+MODULE_DESCRIPTION("Samsung Platform Performance Measuring Unit (PPMU) driver");
+MODULE_AUTHOR("Vivek Yadav <vivek.2311@samsung.com>");
+MODULE_AUTHOR("Ravi Patel <ravi.patel@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/perf/samsung/samsung_ppmu.h b/drivers/perf/samsung/samsung_ppmu.h
new file mode 100644
index 000000000000..2cad75cfa97b
--- /dev/null
+++ b/drivers/perf/samsung/samsung_ppmu.h
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung Platform Performance Measuring Unit (PPMU) headers
+ *
+ * Copyright (c) 2024-25 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Vivek Yadav <vivek.2311@samsung.com>
+ * Ravi Patel <ravi.patel@samsung.com>
+ */
+
+#ifndef __SAMSUNG_PPMU_H__
+#define __SAMSUNG_PPMU_H__
+
+#include <linux/clk.h>
+
+#define PPMU_MAX_COUNTERS (5)
+
+#define to_samsung_ppmu(p) (container_of(p, struct samsung_ppmu, pmu))
+
+#define SAMSUNG_PPMU_ATTR(_name, _func, _config) \
+ (&((struct dev_ext_attribute[]) { \
+ { __ATTR(_name, 0444, _func, NULL), (void *)_config } \
+ })[0].attr.attr)
+
+#define SAMSUNG_PPMU_FORMAT_ATTR(_name, _config) \
+ SAMSUNG_PPMU_ATTR(_name, samsung_ppmu_format_sysfs_show, (void *)_config)
+#define SAMSUNG_PPMU_EVENT_ATTR(_name, _config) \
+ SAMSUNG_PPMU_ATTR(_name, samsung_ppmu_event_sysfs_show, (unsigned long)_config)
+
+#define SAMSUNG_PPMU_GET_EVENTID(ev) ((ev)->hw.config_base & 0xff)
+
+enum ppmu_clock_type {
+ PPMU_ACLK,
+ PPMU_PCLK,
+ PPMU_CLK_COUNT,
+};
+
+enum ppmu_status {
+ PPMU_STOP,
+ PPMU_START,
+};
+
+struct samsung_ppmu;
+
+struct samsung_ppmu_ops {
+ void (*write_evtype)(struct samsung_ppmu *s_ppmu, int idx, u32 type);
+ int (*get_event_idx)(struct perf_event *event);
+ u64 (*read_counter)(struct samsung_ppmu *s_ppmu, struct hw_perf_event *event);
+ void (*enable_counter)(struct samsung_ppmu *s_ppmu, struct hw_perf_event *event);
+ void (*disable_counter)(struct samsung_ppmu *s_ppmu, struct hw_perf_event *event);
+ void (*start_counters)(struct samsung_ppmu *s_ppmu);
+ void (*stop_counters)(struct samsung_ppmu *s_ppmu);
+ u32 (*get_int_status)(struct samsung_ppmu *s_ppmu);
+ void (*clear_int_status)(struct samsung_ppmu *s_ppmu, int idx);
+};
+
+/* Describes the Samsung PPMU features information */
+struct samsung_ppmu_dev_info {
+ const char *name;
+ const struct attribute_group **attr_groups;
+ void *private;
+};
+
+struct samsung_ppmu_hwevents {
+ struct perf_event *hw_events[PPMU_MAX_COUNTERS];
+ DECLARE_BITMAP(used_mask, PPMU_MAX_COUNTERS);
+ const struct attribute_group **attr_groups;
+};
+
+struct samsung_ppmu_drv_data {
+ const struct attribute_group **ppmu_attr_group;
+};
+
+/* Generic pmu struct for different pmu types */
+struct samsung_ppmu {
+ struct pmu pmu;
+ const struct samsung_ppmu_ops *ops;
+ const struct samsung_ppmu_dev_info *dev_info;
+ struct samsung_ppmu_hwevents pmu_events;
+ const struct samsung_ppmu_drv_data *ppmu_data;
+ u32 samsung_ppmu_version;
+ u32 samsung_ppmu_master_id_val;
+ u8 status;
+ u8 id;
+ /* CPU used for counting */
+ int on_cpu;
+ int irq0;
+ int irq1;
+ struct device *dev;
+ struct hlist_node node;
+ void __iomem *base;
+ int num_counters;
+ u32 counter_overflow[PPMU_MAX_COUNTERS];
+ u64 prev_counter[PPMU_MAX_COUNTERS];
+ /* check event code range */
+ int check_event;
+ u32 identifier;
+ struct clk_bulk_data clks[PPMU_CLK_COUNT];
+};
+
+void samsung_ppmu_read(struct perf_event *event);
+int samsung_ppmu_add(struct perf_event *event, int flags);
+void samsung_ppmu_del(struct perf_event *event, int flags);
+void samsung_ppmu_start(struct perf_event *event, int flags);
+void samsung_ppmu_stop(struct perf_event *event, int flags);
+void samsung_ppmu_set_event_period(struct perf_event *event);
+void samsung_ppmu_event_update(struct perf_event *event);
+int samsung_ppmu_event_init(struct perf_event *event);
+void samsung_ppmu_enable(struct pmu *pmu);
+void samsung_ppmu_disable(struct pmu *pmu);
+ssize_t samsung_ppmu_event_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+ssize_t samsung_ppmu_format_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+ssize_t samsung_ppmu_cpumask_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+int samsung_ppmu_online_cpu(unsigned int cpu, struct hlist_node *node);
+int samsung_ppmu_offline_cpu(unsigned int cpu, struct hlist_node *node);
+
+ssize_t samsung_ppmu_identifier_attr_show(struct device *dev,
+ struct device_attribute *attr,
+ char *page);
+int samsung_ppmu_init_irq(struct samsung_ppmu *samsung_ppmu,
+ struct platform_device *pdev);
+
+void samsung_ppmu_init(struct samsung_ppmu *samsung_ppmu, struct module *module);
+
+#endif /* __SAMSUNG_PPMU_H__ */
--
2.49.0
On Tue, 8 Jul 2025 16:02:06 +0530 Vivek Yadav <vivek.2311@samsung.com> wrote: > Add Samsung PPMU driver support in the Linux perf subsystem. > > PPMU24 driver configures the PPMU24 hardware which is found > in the Samsung SoCs like Tesla FSD. > > Signed-off-by: Ravi Patel <ravi.patel@samsung.com> > Signed-off-by: Vivek Yadav <vivek.2311@samsung.com> Hi, My main feedback here is that you have a layer of abstraction that is not currently useful. So unless you plan to include another driver that makes use of the ops structures etc, just squash the whole thing into one much simpler c file without any of the ops stuff. Should simplify the code a lot and generally accelerate it getting reviewed and merged. If it becomes useful to have that abstraction in the future, that is the point when you should add it. Jonathan > diff --git a/drivers/perf/samsung/Makefile b/drivers/perf/samsung/Makefile > new file mode 100644 > index 000000000000..c9ed1e1a986e > --- /dev/null > +++ b/drivers/perf/samsung/Makefile > @@ -0,0 +1,2 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +obj-$(CONFIG_SAMSUNG_PPMU24) += ppmu_platform.o ppmu.o Not immediately obvious why this is multiple files. (no exports needed anyway as Krzysztof pointed out. > diff --git a/drivers/perf/samsung/ppmu.c b/drivers/perf/samsung/ppmu.c > new file mode 100644 > index 000000000000..cacb9cdec79f > --- /dev/null > +++ b/drivers/perf/samsung/ppmu.c > @@ -0,0 +1,494 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Samsung Platform Performance Measuring Unit (PPMU) driver > + * > + * Copyright (c) 2024-25 Samsung Electronics Co., Ltd. > + * > + * Authors: Vivek Yadav <vivek.2311@samsung.com> > + * Ravi Patel <ravi.patel@samsung.com> > + */ > + > +#include <linux/idr.h> > +#include <linux/of.h> > +#include <linux/perf_event.h> > +#include <linux/platform_device.h> > +#include "samsung_ppmu.h" > + > +#define PPMU_CLEAR_FLAG (GENMASK(3, 0) | BIT(31)) > + > +#define PPMU_PMCNT3_HIGH_VAL (3) > +#define PPMU_PMCNT2_HIGH_VAL (2) > +#define PPMU_RESET_CCNT BIT(2) > +#define PPMU_RESET_PMCNT BIT(1) > + > +#define PPMU_PMNC_OP_MODE_MASK (GENMASK(21, 20)) > +#define PPMU_PMNC_OP_MODE_OFF (20) > +#define PPMU_MANUAL_MODE_VAL (0x0) > +#define PPMU_PMNC_GLB_CNT_EN_VAL (BIT(0)) > +#define PPMU_PMNC_RESET_PMCNT_VAL (BIT(1)) > +#define PPMU_PMNC_RESET_CCNT_VAL (BIT(2)) > + > +#define PPMU_V24_IDENTIFIER (0x45) > + > +#define PPMU_CCNT_IDX (4) > +#define PPMU_CCNT_POS_OFF (31) > +#define PPMU_VERSION_CHECK (GENMASK(19, 12)) > + > +#define PPMU_SM_ENABLE_ALL_CNT (0xf) > +#define PPMU_ENABLE_CCNT BIT(31) > +#define PPMU_FILTER_MASK (0x7) > + > +/* ID of event type */ > +enum { > + PPMU_EVENT_READ_CHANNEL_ACTIVE = (0x00), > + PPMU_EVENT_WRITE_CHANNEL_ACTIVE = (0x01), > + PPMU_EVENT_READ_REQUEST = (0x02), > + PPMU_EVENT_WRITE_REQUEST = (0x03), > + PPMU_EVENT_READ_DATA = (0x04), > + PPMU_EVENT_WRITE_DATA = (0x05), > + PPMU_EVENT_WRITE_RESPONSE = (0x06), > + PPMU_EVENT_LAST_READ_DATA = (0x07), > + PPMU_EVENT_LAST_WRITE_DATA = (0x08), > + PPMU_EVENT_READ_REQUEST_BOLCKING = (0x10), > + PPMU_EVENT_WRITE_REQUEST_BOLCKING = (0x11), > + PPMU_EVENT_READ_DATA_BLOCKING = (0x12), > + PPMU_EVENT_WRITE_DATA_BLOCKING = (0x13), > + PPMU_EVENT_WRITE_RESPONSE_BLOCKING = (0x14), > + PPMU_EVENT_READ_BURST_LENGTH = (0x2a), > + PPMU_EVENT_WRITE_BURST_LENGTH = (0x2b), > + PPMU_EVENT_CCNT = (0xfe), > + PPMU_EVENT_MAX = (0xff), > +}; > + > +/* Register offsets */ > +enum ppmu_reg { > + PPMU_VERSION = (0x0000), > + PPMU_PMNC = (0x0004), > + PPMU_CNTENS = (0x0008), > + PPMU_CNTENC = (0x000c), > + PPMU_INTENS = (0x0010), > + PPMU_INTENC = (0x0014), > + PPMU_FLAG = (0x0018), > + PPMU_CIG_CFG0 = (0x001c), > + PPMU_CIG_CFG1 = (0x0020), > + PPMU_CIG_CFG2 = (0x0024), > + PPMU_CIG_RESULT = (0x0028), > + PPMU_CNT_RESET = (0x002c), > + PPMU_CNT_AUTO = (0x0030), > + PPMU_PMCNT0 = (0x0034), > + PPMU_PMCNT1 = (0x0038), > + PPMU_PMCNT2 = (0x003c), > + PPMU_PMCNT3_LOW = (0x0040), > + PPMU_PMCNT3_HIGH = (0x0044), > + PPMU_CCNT = (0x0048), > + PPMU_PMCNT2_HIGH = (0x0054), > + PPMU_CONFIG_INFO = (0X005c), > + PPMU_INTERRUPT_TEST = (0x0060), > + PPMU_EVENT_EV0_TYPE = (0x0200), > + PPMU_EVENT_EV1_TYPE = (0x0204), > + PPMU_EVENT_EV2_TYPE = (0x0208), > + PPMU_EVENT_EV3_TYPE = (0x020c), > + PPMU_EVENT_SM_ID_MASK = (0x0220), > + PPMU_EVENT_SM_ID_VALUE = (0x0224), > + PPMU_EVENT_SM_ID_A = (0x0228), > + PPMU_EVENT_SM_OTHERS_V = (0x022c), > + PPMU_EVENT_SM_OTHERS_A = (0x0230), > + PPMU_EVENT_SAMPLE_RD_ID_MASK = (0x0234), > + PPMU_EVENT_SAMPLE_RD_ID_VALUE = (0x0238), > + PPMU_EVENT_SAMPLE_WR_ID_MASK = (0x023c), > + PPMU_EVENT_SAMPLE_WD_ID_VALUE = (0x0240), > + PPMU_EVENT_AXI_CH_TYPE = (0x0244), > + PPMU_EVENT_MO_INFO = (0x0250), > + PPMU_EVENT_MO_INFO_SM_ID = (0x0254), > + PPMU_EVENT_MO_INFO_SAMPLE = (0x0258), > + PPMU_EVENT_IMPRECISE_ERR = (0x0260), Brackets () not adding anything useful. > +static void samsung_ppmu_get_stop_counters(struct samsung_ppmu *s_ppmu) > +{ > + u32 val; > + > + /* Stop counters */ > + val = readl(s_ppmu->base + PPMU_PMNC); > + val &= (~PPMU_PMNC_GLB_CNT_EN_VAL); > + writel(val, s_ppmu->base + PPMU_PMNC); > + > + s_ppmu->status = PPMU_STOP; If it only takes states on and off make it a bool and give it name to indicate directly what the two states are. s_ppmu->started = false; > +} > +static const struct samsung_ppmu_ops samsung_ppmu_plat_ops = { If there is only one implementation in a patch set it is best to avoid abstractions and ops tables like this. If you have multiple implementations then submit them all at once so we can see how this is used. For now it complicates the driver for no known reason. > + .write_evtype = samsung_ppmu_write_evtype, > + .get_event_idx = samsung_ppmu_get_event_idx, > + .read_counter = samsung_ppmu_get_read_counter, > + .enable_counter = samsung_ppmu_get_enable_counter, > + .disable_counter = samsung_ppmu_get_disable_counter, > + .start_counters = samsung_ppmu_get_start_counters, > + .stop_counters = samsung_ppmu_get_stop_counters, > + .get_int_status = samsung_ppmu_get_int_status, > + .clear_int_status = samsung_ppmu_clear_int_status, > +}; > + > +static int ppmu_clock_init(struct samsung_ppmu *s_ppmu) > +{ > + int ret = 0; > + struct device *dev = s_ppmu->dev; > + > + s_ppmu->clks[PPMU_ACLK].id = "aclk"; > + s_ppmu->clks[PPMU_PCLK].id = "pclk"; > + > + ret = devm_clk_bulk_get(dev, PPMU_CLK_COUNT, s_ppmu->clks); > + if (ret) { > + dev_err(dev, "Failed to get clocks. Err %d\n", ret); > + return ret; > + } > + > + ret = clk_bulk_prepare_enable(PPMU_CLK_COUNT, s_ppmu->clks); > + if (ret) > + dev_err(dev, "Clock enable failed. Err %d\n", ret); > + > + return ret; > +} > + > +static int samsung_ppmu_probe(struct platform_device *pdev) > +{ > + struct samsung_ppmu *s_ppmu; > + struct device *dev = &pdev->dev; > + u32 version; > + char *name; > + int ret; > + > + s_ppmu = devm_kzalloc(dev, sizeof(*s_ppmu), GFP_KERNEL); > + if (!s_ppmu) > + return -ENOMEM; > + > + s_ppmu->dev = &pdev->dev; > + > + s_ppmu->id = idr_alloc(&my_idr, dev, 0, 2, GFP_KERNEL); Mixing devm cleaned up stuff and things that aren't is usuall a bad idea. You can use devm_add_action_or_reset() to deal with allocations and configuration that does not have it's own devm interfaces. > + if (s_ppmu->id < 0) { > + dev_err(dev, "Failed to allocate ID dynamically\n"); > + return s_ppmu->id; > + } > + > + /* Register base address */ > + s_ppmu->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(s_ppmu->base)) { > + dev_err(dev, "IO remap failed\n"); > + return PTR_ERR(s_ppmu->base); > + } > + > + /* Register IRQ */ > + ret = samsung_ppmu_init_irq(s_ppmu, pdev); > + if (ret) > + return ret; > + > + s_ppmu->check_event = PPMU_EVENT_MAX; > + s_ppmu->num_counters = PPMU_MAX_COUNTERS; > + s_ppmu->on_cpu = 0; > + s_ppmu->identifier = PPMU_V24_IDENTIFIER; > + > + s_ppmu->ppmu_data = device_get_match_data(&pdev->dev); > + if (!s_ppmu->ppmu_data) { > + dev_err(&pdev->dev, "No matching device data found\n"); > + return -ENODEV; > + } > + > + /* Register PPMU driver ops */ > + s_ppmu->pmu_events.attr_groups = s_ppmu->ppmu_data->ppmu_attr_group; > + s_ppmu->ops = &samsung_ppmu_plat_ops; > + > + /* Set private data to platform_device structure */ > + platform_set_drvdata(pdev, s_ppmu); > + > + /* Initialize the PPMU */ > + samsung_ppmu_init(s_ppmu, THIS_MODULE); > + > + ret = ppmu_clock_init(s_ppmu); > + if (ret) > + return ret; > + > + version = readl(s_ppmu->base + PPMU_VERSION); > + version &= PPMU_VERSION_CHECK; > + version >>= 12; > + s_ppmu->samsung_ppmu_version = version; > + > + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ppmu_v_%x_%d", version, s_ppmu->id); > + if (!name) > + return -ENOMEM; > + > + ret = perf_pmu_register(&s_ppmu->pmu, name, -1); > + if (ret) { > + clk_bulk_disable_unprepare(PPMU_CLK_COUNT, s_ppmu->clks); > + dev_err(dev, "Failed to register PPMU in perf. Err %d\n", ret); > + } > + > + return ret; > +} > + > +static void samsung_ppmu_remove(struct platform_device *pdev) > +{ > + struct samsung_ppmu *s_ppmu = platform_get_drvdata(pdev); > + > + clk_bulk_disable_unprepare(PPMU_CLK_COUNT, s_ppmu->clks); Should reverse what happens in probe. Here you turn off clocks bfore you remove userspace interfaces which looks unlikely to be correct. > + > + perf_pmu_unregister(&s_ppmu->pmu); > + > + idr_remove(&my_idr, s_ppmu->id); > +} > diff --git a/drivers/perf/samsung/ppmu_platform.c b/drivers/perf/samsung/ppmu_platform.c > new file mode 100644 > index 000000000000..ee11311d5a61 > --- /dev/null > +++ b/drivers/perf/samsung/ppmu_platform.c > @@ -0,0 +1,338 @@ > +int samsung_ppmu_init_irq(struct samsung_ppmu *samsung_ppmu, > + struct platform_device *pdev) > +{ > + int irq0, irq1, ret, irq_count; > + > + irq0 = platform_get_irq(pdev, 0); > + if (irq0 < 0) { > + dev_err(&pdev->dev, "Failed to get IRQ 0\n"); In stuff that only happens from probe() use return dev_err_probe(&pdev->dev, irq0, "Failed to get IRQ 0\n"); etc > + return irq0; > + } > + > + ret = devm_request_irq(&pdev->dev, irq0, samsung_ppmu_isr, > + IRQF_NOBALANCING | IRQF_NO_THREAD | IRQF_SHARED, > + dev_name(&pdev->dev), samsung_ppmu); > + if (ret) { > + dev_err(&pdev->dev, > + "Fail to request IRQ: %d ret: %d.\n", irq0, ret); > + return ret; > + } > + > + samsung_ppmu->irq0 = irq0; > + > + irq_count = of_property_count_elems_of_size(pdev->dev.of_node, "interrupts", sizeof(u32)); > + if (irq_count > 1) { > + irq1 = platform_get_irq(pdev, 1); > + if (irq1 < 0) { > + dev_err(&pdev->dev, "Failed to get IRQ 0\n"); > + return irq1; > + } > + > + ret = devm_request_irq(&pdev->dev, irq1, samsung_ppmu_isr, > + IRQF_NOBALANCING | IRQF_NO_THREAD | IRQF_SHARED, > + dev_name(&pdev->dev), samsung_ppmu); > + if (ret) { > + dev_err(&pdev->dev, > + "Fail to request IRQ: %d ret: %d.\n", irq1, ret); > + return ret; > + } > + samsung_ppmu->irq1 = irq1; > + } > + > + return ret; If you know it's 0, then hard code that to make it obvious that this is the good exit path. return 0; > +} > +EXPORT_SYMBOL_GPL(samsung_ppmu_init_irq); > +void samsung_ppmu_disable(struct pmu *pmu) > +{ > + struct samsung_ppmu *samsung_ppmu = to_samsung_ppmu(pmu); > + > + samsung_ppmu->ops->stop_counters(samsung_ppmu); > +} > +EXPORT_SYMBOL_GPL(samsung_ppmu_disable); As noted, 1 module so no exports needed. As in many comments in this review, if you don't have multiple stop_counters implementations, don't have these ops wrappers. They massively increase code complexity for no gain yet. > + > +void samsung_ppmu_init(struct samsung_ppmu *s_ppmu, struct module *module) > +{ > + struct pmu *pmu = &s_ppmu->pmu; > + > + pmu->module = module; > + pmu->task_ctx_nr = perf_invalid_context; > + pmu->event_init = samsung_ppmu_event_init; > + pmu->pmu_enable = samsung_ppmu_enable; > + pmu->pmu_disable = samsung_ppmu_disable; > + pmu->add = samsung_ppmu_add; > + pmu->del = samsung_ppmu_del; > + pmu->start = samsung_ppmu_start; > + pmu->stop = samsung_ppmu_stop; > + pmu->read = samsung_ppmu_read; > + pmu->attr_groups = s_ppmu->pmu_events.attr_groups; > + pmu->capabilities = PERF_PMU_CAP_NO_EXCLUDE; > +} > +EXPORT_SYMBOL_GPL(samsung_ppmu_init); > diff --git a/drivers/perf/samsung/samsung_ppmu.h b/drivers/perf/samsung/samsung_ppmu.h > new file mode 100644 > index 000000000000..2cad75cfa97b > --- /dev/null > +++ b/drivers/perf/samsung/samsung_ppmu.h Squashing the lot into 1 c file with no header will end up simpler I think. > @@ -0,0 +1,128 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Samsung Platform Performance Measuring Unit (PPMU) headers > + * > + * Copyright (c) 2024-25 Samsung Electronics Co., Ltd. > + * > + * Authors: Vivek Yadav <vivek.2311@samsung.com> > + * Ravi Patel <ravi.patel@samsung.com> > + */ > + > +#ifndef __SAMSUNG_PPMU_H__ > +#define __SAMSUNG_PPMU_H__ > + > +#include <linux/clk.h> > + > +#define PPMU_MAX_COUNTERS (5) It's a number not a macro parameter, so no need for () > + > +#define to_samsung_ppmu(p) (container_of(p, struct samsung_ppmu, pmu)) > + > +#define SAMSUNG_PPMU_ATTR(_name, _func, _config) \ > + (&((struct dev_ext_attribute[]) { \ > + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ > + })[0].attr.attr) > + > +#define SAMSUNG_PPMU_FORMAT_ATTR(_name, _config) \ > + SAMSUNG_PPMU_ATTR(_name, samsung_ppmu_format_sysfs_show, (void *)_config) > +#define SAMSUNG_PPMU_EVENT_ATTR(_name, _config) \ > + SAMSUNG_PPMU_ATTR(_name, samsung_ppmu_event_sysfs_show, (unsigned long)_config) > + > +#define SAMSUNG_PPMU_GET_EVENTID(ev) ((ev)->hw.config_base & 0xff) > + > +enum ppmu_clock_type { > + PPMU_ACLK, > + PPMU_PCLK, > + PPMU_CLK_COUNT, Given this is (I think) just here as a counter element and hence must always be last, good to drop the trailing , > +}; > + > +enum ppmu_status { > + PPMU_STOP, > + PPMU_START, > +}; > + > +struct samsung_ppmu; > + > +struct samsung_ppmu_ops { As mentioned above, a ops table for one implementation looks like preparation for generalization that may never happen. If you don't have a second user today, just squash the calls inline and drop this. It is easy to bring the abstraction back at the point where it is useful (1st patch in a series that adds a second user of shared infrastructure) > + void (*write_evtype)(struct samsung_ppmu *s_ppmu, int idx, u32 type); > + int (*get_event_idx)(struct perf_event *event); > + u64 (*read_counter)(struct samsung_ppmu *s_ppmu, struct hw_perf_event *event); > + void (*enable_counter)(struct samsung_ppmu *s_ppmu, struct hw_perf_event *event); > + void (*disable_counter)(struct samsung_ppmu *s_ppmu, struct hw_perf_event *event); > + void (*start_counters)(struct samsung_ppmu *s_ppmu); > + void (*stop_counters)(struct samsung_ppmu *s_ppmu); > + u32 (*get_int_status)(struct samsung_ppmu *s_ppmu); > + void (*clear_int_status)(struct samsung_ppmu *s_ppmu, int idx); > +}; > + > +/* Describes the Samsung PPMU features information */ > +struct samsung_ppmu_dev_info { Not used that I can see. You have an instance in the structure below but nothing ever sets it. > + const char *name; > + const struct attribute_group **attr_groups; > + void *private; > +}; > + > +struct samsung_ppmu_hwevents { If this is never used except as encapsulation of related fields in the main structure below then no need for the type. Just have the structure defined inline in struct samsung_ppmu > + struct perf_event *hw_events[PPMU_MAX_COUNTERS]; > + DECLARE_BITMAP(used_mask, PPMU_MAX_COUNTERS); > + const struct attribute_group **attr_groups; > +}; > + > +struct samsung_ppmu_drv_data { Currently only one value - as such just hard code it in the driver (all these comments only apply if you don't have another driver to post that also uses the shared infrastructure). > + const struct attribute_group **ppmu_attr_group; > +}; > + > +/* Generic pmu struct for different pmu types */ > +struct samsung_ppmu { > + struct pmu pmu; > + const struct samsung_ppmu_ops *ops; > + const struct samsung_ppmu_dev_info *dev_info; > + struct samsung_ppmu_hwevents pmu_events; > + const struct samsung_ppmu_drv_data *ppmu_data; > + u32 samsung_ppmu_version; > + u32 samsung_ppmu_master_id_val; > + u8 status; > + u8 id; > + /* CPU used for counting */ > + int on_cpu; > + int irq0; > + int irq1; > + struct device *dev; > + struct hlist_node node; > + void __iomem *base; > + int num_counters; > + u32 counter_overflow[PPMU_MAX_COUNTERS]; > + u64 prev_counter[PPMU_MAX_COUNTERS]; > + /* check event code range */ > + int check_event; > + u32 identifier; > + struct clk_bulk_data clks[PPMU_CLK_COUNT]; > +}; > + > +void samsung_ppmu_read(struct perf_event *event); > +int samsung_ppmu_add(struct perf_event *event, int flags); > +void samsung_ppmu_del(struct perf_event *event, int flags); > +void samsung_ppmu_start(struct perf_event *event, int flags); > +void samsung_ppmu_stop(struct perf_event *event, int flags); > +void samsung_ppmu_set_event_period(struct perf_event *event); > +void samsung_ppmu_event_update(struct perf_event *event); > +int samsung_ppmu_event_init(struct perf_event *event); > +void samsung_ppmu_enable(struct pmu *pmu); > +void samsung_ppmu_disable(struct pmu *pmu); > +ssize_t samsung_ppmu_event_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf); > +ssize_t samsung_ppmu_format_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf); > +ssize_t samsung_ppmu_cpumask_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf); > +int samsung_ppmu_online_cpu(unsigned int cpu, struct hlist_node *node); > +int samsung_ppmu_offline_cpu(unsigned int cpu, struct hlist_node *node); > + > +ssize_t samsung_ppmu_identifier_attr_show(struct device *dev, > + struct device_attribute *attr, > + char *page); > +int samsung_ppmu_init_irq(struct samsung_ppmu *samsung_ppmu, > + struct platform_device *pdev); > + > +void samsung_ppmu_init(struct samsung_ppmu *samsung_ppmu, struct module *module); > + > +#endif /* __SAMSUNG_PPMU_H__ */
On 08/07/2025 12:32, Vivek Yadav wrote: > +static struct platform_driver samsung_ppmu_driver = { > + .probe = samsung_ppmu_probe, > + .remove = samsung_ppmu_remove, > + .driver = { > + .name = "samsung-ppmu", > + .of_match_table = ppmu_of_match, > + }, > +}; > + > +module_platform_driver(samsung_ppmu_driver); > + > +MODULE_ALIAS("perf:samsung-ppmu"); No, drop. What's with these aliases from samsung... second try today. From where do you get such code? > +MODULE_DESCRIPTION("Samsung Platform Performance Measuring Unit (PPMU) driver"); > +MODULE_AUTHOR("Vivek Yadav <vivek.2311@samsung.com>"); > +MODULE_AUTHOR("Ravi Patel <ravi.patel@samsung.com>"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/perf/samsung/ppmu_platform.c b/drivers/perf/samsung/ppmu_platform.c > new file mode 100644 > index 000000000000..ee11311d5a61 > --- /dev/null > +++ b/drivers/perf/samsung/ppmu_platform.c > @@ -0,0 +1,338 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Samsung Platform Performance Measuring Unit (PPMU) core file > + * > + * Copyright (c) 2024-25 Samsung Electronics Co., Ltd. > + * > + * Authors: Vivek Yadav <vivek.2311@samsung.com> > + * Ravi Patel <ravi.patel@samsung.com> > + */ > + > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/perf_event.h> > +#include "samsung_ppmu.h" > + > +/* > + * PMU format attributes > + */ > +ssize_t samsung_ppmu_format_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct dev_ext_attribute *eattr; > + > + eattr = container_of(attr, struct dev_ext_attribute, attr); > + > + return sysfs_emit(buf, "%s\n", (char *)eattr->var); > +} > +EXPORT_SYMBOL_GPL(samsung_ppmu_format_sysfs_show); 1. Where did you document ABI? 2. This is not used. You have one module, not two. Drop all exports. 3. You need to clearly explain what is wrong with existing drivers in a very detailed way, before you start posting another (possibly duplicated) drivers. Best regards, Krzysztof
© 2016 - 2025 Red Hat, Inc.