lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250708103208.79444-5-vivek.2311@samsung.com>
Date: Tue,  8 Jul 2025 16:02:06 +0530
From: Vivek Yadav <vivek.2311@...sung.com>
To: pankaj.dubey@...sung.com, ravi.patel@...sung.com, shradha.t@...sung.com,
	mturquette@...libre.com, sboyd@...nel.org, robh@...nel.org, krzk@...nel.org,
	krzk+dt@...nel.org, conor+dt@...nel.org, will@...nel.org,
	mark.rutland@....com, s.nawrocki@...sung.com, cw00.choi@...sung.com,
	alim.akhtar@...sung.com, linux-fsd@...la.com
Cc: linux-clk@...r.kernel.org, devicetree@...r.kernel.org,
	linux-kernel@...r.kernel.org, linux-arm-kernel@...ts.infradead.org,
	linux-perf-users@...r.kernel.org, linux-samsung-soc@...r.kernel.org, Vivek
	Yadav <vivek.2311@...sung.com>
Subject: [PATCH 4/6] drivers: perf: samsung: Add PPMU driver support

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@...sung.com>
Signed-off-by: Vivek Yadav <vivek.2311@...sung.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@...sung.com>
+ *          Ravi Patel <ravi.patel@...sung.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@...sung.com>");
+MODULE_AUTHOR("Ravi Patel <ravi.patel@...sung.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@...sung.com>
+ *          Ravi Patel <ravi.patel@...sung.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@...sung.com>");
+MODULE_AUTHOR("Ravi Patel <ravi.patel@...sung.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@...sung.com>
+ *          Ravi Patel <ravi.patel@...sung.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


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ