[PATCH 4/6] drivers: perf: samsung: Add PPMU driver support
Vivek Yadav
vivek.2311 at samsung.com
Tue Jul 8 03:32:06 PDT 2025
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 at samsung.com>
Signed-off-by: Vivek Yadav <vivek.2311 at 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 at samsung.com>
+ * Ravi Patel <ravi.patel at 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 at samsung.com>");
+MODULE_AUTHOR("Ravi Patel <ravi.patel at 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 at samsung.com>
+ * Ravi Patel <ravi.patel at 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 at samsung.com>");
+MODULE_AUTHOR("Ravi Patel <ravi.patel at 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 at samsung.com>
+ * Ravi Patel <ravi.patel at 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
More information about the linux-arm-kernel
mailing list