[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