summaryrefslogtreecommitdiffstats
path: root/drivers/devfreq/event
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
commit3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch)
treeb767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/devfreq/event
downloadlinux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz
linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'drivers/devfreq/event')
-rw-r--r--drivers/devfreq/event/Kconfig42
-rw-r--r--drivers/devfreq/event/Makefile6
-rw-r--r--drivers/devfreq/event/exynos-nocp.c297
-rw-r--r--drivers/devfreq/event/exynos-nocp.h75
-rw-r--r--drivers/devfreq/event/exynos-ppmu.c714
-rw-r--r--drivers/devfreq/event/exynos-ppmu.h160
-rw-r--r--drivers/devfreq/event/rockchip-dfi.c889
7 files changed, 2183 insertions, 0 deletions
diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig
new file mode 100644
index 000000000..878825372
--- /dev/null
+++ b/drivers/devfreq/event/Kconfig
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig PM_DEVFREQ_EVENT
+ bool "DEVFREQ-Event device Support"
+ help
+ The devfreq-event device provide the raw data and events which
+ indicate the current state of devfreq-event device. The provided
+ data from devfreq-event device is used to monitor the state of
+ device and determine the suitable size of resource to reduce the
+ wasted resource.
+
+ The devfreq-event device can support the various type of events
+ (e.g., raw data, utilization, latency, bandwidth). The events
+ may be used by devfreq governor and other subsystem.
+
+if PM_DEVFREQ_EVENT
+
+config DEVFREQ_EVENT_EXYNOS_NOCP
+ tristate "Exynos NoC (Network On Chip) Probe DEVFREQ event Driver"
+ depends on ARCH_EXYNOS || COMPILE_TEST
+ select PM_OPP
+ select REGMAP_MMIO
+ help
+ This add the devfreq-event driver for Exynos SoC. It provides NoC
+ (Network on Chip) Probe counters to measure the bandwidth of AXI bus.
+
+config DEVFREQ_EVENT_EXYNOS_PPMU
+ tristate "Exynos PPMU (Platform Performance Monitoring Unit) DEVFREQ event Driver"
+ depends on ARCH_EXYNOS || COMPILE_TEST
+ select PM_OPP
+ help
+ This add the devfreq-event driver for Exynos SoC. It provides PPMU
+ (Platform Performance Monitoring Unit) counters to estimate the
+ utilization of each module.
+
+config DEVFREQ_EVENT_ROCKCHIP_DFI
+ tristate "ROCKCHIP DFI DEVFREQ event Driver"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ help
+ This add the devfreq-event driver for Rockchip SoC. It provides DFI
+ (DDR Monitor Module) driver to count ddr load.
+
+endif # PM_DEVFREQ_EVENT
diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile
new file mode 100644
index 000000000..3c847e5d5
--- /dev/null
+++ b/drivers/devfreq/event/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Exynos DEVFREQ Event Drivers
+
+obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o
+obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o
+obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o
diff --git a/drivers/devfreq/event/exynos-nocp.c b/drivers/devfreq/event/exynos-nocp.c
new file mode 100644
index 000000000..6a3efd782
--- /dev/null
+++ b/drivers/devfreq/event/exynos-nocp.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * exynos-nocp.c - Exynos NoC (Network On Chip) Probe support
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Author : Chanwoo Choi <cw00.choi@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/devfreq-event.h>
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "exynos-nocp.h"
+
+struct exynos_nocp {
+ struct devfreq_event_dev *edev;
+ struct devfreq_event_desc desc;
+
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk;
+};
+
+/*
+ * The devfreq-event ops structure for nocp probe.
+ */
+static int exynos_nocp_set_event(struct devfreq_event_dev *edev)
+{
+ struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev);
+ int ret;
+
+ /* Disable NoC probe */
+ ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
+ NOCP_MAIN_CTL_STATEN_MASK, 0);
+ if (ret < 0) {
+ dev_err(nocp->dev, "failed to disable the NoC probe device\n");
+ return ret;
+ }
+
+ /* Set a statistics dump period to 0 */
+ ret = regmap_write(nocp->regmap, NOCP_STAT_PERIOD, 0x0);
+ if (ret < 0)
+ goto out;
+
+ /* Set the IntEvent fields of *_SRC */
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_SRC,
+ NOCP_CNT_SRC_INTEVENT_MASK,
+ NOCP_CNT_SRC_INTEVENT_BYTE_MASK);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_SRC,
+ NOCP_CNT_SRC_INTEVENT_MASK,
+ NOCP_CNT_SRC_INTEVENT_CHAIN_MASK);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_SRC,
+ NOCP_CNT_SRC_INTEVENT_MASK,
+ NOCP_CNT_SRC_INTEVENT_CYCLE_MASK);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_SRC,
+ NOCP_CNT_SRC_INTEVENT_MASK,
+ NOCP_CNT_SRC_INTEVENT_CHAIN_MASK);
+ if (ret < 0)
+ goto out;
+
+
+ /* Set an alarm with a max/min value of 0 to generate StatALARM */
+ ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MIN, 0x0);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MAX, 0x0);
+ if (ret < 0)
+ goto out;
+
+ /* Set AlarmMode */
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_ALARM_MODE,
+ NOCP_CNT_ALARM_MODE_MASK,
+ NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_ALARM_MODE,
+ NOCP_CNT_ALARM_MODE_MASK,
+ NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_ALARM_MODE,
+ NOCP_CNT_ALARM_MODE_MASK,
+ NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_ALARM_MODE,
+ NOCP_CNT_ALARM_MODE_MASK,
+ NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
+ if (ret < 0)
+ goto out;
+
+ /* Enable the measurements by setting AlarmEn and StatEn */
+ ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
+ NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK,
+ NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK);
+ if (ret < 0)
+ goto out;
+
+ /* Set GlobalEN */
+ ret = regmap_update_bits(nocp->regmap, NOCP_CFG_CTL,
+ NOCP_CFG_CTL_GLOBALEN_MASK,
+ NOCP_CFG_CTL_GLOBALEN_MASK);
+ if (ret < 0)
+ goto out;
+
+ /* Enable NoC probe */
+ ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
+ NOCP_MAIN_CTL_STATEN_MASK,
+ NOCP_MAIN_CTL_STATEN_MASK);
+ if (ret < 0)
+ goto out;
+
+ return 0;
+
+out:
+ /* Reset NoC probe */
+ if (regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
+ NOCP_MAIN_CTL_STATEN_MASK, 0)) {
+ dev_err(nocp->dev, "Failed to reset NoC probe device\n");
+ }
+
+ return ret;
+}
+
+static int exynos_nocp_get_event(struct devfreq_event_dev *edev,
+ struct devfreq_event_data *edata)
+{
+ struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev);
+ unsigned int counter[4];
+ int ret;
+
+ /* Read cycle count */
+ ret = regmap_read(nocp->regmap, NOCP_COUNTERS_0_VAL, &counter[0]);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_read(nocp->regmap, NOCP_COUNTERS_1_VAL, &counter[1]);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_read(nocp->regmap, NOCP_COUNTERS_2_VAL, &counter[2]);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_read(nocp->regmap, NOCP_COUNTERS_3_VAL, &counter[3]);
+ if (ret < 0)
+ goto out;
+
+ edata->load_count = ((counter[1] << 16) | counter[0]);
+ edata->total_count = ((counter[3] << 16) | counter[2]);
+
+ dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name,
+ edata->load_count, edata->total_count);
+
+ return 0;
+
+out:
+ dev_err(nocp->dev, "Failed to read the counter of NoC probe device\n");
+
+ return ret;
+}
+
+static const struct devfreq_event_ops exynos_nocp_ops = {
+ .set_event = exynos_nocp_set_event,
+ .get_event = exynos_nocp_get_event,
+};
+
+static const struct of_device_id exynos_nocp_id_match[] = {
+ { .compatible = "samsung,exynos5420-nocp", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, exynos_nocp_id_match);
+
+static struct regmap_config exynos_nocp_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = NOCP_COUNTERS_3_VAL,
+};
+
+static int exynos_nocp_parse_dt(struct platform_device *pdev,
+ struct exynos_nocp *nocp)
+{
+ struct device *dev = nocp->dev;
+ struct device_node *np = dev->of_node;
+ struct resource *res;
+ void __iomem *base;
+
+ if (!np) {
+ dev_err(dev, "failed to find devicetree node\n");
+ return -EINVAL;
+ }
+
+ nocp->clk = devm_clk_get(dev, "nocp");
+ if (IS_ERR(nocp->clk))
+ nocp->clk = NULL;
+
+ /* Maps the memory mapped IO to control nocp register */
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ exynos_nocp_regmap_config.max_register = resource_size(res) - 4;
+
+ nocp->regmap = devm_regmap_init_mmio(dev, base,
+ &exynos_nocp_regmap_config);
+ if (IS_ERR(nocp->regmap)) {
+ dev_err(dev, "failed to initialize regmap\n");
+ return PTR_ERR(nocp->regmap);
+ }
+
+ return 0;
+}
+
+static int exynos_nocp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct exynos_nocp *nocp;
+ int ret;
+
+ nocp = devm_kzalloc(&pdev->dev, sizeof(*nocp), GFP_KERNEL);
+ if (!nocp)
+ return -ENOMEM;
+
+ nocp->dev = &pdev->dev;
+
+ /* Parse dt data to get resource */
+ ret = exynos_nocp_parse_dt(pdev, nocp);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to parse devicetree for resource\n");
+ return ret;
+ }
+
+ /* Add devfreq-event device to measure the bandwidth of NoC */
+ nocp->desc.ops = &exynos_nocp_ops;
+ nocp->desc.driver_data = nocp;
+ nocp->desc.name = np->full_name;
+ nocp->edev = devm_devfreq_event_add_edev(&pdev->dev, &nocp->desc);
+ if (IS_ERR(nocp->edev)) {
+ dev_err(&pdev->dev,
+ "failed to add devfreq-event device\n");
+ return PTR_ERR(nocp->edev);
+ }
+ platform_set_drvdata(pdev, nocp);
+
+ ret = clk_prepare_enable(nocp->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to prepare ppmu clock\n");
+ return ret;
+ }
+
+ pr_info("exynos-nocp: new NoC Probe device registered: %s\n",
+ dev_name(dev));
+
+ return 0;
+}
+
+static void exynos_nocp_remove(struct platform_device *pdev)
+{
+ struct exynos_nocp *nocp = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(nocp->clk);
+}
+
+static struct platform_driver exynos_nocp_driver = {
+ .probe = exynos_nocp_probe,
+ .remove = exynos_nocp_remove,
+ .driver = {
+ .name = "exynos-nocp",
+ .of_match_table = exynos_nocp_id_match,
+ },
+};
+module_platform_driver(exynos_nocp_driver);
+
+MODULE_DESCRIPTION("Exynos NoC (Network on Chip) Probe driver");
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/devfreq/event/exynos-nocp.h b/drivers/devfreq/event/exynos-nocp.h
new file mode 100644
index 000000000..2d6f08cfd
--- /dev/null
+++ b/drivers/devfreq/event/exynos-nocp.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * exynos-nocp.h - Exynos NoC (Network on Chip) Probe header file
+ *
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Author : Chanwoo Choi <cw00.choi@samsung.com>
+ */
+
+#ifndef __EXYNOS_NOCP_H__
+#define __EXYNOS_NOCP_H__
+
+enum nocp_reg {
+ NOCP_ID_REVISION_ID = 0x04,
+ NOCP_MAIN_CTL = 0x08,
+ NOCP_CFG_CTL = 0x0C,
+
+ NOCP_STAT_PERIOD = 0x24,
+ NOCP_STAT_GO = 0x28,
+ NOCP_STAT_ALARM_MIN = 0x2C,
+ NOCP_STAT_ALARM_MAX = 0x30,
+ NOCP_STAT_ALARM_STATUS = 0x34,
+ NOCP_STAT_ALARM_CLR = 0x38,
+
+ NOCP_COUNTERS_0_SRC = 0x138,
+ NOCP_COUNTERS_0_ALARM_MODE = 0x13C,
+ NOCP_COUNTERS_0_VAL = 0x140,
+
+ NOCP_COUNTERS_1_SRC = 0x14C,
+ NOCP_COUNTERS_1_ALARM_MODE = 0x150,
+ NOCP_COUNTERS_1_VAL = 0x154,
+
+ NOCP_COUNTERS_2_SRC = 0x160,
+ NOCP_COUNTERS_2_ALARM_MODE = 0x164,
+ NOCP_COUNTERS_2_VAL = 0x168,
+
+ NOCP_COUNTERS_3_SRC = 0x174,
+ NOCP_COUNTERS_3_ALARM_MODE = 0x178,
+ NOCP_COUNTERS_3_VAL = 0x17C,
+};
+
+/* NOCP_MAIN_CTL register */
+#define NOCP_MAIN_CTL_ERREN_MASK BIT(0)
+#define NOCP_MAIN_CTL_TRACEEN_MASK BIT(1)
+#define NOCP_MAIN_CTL_PAYLOADEN_MASK BIT(2)
+#define NOCP_MAIN_CTL_STATEN_MASK BIT(3)
+#define NOCP_MAIN_CTL_ALARMEN_MASK BIT(4)
+#define NOCP_MAIN_CTL_STATCONDDUMP_MASK BIT(5)
+#define NOCP_MAIN_CTL_INTRUSIVEMODE_MASK BIT(6)
+
+/* NOCP_CFG_CTL register */
+#define NOCP_CFG_CTL_GLOBALEN_MASK BIT(0)
+#define NOCP_CFG_CTL_ACTIVE_MASK BIT(1)
+
+/* NOCP_COUNTERS_x_SRC register */
+#define NOCP_CNT_SRC_INTEVENT_SHIFT 0
+#define NOCP_CNT_SRC_INTEVENT_MASK (0x1F << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_OFF_MASK (0x0 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_CYCLE_MASK (0x1 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_IDLE_MASK (0x2 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_XFER_MASK (0x3 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_BUSY_MASK (0x4 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_WAIT_MASK (0x5 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_PKT_MASK (0x6 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_BYTE_MASK (0x8 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+#define NOCP_CNT_SRC_INTEVENT_CHAIN_MASK (0x10 << NOCP_CNT_SRC_INTEVENT_SHIFT)
+
+/* NOCP_COUNTERS_x_ALARM_MODE register */
+#define NOCP_CNT_ALARM_MODE_SHIFT 0
+#define NOCP_CNT_ALARM_MODE_MASK (0x3 << NOCP_CNT_ALARM_MODE_SHIFT)
+#define NOCP_CNT_ALARM_MODE_OFF_MASK (0x0 << NOCP_CNT_ALARM_MODE_SHIFT)
+#define NOCP_CNT_ALARM_MODE_MIN_MASK (0x1 << NOCP_CNT_ALARM_MODE_SHIFT)
+#define NOCP_CNT_ALARM_MODE_MAX_MASK (0x2 << NOCP_CNT_ALARM_MODE_SHIFT)
+#define NOCP_CNT_ALARM_MODE_MIN_MAX_MASK (0x3 << NOCP_CNT_ALARM_MODE_SHIFT)
+
+#endif /* __EXYNOS_NOCP_H__ */
diff --git a/drivers/devfreq/event/exynos-ppmu.c b/drivers/devfreq/event/exynos-ppmu.c
new file mode 100644
index 000000000..88cd4dfe8
--- /dev/null
+++ b/drivers/devfreq/event/exynos-ppmu.c
@@ -0,0 +1,714 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * exynos_ppmu.c - Exynos PPMU (Platform Performance Monitoring Unit) support
+ *
+ * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd.
+ * Author : Chanwoo Choi <cw00.choi@samsung.com>
+ *
+ * This driver is based on drivers/devfreq/exynos/exynos_ppmu.c
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/suspend.h>
+#include <linux/devfreq-event.h>
+
+#include "exynos-ppmu.h"
+
+enum exynos_ppmu_type {
+ EXYNOS_TYPE_PPMU,
+ EXYNOS_TYPE_PPMU_V2,
+};
+
+struct exynos_ppmu_data {
+ struct clk *clk;
+};
+
+struct exynos_ppmu {
+ struct devfreq_event_dev **edev;
+ struct devfreq_event_desc *desc;
+ unsigned int num_events;
+
+ struct device *dev;
+ struct regmap *regmap;
+
+ struct exynos_ppmu_data ppmu;
+ enum exynos_ppmu_type ppmu_type;
+};
+
+#define PPMU_EVENT(name) \
+ { "ppmu-event0-"#name, PPMU_PMNCNT0 }, \
+ { "ppmu-event1-"#name, PPMU_PMNCNT1 }, \
+ { "ppmu-event2-"#name, PPMU_PMNCNT2 }, \
+ { "ppmu-event3-"#name, PPMU_PMNCNT3 }
+
+static struct __exynos_ppmu_events {
+ char *name;
+ int id;
+} ppmu_events[] = {
+ /* For Exynos3250, Exynos4 and Exynos5260 */
+ PPMU_EVENT(g3d),
+ PPMU_EVENT(fsys),
+
+ /* For Exynos4 SoCs and Exynos3250 */
+ PPMU_EVENT(dmc0),
+ PPMU_EVENT(dmc1),
+ PPMU_EVENT(cpu),
+ PPMU_EVENT(rightbus),
+ PPMU_EVENT(leftbus),
+ PPMU_EVENT(lcd0),
+ PPMU_EVENT(camif),
+
+ /* Only for Exynos3250 and Exynos5260 */
+ PPMU_EVENT(mfc),
+
+ /* Only for Exynos4 SoCs */
+ PPMU_EVENT(mfc-left),
+ PPMU_EVENT(mfc-right),
+
+ /* Only for Exynos5260 SoCs */
+ PPMU_EVENT(drex0-s0),
+ PPMU_EVENT(drex0-s1),
+ PPMU_EVENT(drex1-s0),
+ PPMU_EVENT(drex1-s1),
+ PPMU_EVENT(eagle),
+ PPMU_EVENT(kfc),
+ PPMU_EVENT(isp),
+ PPMU_EVENT(fimc),
+ PPMU_EVENT(gscl),
+ PPMU_EVENT(mscl),
+ PPMU_EVENT(fimd0x),
+ PPMU_EVENT(fimd1x),
+
+ /* Only for Exynos5433 SoCs */
+ PPMU_EVENT(d0-cpu),
+ PPMU_EVENT(d0-general),
+ PPMU_EVENT(d0-rt),
+ PPMU_EVENT(d1-cpu),
+ PPMU_EVENT(d1-general),
+ PPMU_EVENT(d1-rt),
+
+ /* For Exynos5422 SoC, deprecated (backwards compatible) */
+ PPMU_EVENT(dmc0_0),
+ PPMU_EVENT(dmc0_1),
+ PPMU_EVENT(dmc1_0),
+ PPMU_EVENT(dmc1_1),
+ /* For Exynos5422 SoC */
+ PPMU_EVENT(dmc0-0),
+ PPMU_EVENT(dmc0-1),
+ PPMU_EVENT(dmc1-0),
+ PPMU_EVENT(dmc1-1),
+};
+
+static int __exynos_ppmu_find_ppmu_id(const char *edev_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ppmu_events); i++)
+ if (!strcmp(edev_name, ppmu_events[i].name))
+ return ppmu_events[i].id;
+
+ return -EINVAL;
+}
+
+static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev)
+{
+ return __exynos_ppmu_find_ppmu_id(edev->desc->name);
+}
+
+/*
+ * The devfreq-event ops structure for PPMU v1.1
+ */
+static int exynos_ppmu_disable(struct devfreq_event_dev *edev)
+{
+ struct exynos_ppmu *info = devfreq_event_get_drvdata(edev);
+ int ret;
+ u32 pmnc;
+
+ /* Disable all counters */
+ ret = regmap_write(info->regmap, PPMU_CNTENC,
+ PPMU_CCNT_MASK |
+ PPMU_PMCNT0_MASK |
+ PPMU_PMCNT1_MASK |
+ PPMU_PMCNT2_MASK |
+ PPMU_PMCNT3_MASK);
+ if (ret < 0)
+ return ret;
+
+ /* Disable PPMU */
+ ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc);
+ if (ret < 0)
+ return ret;
+
+ pmnc &= ~PPMU_PMNC_ENABLE_MASK;
+ ret = regmap_write(info->regmap, PPMU_PMNC, pmnc);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int exynos_ppmu_set_event(struct devfreq_event_dev *edev)
+{
+ struct exynos_ppmu *info = devfreq_event_get_drvdata(edev);
+ int id = exynos_ppmu_find_ppmu_id(edev);
+ int ret;
+ u32 pmnc, cntens;
+
+ if (id < 0)
+ return id;
+
+ /* Enable specific counter */
+ ret = regmap_read(info->regmap, PPMU_CNTENS, &cntens);
+ if (ret < 0)
+ return ret;
+
+ cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id));
+ ret = regmap_write(info->regmap, PPMU_CNTENS, cntens);
+ if (ret < 0)
+ return ret;
+
+ /* Set the event of proper data type monitoring */
+ ret = regmap_write(info->regmap, PPMU_BEVTxSEL(id),
+ edev->desc->event_type);
+ if (ret < 0)
+ return ret;
+
+ /* Reset cycle counter/performance counter and enable PPMU */
+ ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc);
+ if (ret < 0)
+ return ret;
+
+ pmnc &= ~(PPMU_PMNC_ENABLE_MASK
+ | PPMU_PMNC_COUNTER_RESET_MASK
+ | PPMU_PMNC_CC_RESET_MASK);
+ pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT);
+ pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT);
+ pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT);
+ ret = regmap_write(info->regmap, PPMU_PMNC, pmnc);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int exynos_ppmu_get_event(struct devfreq_event_dev *edev,
+ struct devfreq_event_data *edata)
+{
+ struct exynos_ppmu *info = devfreq_event_get_drvdata(edev);
+ int id = exynos_ppmu_find_ppmu_id(edev);
+ unsigned int total_count, load_count;
+ unsigned int pmcnt3_high, pmcnt3_low;
+ unsigned int pmnc, cntenc;
+ int ret;
+
+ if (id < 0)
+ return -EINVAL;
+
+ /* Disable PPMU */
+ ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc);
+ if (ret < 0)
+ return ret;
+
+ pmnc &= ~PPMU_PMNC_ENABLE_MASK;
+ ret = regmap_write(info->regmap, PPMU_PMNC, pmnc);
+ if (ret < 0)
+ return ret;
+
+ /* Read cycle count */
+ ret = regmap_read(info->regmap, PPMU_CCNT, &total_count);
+ if (ret < 0)
+ return ret;
+ edata->total_count = total_count;
+
+ /* Read performance count */
+ switch (id) {
+ case PPMU_PMNCNT0:
+ case PPMU_PMNCNT1:
+ case PPMU_PMNCNT2:
+ ret = regmap_read(info->regmap, PPMU_PMNCT(id), &load_count);
+ if (ret < 0)
+ return ret;
+ edata->load_count = load_count;
+ break;
+ case PPMU_PMNCNT3:
+ ret = regmap_read(info->regmap, PPMU_PMCNT3_HIGH, &pmcnt3_high);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(info->regmap, PPMU_PMCNT3_LOW, &pmcnt3_low);
+ if (ret < 0)
+ return ret;
+
+ edata->load_count = ((pmcnt3_high << 8) | pmcnt3_low);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Disable specific counter */
+ ret = regmap_read(info->regmap, PPMU_CNTENC, &cntenc);
+ if (ret < 0)
+ return ret;
+
+ cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id));
+ ret = regmap_write(info->regmap, PPMU_CNTENC, cntenc);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name,
+ edata->load_count, edata->total_count);
+
+ return 0;
+}
+
+static const struct devfreq_event_ops exynos_ppmu_ops = {
+ .disable = exynos_ppmu_disable,
+ .set_event = exynos_ppmu_set_event,
+ .get_event = exynos_ppmu_get_event,
+};
+
+/*
+ * The devfreq-event ops structure for PPMU v2.0
+ */
+static int exynos_ppmu_v2_disable(struct devfreq_event_dev *edev)
+{
+ struct exynos_ppmu *info = devfreq_event_get_drvdata(edev);
+ int ret;
+ u32 pmnc, clear;
+
+ /* Disable all counters */
+ clear = (PPMU_CCNT_MASK | PPMU_PMCNT0_MASK | PPMU_PMCNT1_MASK
+ | PPMU_PMCNT2_MASK | PPMU_PMCNT3_MASK);
+ ret = regmap_write(info->regmap, PPMU_V2_FLAG, clear);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_INTENC, clear);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CNTENC, clear);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CNT_RESET, clear);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG0, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG1, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG2, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CIG_RESULT, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CNT_AUTO, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CH_EV0_TYPE, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CH_EV1_TYPE, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CH_EV2_TYPE, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_CH_EV3_TYPE, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_SM_ID_V, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_SM_ID_A, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_SM_OTHERS_V, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_SM_OTHERS_A, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(info->regmap, PPMU_V2_INTERRUPT_RESET, 0x0);
+ if (ret < 0)
+ return ret;
+
+ /* Disable PPMU */
+ ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc);
+ if (ret < 0)
+ return ret;
+
+ pmnc &= ~PPMU_PMNC_ENABLE_MASK;
+ ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int exynos_ppmu_v2_set_event(struct devfreq_event_dev *edev)
+{
+ struct exynos_ppmu *info = devfreq_event_get_drvdata(edev);
+ unsigned int pmnc, cntens;
+ int id = exynos_ppmu_find_ppmu_id(edev);
+ int ret;
+
+ /* Enable all counters */
+ ret = regmap_read(info->regmap, PPMU_V2_CNTENS, &cntens);
+ if (ret < 0)
+ return ret;
+
+ cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id));
+ ret = regmap_write(info->regmap, PPMU_V2_CNTENS, cntens);
+ if (ret < 0)
+ return ret;
+
+ /* Set the event of proper data type monitoring */
+ ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id),
+ edev->desc->event_type);
+ if (ret < 0)
+ return ret;
+
+ /* Reset cycle counter/performance counter and enable PPMU */
+ ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc);
+ if (ret < 0)
+ return ret;
+
+ pmnc &= ~(PPMU_PMNC_ENABLE_MASK
+ | PPMU_PMNC_COUNTER_RESET_MASK
+ | PPMU_PMNC_CC_RESET_MASK
+ | PPMU_PMNC_CC_DIVIDER_MASK
+ | PPMU_V2_PMNC_START_MODE_MASK);
+ pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT);
+ pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT);
+ pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT);
+ pmnc |= (PPMU_V2_MODE_MANUAL << PPMU_V2_PMNC_START_MODE_SHIFT);
+
+ ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int exynos_ppmu_v2_get_event(struct devfreq_event_dev *edev,
+ struct devfreq_event_data *edata)
+{
+ struct exynos_ppmu *info = devfreq_event_get_drvdata(edev);
+ int id = exynos_ppmu_find_ppmu_id(edev);
+ int ret;
+ unsigned int pmnc, cntenc;
+ unsigned int pmcnt_high, pmcnt_low;
+ unsigned int total_count, count;
+ unsigned long load_count = 0;
+
+ /* Disable PPMU */
+ ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc);
+ if (ret < 0)
+ return ret;
+
+ pmnc &= ~PPMU_PMNC_ENABLE_MASK;
+ ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc);
+ if (ret < 0)
+ return ret;
+
+ /* Read cycle count and performance count */
+ ret = regmap_read(info->regmap, PPMU_V2_CCNT, &total_count);
+ if (ret < 0)
+ return ret;
+ edata->total_count = total_count;
+
+ switch (id) {
+ case PPMU_PMNCNT0:
+ case PPMU_PMNCNT1:
+ case PPMU_PMNCNT2:
+ ret = regmap_read(info->regmap, PPMU_V2_PMNCT(id), &count);
+ if (ret < 0)
+ return ret;
+ load_count = count;
+ break;
+ case PPMU_PMNCNT3:
+ ret = regmap_read(info->regmap, PPMU_V2_PMCNT3_HIGH,
+ &pmcnt_high);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(info->regmap, PPMU_V2_PMCNT3_LOW, &pmcnt_low);
+ if (ret < 0)
+ return ret;
+
+ load_count = ((u64)((pmcnt_high & 0xff)) << 32)+ (u64)pmcnt_low;
+ break;
+ }
+ edata->load_count = load_count;
+
+ /* Disable all counters */
+ ret = regmap_read(info->regmap, PPMU_V2_CNTENC, &cntenc);
+ if (ret < 0)
+ return 0;
+
+ cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id));
+ ret = regmap_write(info->regmap, PPMU_V2_CNTENC, cntenc);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(&edev->dev, "%25s (load: %ld / %ld)\n", edev->desc->name,
+ edata->load_count, edata->total_count);
+ return 0;
+}
+
+static const struct devfreq_event_ops exynos_ppmu_v2_ops = {
+ .disable = exynos_ppmu_v2_disable,
+ .set_event = exynos_ppmu_v2_set_event,
+ .get_event = exynos_ppmu_v2_get_event,
+};
+
+static const struct of_device_id exynos_ppmu_id_match[] = {
+ {
+ .compatible = "samsung,exynos-ppmu",
+ .data = (void *)EXYNOS_TYPE_PPMU,
+ }, {
+ .compatible = "samsung,exynos-ppmu-v2",
+ .data = (void *)EXYNOS_TYPE_PPMU_V2,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, exynos_ppmu_id_match);
+
+static int of_get_devfreq_events(struct device_node *np,
+ struct exynos_ppmu *info)
+{
+ struct devfreq_event_desc *desc;
+ struct device *dev = info->dev;
+ struct device_node *events_np, *node;
+ int i, j, count;
+ int ret;
+
+ events_np = of_get_child_by_name(np, "events");
+ if (!events_np) {
+ dev_err(dev,
+ "failed to get child node of devfreq-event devices\n");
+ return -EINVAL;
+ }
+
+ count = of_get_child_count(events_np);
+ desc = devm_kcalloc(dev, count, sizeof(*desc), GFP_KERNEL);
+ if (!desc) {
+ of_node_put(events_np);
+ return -ENOMEM;
+ }
+ info->num_events = count;
+
+ info->ppmu_type = (enum exynos_ppmu_type)device_get_match_data(dev);
+
+ j = 0;
+ for_each_child_of_node(events_np, node) {
+ for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) {
+ if (!ppmu_events[i].name)
+ continue;
+
+ if (of_node_name_eq(node, ppmu_events[i].name))
+ break;
+ }
+
+ if (i == ARRAY_SIZE(ppmu_events)) {
+ dev_warn(dev,
+ "don't know how to configure events : %pOFn\n",
+ node);
+ continue;
+ }
+
+ switch (info->ppmu_type) {
+ case EXYNOS_TYPE_PPMU:
+ desc[j].ops = &exynos_ppmu_ops;
+ break;
+ case EXYNOS_TYPE_PPMU_V2:
+ desc[j].ops = &exynos_ppmu_v2_ops;
+ break;
+ }
+
+ desc[j].driver_data = info;
+
+ of_property_read_string(node, "event-name", &desc[j].name);
+ ret = of_property_read_u32(node, "event-data-type",
+ &desc[j].event_type);
+ if (ret) {
+ /* Set the event of proper data type counting.
+ * Check if the data type has been defined in DT,
+ * use default if not.
+ */
+ if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) {
+ /* Not all registers take the same value for
+ * read+write data count.
+ */
+ switch (ppmu_events[i].id) {
+ case PPMU_PMNCNT0:
+ case PPMU_PMNCNT1:
+ case PPMU_PMNCNT2:
+ desc[j].event_type = PPMU_V2_RO_DATA_CNT
+ | PPMU_V2_WO_DATA_CNT;
+ break;
+ case PPMU_PMNCNT3:
+ desc[j].event_type =
+ PPMU_V2_EVT3_RW_DATA_CNT;
+ break;
+ }
+ } else {
+ desc[j].event_type = PPMU_RO_DATA_CNT |
+ PPMU_WO_DATA_CNT;
+ }
+ }
+
+ j++;
+ }
+ info->desc = desc;
+
+ of_node_put(events_np);
+
+ return 0;
+}
+
+static struct regmap_config exynos_ppmu_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int exynos_ppmu_parse_dt(struct platform_device *pdev,
+ struct exynos_ppmu *info)
+{
+ struct device *dev = info->dev;
+ struct device_node *np = dev->of_node;
+ struct resource *res;
+ void __iomem *base;
+ int ret = 0;
+
+ if (!np) {
+ dev_err(dev, "failed to find devicetree node\n");
+ return -EINVAL;
+ }
+
+ /* Maps the memory mapped IO to control PPMU register */
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ exynos_ppmu_regmap_config.max_register = resource_size(res) - 4;
+ info->regmap = devm_regmap_init_mmio(dev, base,
+ &exynos_ppmu_regmap_config);
+ if (IS_ERR(info->regmap)) {
+ dev_err(dev, "failed to initialize regmap\n");
+ return PTR_ERR(info->regmap);
+ }
+
+ info->ppmu.clk = devm_clk_get(dev, "ppmu");
+ if (IS_ERR(info->ppmu.clk)) {
+ info->ppmu.clk = NULL;
+ dev_warn(dev, "cannot get PPMU clock\n");
+ }
+
+ ret = of_get_devfreq_events(np, info);
+ if (ret < 0) {
+ dev_err(dev, "failed to parse exynos ppmu dt node\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int exynos_ppmu_probe(struct platform_device *pdev)
+{
+ struct exynos_ppmu *info;
+ struct devfreq_event_dev **edev;
+ struct devfreq_event_desc *desc;
+ int i, ret = 0, size;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = &pdev->dev;
+
+ /* Parse dt data to get resource */
+ ret = exynos_ppmu_parse_dt(pdev, info);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to parse devicetree for resource\n");
+ return ret;
+ }
+ desc = info->desc;
+
+ size = sizeof(struct devfreq_event_dev *) * info->num_events;
+ info->edev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!info->edev)
+ return -ENOMEM;
+
+ edev = info->edev;
+ platform_set_drvdata(pdev, info);
+
+ for (i = 0; i < info->num_events; i++) {
+ edev[i] = devm_devfreq_event_add_edev(&pdev->dev, &desc[i]);
+ if (IS_ERR(edev[i])) {
+ dev_err(&pdev->dev,
+ "failed to add devfreq-event device\n");
+ return PTR_ERR(edev[i]);
+ }
+
+ pr_info("exynos-ppmu: new PPMU device registered %s (%s)\n",
+ dev_name(&pdev->dev), desc[i].name);
+ }
+
+ ret = clk_prepare_enable(info->ppmu.clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to prepare ppmu clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void exynos_ppmu_remove(struct platform_device *pdev)
+{
+ struct exynos_ppmu *info = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(info->ppmu.clk);
+}
+
+static struct platform_driver exynos_ppmu_driver = {
+ .probe = exynos_ppmu_probe,
+ .remove = exynos_ppmu_remove,
+ .driver = {
+ .name = "exynos-ppmu",
+ .of_match_table = exynos_ppmu_id_match,
+ },
+};
+module_platform_driver(exynos_ppmu_driver);
+
+MODULE_DESCRIPTION("Exynos PPMU(Platform Performance Monitoring Unit) driver");
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/devfreq/event/exynos-ppmu.h b/drivers/devfreq/event/exynos-ppmu.h
new file mode 100644
index 000000000..97f667d0c
--- /dev/null
+++ b/drivers/devfreq/event/exynos-ppmu.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * exynos_ppmu.h - Exynos PPMU header file
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ * Author : Chanwoo Choi <cw00.choi@samsung.com>
+ */
+
+#ifndef __EXYNOS_PPMU_H__
+#define __EXYNOS_PPMU_H__
+
+enum ppmu_state {
+ PPMU_DISABLE = 0,
+ PPMU_ENABLE,
+};
+
+enum ppmu_counter {
+ PPMU_PMNCNT0 = 0,
+ PPMU_PMNCNT1,
+ PPMU_PMNCNT2,
+ PPMU_PMNCNT3,
+
+ PPMU_PMNCNT_MAX,
+};
+
+/***
+ * PPMUv1.1 Definitions
+ */
+enum ppmu_event_type {
+ PPMU_RO_BUSY_CYCLE_CNT = 0x0,
+ PPMU_WO_BUSY_CYCLE_CNT = 0x1,
+ PPMU_RW_BUSY_CYCLE_CNT = 0x2,
+ PPMU_RO_REQUEST_CNT = 0x3,
+ PPMU_WO_REQUEST_CNT = 0x4,
+ PPMU_RO_DATA_CNT = 0x5,
+ PPMU_WO_DATA_CNT = 0x6,
+ PPMU_RO_LATENCY = 0x12,
+ PPMU_WO_LATENCY = 0x16,
+};
+
+enum ppmu_reg {
+ /* PPC control register */
+ PPMU_PMNC = 0x00,
+ PPMU_CNTENS = 0x10,
+ PPMU_CNTENC = 0x20,
+ PPMU_INTENS = 0x30,
+ PPMU_INTENC = 0x40,
+ PPMU_FLAG = 0x50,
+
+ /* Cycle Counter and Performance Event Counter Register */
+ PPMU_CCNT = 0x100,
+ PPMU_PMCNT0 = 0x110,
+ PPMU_PMCNT1 = 0x120,
+ PPMU_PMCNT2 = 0x130,
+ PPMU_PMCNT3_HIGH = 0x140,
+ PPMU_PMCNT3_LOW = 0x150,
+
+ /* Bus Event Generator */
+ PPMU_BEVT0SEL = 0x1000,
+ PPMU_BEVT1SEL = 0x1100,
+ PPMU_BEVT2SEL = 0x1200,
+ PPMU_BEVT3SEL = 0x1300,
+ PPMU_COUNTER_RESET = 0x1810,
+ PPMU_READ_OVERFLOW_CNT = 0x1810,
+ PPMU_READ_UNDERFLOW_CNT = 0x1814,
+ PPMU_WRITE_OVERFLOW_CNT = 0x1850,
+ PPMU_WRITE_UNDERFLOW_CNT = 0x1854,
+ PPMU_READ_PENDING_CNT = 0x1880,
+ PPMU_WRITE_PENDING_CNT = 0x1884
+};
+
+/* PMNC register */
+#define PPMU_PMNC_CC_RESET_SHIFT 2
+#define PPMU_PMNC_COUNTER_RESET_SHIFT 1
+#define PPMU_PMNC_ENABLE_SHIFT 0
+#define PPMU_PMNC_START_MODE_MASK BIT(16)
+#define PPMU_PMNC_CC_DIVIDER_MASK BIT(3)
+#define PPMU_PMNC_CC_RESET_MASK BIT(2)
+#define PPMU_PMNC_COUNTER_RESET_MASK BIT(1)
+#define PPMU_PMNC_ENABLE_MASK BIT(0)
+
+/* CNTENS/CNTENC/INTENS/INTENC/FLAG register */
+#define PPMU_CCNT_MASK BIT(31)
+#define PPMU_PMCNT3_MASK BIT(3)
+#define PPMU_PMCNT2_MASK BIT(2)
+#define PPMU_PMCNT1_MASK BIT(1)
+#define PPMU_PMCNT0_MASK BIT(0)
+
+/* PPMU_PMNCTx/PPMU_BETxSEL registers */
+#define PPMU_PMNCT(x) (PPMU_PMCNT0 + (0x10 * x))
+#define PPMU_BEVTxSEL(x) (PPMU_BEVT0SEL + (0x100 * x))
+
+/***
+ * PPMU_V2.0 definitions
+ */
+enum ppmu_v2_mode {
+ PPMU_V2_MODE_MANUAL = 0,
+ PPMU_V2_MODE_AUTO = 1,
+ PPMU_V2_MODE_CIG = 2, /* CIG (Conditional Interrupt Generation) */
+};
+
+enum ppmu_v2_event_type {
+ PPMU_V2_RO_DATA_CNT = 0x4,
+ PPMU_V2_WO_DATA_CNT = 0x5,
+
+ PPMU_V2_EVT3_RW_DATA_CNT = 0x22, /* Only for Event3 */
+};
+
+enum ppmu_V2_reg {
+ /* PPC control register */
+ PPMU_V2_PMNC = 0x04,
+ PPMU_V2_CNTENS = 0x08,
+ PPMU_V2_CNTENC = 0x0c,
+ PPMU_V2_INTENS = 0x10,
+ PPMU_V2_INTENC = 0x14,
+ PPMU_V2_FLAG = 0x18,
+
+ /* Cycle Counter and Performance Event Counter Register */
+ PPMU_V2_CCNT = 0x48,
+ PPMU_V2_PMCNT0 = 0x34,
+ PPMU_V2_PMCNT1 = 0x38,
+ PPMU_V2_PMCNT2 = 0x3c,
+ PPMU_V2_PMCNT3_LOW = 0x40,
+ PPMU_V2_PMCNT3_HIGH = 0x44,
+
+ /* Bus Event Generator */
+ PPMU_V2_CIG_CFG0 = 0x1c,
+ PPMU_V2_CIG_CFG1 = 0x20,
+ PPMU_V2_CIG_CFG2 = 0x24,
+ PPMU_V2_CIG_RESULT = 0x28,
+ PPMU_V2_CNT_RESET = 0x2c,
+ PPMU_V2_CNT_AUTO = 0x30,
+ PPMU_V2_CH_EV0_TYPE = 0x200,
+ PPMU_V2_CH_EV1_TYPE = 0x204,
+ PPMU_V2_CH_EV2_TYPE = 0x208,
+ PPMU_V2_CH_EV3_TYPE = 0x20c,
+ PPMU_V2_SM_ID_V = 0x220,
+ PPMU_V2_SM_ID_A = 0x224,
+ PPMU_V2_SM_OTHERS_V = 0x228,
+ PPMU_V2_SM_OTHERS_A = 0x22c,
+ PPMU_V2_INTERRUPT_RESET = 0x260,
+};
+
+/* PMNC register */
+#define PPMU_V2_PMNC_START_MODE_SHIFT 20
+#define PPMU_V2_PMNC_START_MODE_MASK (0x3 << PPMU_V2_PMNC_START_MODE_SHIFT)
+
+#define PPMU_PMNC_CC_RESET_SHIFT 2
+#define PPMU_PMNC_COUNTER_RESET_SHIFT 1
+#define PPMU_PMNC_ENABLE_SHIFT 0
+#define PPMU_PMNC_START_MODE_MASK BIT(16)
+#define PPMU_PMNC_CC_DIVIDER_MASK BIT(3)
+#define PPMU_PMNC_CC_RESET_MASK BIT(2)
+#define PPMU_PMNC_COUNTER_RESET_MASK BIT(1)
+#define PPMU_PMNC_ENABLE_MASK BIT(0)
+
+#define PPMU_V2_PMNCT(x) (PPMU_V2_PMCNT0 + (0x4 * x))
+#define PPMU_V2_CH_EVx_TYPE(x) (PPMU_V2_CH_EV0_TYPE + (0x4 * x))
+
+#endif /* __EXYNOS_PPMU_H__ */
diff --git a/drivers/devfreq/event/rockchip-dfi.c b/drivers/devfreq/event/rockchip-dfi.c
new file mode 100644
index 000000000..255aee1bd
--- /dev/null
+++ b/drivers/devfreq/event/rockchip-dfi.c
@@ -0,0 +1,889 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Author: Lin Huang <hl@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/devfreq-event.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/seqlock.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/bitfield.h>
+#include <linux/hw_bitfield.h>
+#include <linux/bits.h>
+#include <linux/perf_event.h>
+
+#include <soc/rockchip/rockchip_grf.h>
+#include <soc/rockchip/rk3399_grf.h>
+#include <soc/rockchip/rk3568_grf.h>
+#include <soc/rockchip/rk3588_grf.h>
+
+#define DMC_MAX_CHANNELS 4
+
+/* DDRMON_CTRL */
+#define DDRMON_CTRL 0x04
+#define DDRMON_CTRL_LPDDR5 BIT(6)
+#define DDRMON_CTRL_DDR4 BIT(5)
+#define DDRMON_CTRL_LPDDR4 BIT(4)
+#define DDRMON_CTRL_HARDWARE_EN BIT(3)
+#define DDRMON_CTRL_LPDDR23 BIT(2)
+#define DDRMON_CTRL_SOFTWARE_EN BIT(1)
+#define DDRMON_CTRL_TIMER_CNT_EN BIT(0)
+#define DDRMON_CTRL_LP5_BANK_MODE_MASK GENMASK(8, 7)
+
+#define DDRMON_CH0_WR_NUM 0x20
+#define DDRMON_CH0_RD_NUM 0x24
+#define DDRMON_CH0_COUNT_NUM 0x28
+#define DDRMON_CH0_DFI_ACCESS_NUM 0x2c
+#define DDRMON_CH1_COUNT_NUM 0x3c
+#define DDRMON_CH1_DFI_ACCESS_NUM 0x40
+
+#define PERF_EVENT_CYCLES 0x0
+#define PERF_EVENT_READ_BYTES 0x1
+#define PERF_EVENT_WRITE_BYTES 0x2
+#define PERF_EVENT_READ_BYTES0 0x3
+#define PERF_EVENT_WRITE_BYTES0 0x4
+#define PERF_EVENT_READ_BYTES1 0x5
+#define PERF_EVENT_WRITE_BYTES1 0x6
+#define PERF_EVENT_READ_BYTES2 0x7
+#define PERF_EVENT_WRITE_BYTES2 0x8
+#define PERF_EVENT_READ_BYTES3 0x9
+#define PERF_EVENT_WRITE_BYTES3 0xa
+#define PERF_EVENT_BYTES 0xb
+#define PERF_ACCESS_TYPE_MAX 0xc
+
+/**
+ * struct dmc_count_channel - structure to hold counter values from the DDR controller
+ * @access: Number of read and write accesses
+ * @clock_cycles: DDR clock cycles
+ * @read_access: number of read accesses
+ * @write_access: number of write accesses
+ */
+struct dmc_count_channel {
+ u64 access;
+ u64 clock_cycles;
+ u64 read_access;
+ u64 write_access;
+};
+
+struct dmc_count {
+ struct dmc_count_channel c[DMC_MAX_CHANNELS];
+};
+
+/*
+ * The dfi controller can monitor DDR load. It has an upper and lower threshold
+ * for the operating points. Whenever the usage leaves these bounds an event is
+ * generated to indicate the DDR frequency should be changed.
+ */
+struct rockchip_dfi {
+ struct devfreq_event_dev *edev;
+ struct devfreq_event_desc desc;
+ struct dmc_count last_event_count;
+
+ struct dmc_count last_perf_count;
+ struct dmc_count total_count;
+ seqlock_t count_seqlock; /* protects last_perf_count and total_count */
+
+ struct device *dev;
+ void __iomem *regs;
+ struct regmap *regmap_pmu;
+ struct clk *clk;
+ int usecount;
+ struct mutex mutex;
+ u32 ddr_type;
+ unsigned int channel_mask;
+ unsigned int max_channels;
+ enum cpuhp_state cpuhp_state;
+ struct hlist_node node;
+ struct pmu pmu;
+ struct hrtimer timer;
+ unsigned int cpu;
+ int active_events;
+ int burst_len;
+ int buswidth[DMC_MAX_CHANNELS];
+ int ddrmon_stride;
+ bool ddrmon_ctrl_single;
+ u32 lp5_bank_mode;
+ bool lp5_ckr; /* true if in 4:1 command-to-data clock ratio mode */
+ unsigned int count_multiplier; /* number of data clocks per count */
+};
+
+static int rockchip_dfi_ddrtype_to_ctrl(struct rockchip_dfi *dfi, u32 *ctrl)
+{
+ u32 ddrmon_ver;
+
+ switch (dfi->ddr_type) {
+ case ROCKCHIP_DDRTYPE_LPDDR2:
+ case ROCKCHIP_DDRTYPE_LPDDR3:
+ *ctrl = FIELD_PREP_WM16(DDRMON_CTRL_LPDDR23, 1) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LPDDR4, 0) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LPDDR5, 0);
+ break;
+ case ROCKCHIP_DDRTYPE_LPDDR4:
+ case ROCKCHIP_DDRTYPE_LPDDR4X:
+ *ctrl = FIELD_PREP_WM16(DDRMON_CTRL_LPDDR23, 0) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LPDDR4, 1) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LPDDR5, 0);
+ break;
+ case ROCKCHIP_DDRTYPE_LPDDR5:
+ ddrmon_ver = readl_relaxed(dfi->regs);
+ if (ddrmon_ver < 0x40) {
+ *ctrl = FIELD_PREP_WM16(DDRMON_CTRL_LPDDR23, 0) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LPDDR4, 0) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LPDDR5, 1) |
+ FIELD_PREP_WM16(DDRMON_CTRL_LP5_BANK_MODE_MASK,
+ dfi->lp5_bank_mode);
+ break;
+ }
+
+ /*
+ * As it is unknown whether the unpleasant special case
+ * behaviour used by the vendor kernel is needed for any
+ * shipping hardware, ask users to report if they have
+ * some of that hardware.
+ */
+ dev_err(&dfi->edev->dev,
+ "unsupported DDRMON version 0x%04X, please let linux-rockchip know!\n",
+ ddrmon_ver);
+ return -EOPNOTSUPP;
+ default:
+ dev_err(&dfi->edev->dev, "unsupported memory type 0x%X\n",
+ dfi->ddr_type);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int rockchip_dfi_enable(struct rockchip_dfi *dfi)
+{
+ void __iomem *dfi_regs = dfi->regs;
+ int i, ret = 0;
+ u32 ctrl;
+
+ mutex_lock(&dfi->mutex);
+
+ dfi->usecount++;
+ if (dfi->usecount > 1)
+ goto out;
+
+ ret = clk_prepare_enable(dfi->clk);
+ if (ret) {
+ dev_err(&dfi->edev->dev, "failed to enable dfi clk: %d\n", ret);
+ goto out;
+ }
+
+ ret = rockchip_dfi_ddrtype_to_ctrl(dfi, &ctrl);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < dfi->max_channels; i++) {
+
+ if (!(dfi->channel_mask & BIT(i)))
+ continue;
+
+ /* clear DDRMON_CTRL setting */
+ writel_relaxed(FIELD_PREP_WM16(DDRMON_CTRL_TIMER_CNT_EN, 0) |
+ FIELD_PREP_WM16(DDRMON_CTRL_SOFTWARE_EN, 0) |
+ FIELD_PREP_WM16(DDRMON_CTRL_HARDWARE_EN, 0),
+ dfi_regs + i * dfi->ddrmon_stride + DDRMON_CTRL);
+
+ writel_relaxed(ctrl, dfi_regs + i * dfi->ddrmon_stride +
+ DDRMON_CTRL);
+
+ /* enable count, use software mode */
+ writel_relaxed(FIELD_PREP_WM16(DDRMON_CTRL_SOFTWARE_EN, 1),
+ dfi_regs + i * dfi->ddrmon_stride + DDRMON_CTRL);
+
+ if (dfi->ddrmon_ctrl_single)
+ break;
+ }
+out:
+ mutex_unlock(&dfi->mutex);
+
+ return ret;
+}
+
+static void rockchip_dfi_disable(struct rockchip_dfi *dfi)
+{
+ void __iomem *dfi_regs = dfi->regs;
+ int i;
+
+ mutex_lock(&dfi->mutex);
+
+ dfi->usecount--;
+
+ WARN_ON_ONCE(dfi->usecount < 0);
+
+ if (dfi->usecount > 0)
+ goto out;
+
+ for (i = 0; i < dfi->max_channels; i++) {
+ if (!(dfi->channel_mask & BIT(i)))
+ continue;
+
+ writel_relaxed(FIELD_PREP_WM16(DDRMON_CTRL_SOFTWARE_EN, 0),
+ dfi_regs + i * dfi->ddrmon_stride + DDRMON_CTRL);
+
+ if (dfi->ddrmon_ctrl_single)
+ break;
+ }
+
+ clk_disable_unprepare(dfi->clk);
+out:
+ mutex_unlock(&dfi->mutex);
+}
+
+static void rockchip_dfi_read_counters(struct rockchip_dfi *dfi, struct dmc_count *res)
+{
+ u32 i;
+ void __iomem *dfi_regs = dfi->regs;
+
+ for (i = 0; i < dfi->max_channels; i++) {
+ if (!(dfi->channel_mask & BIT(i)))
+ continue;
+ res->c[i].read_access = readl_relaxed(dfi_regs +
+ DDRMON_CH0_RD_NUM + i * dfi->ddrmon_stride);
+ res->c[i].write_access = readl_relaxed(dfi_regs +
+ DDRMON_CH0_WR_NUM + i * dfi->ddrmon_stride);
+ res->c[i].access = readl_relaxed(dfi_regs +
+ DDRMON_CH0_DFI_ACCESS_NUM + i * dfi->ddrmon_stride);
+ res->c[i].clock_cycles = readl_relaxed(dfi_regs +
+ DDRMON_CH0_COUNT_NUM + i * dfi->ddrmon_stride);
+ }
+}
+
+static int rockchip_dfi_event_disable(struct devfreq_event_dev *edev)
+{
+ struct rockchip_dfi *dfi = devfreq_event_get_drvdata(edev);
+
+ rockchip_dfi_disable(dfi);
+
+ return 0;
+}
+
+static int rockchip_dfi_event_enable(struct devfreq_event_dev *edev)
+{
+ struct rockchip_dfi *dfi = devfreq_event_get_drvdata(edev);
+
+ return rockchip_dfi_enable(dfi);
+}
+
+static int rockchip_dfi_set_event(struct devfreq_event_dev *edev)
+{
+ return 0;
+}
+
+static int rockchip_dfi_get_event(struct devfreq_event_dev *edev,
+ struct devfreq_event_data *edata)
+{
+ struct rockchip_dfi *dfi = devfreq_event_get_drvdata(edev);
+ struct dmc_count count;
+ struct dmc_count *last = &dfi->last_event_count;
+ u32 access = 0, clock_cycles = 0;
+ int i;
+
+ rockchip_dfi_read_counters(dfi, &count);
+
+ /* We can only report one channel, so find the busiest one */
+ for (i = 0; i < dfi->max_channels; i++) {
+ u32 a, c;
+
+ if (!(dfi->channel_mask & BIT(i)))
+ continue;
+
+ a = count.c[i].access - last->c[i].access;
+ c = count.c[i].clock_cycles - last->c[i].clock_cycles;
+
+ if (a > access) {
+ access = a;
+ clock_cycles = c;
+ }
+ }
+
+ edata->load_count = access * 4;
+ edata->total_count = clock_cycles;
+
+ dfi->last_event_count = count;
+
+ return 0;
+}
+
+static const struct devfreq_event_ops rockchip_dfi_ops = {
+ .disable = rockchip_dfi_event_disable,
+ .enable = rockchip_dfi_event_enable,
+ .get_event = rockchip_dfi_get_event,
+ .set_event = rockchip_dfi_set_event,
+};
+
+#ifdef CONFIG_PERF_EVENTS
+
+static void rockchip_ddr_perf_counters_add(struct rockchip_dfi *dfi,
+ const struct dmc_count *now,
+ struct dmc_count *res)
+{
+ const struct dmc_count *last = &dfi->last_perf_count;
+ int i;
+
+ for (i = 0; i < dfi->max_channels; i++) {
+ res->c[i].read_access = dfi->total_count.c[i].read_access +
+ (u32)(now->c[i].read_access - last->c[i].read_access);
+ res->c[i].write_access = dfi->total_count.c[i].write_access +
+ (u32)(now->c[i].write_access - last->c[i].write_access);
+ res->c[i].access = dfi->total_count.c[i].access +
+ (u32)(now->c[i].access - last->c[i].access);
+ res->c[i].clock_cycles = dfi->total_count.c[i].clock_cycles +
+ (u32)(now->c[i].clock_cycles - last->c[i].clock_cycles);
+ }
+}
+
+static ssize_t ddr_perf_cpumask_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pmu *pmu = dev_get_drvdata(dev);
+ struct rockchip_dfi *dfi = container_of(pmu, struct rockchip_dfi, pmu);
+
+ return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(dfi->cpu)));
+}
+
+static struct device_attribute ddr_perf_cpumask_attr =
+ __ATTR(cpumask, 0444, ddr_perf_cpumask_show, NULL);
+
+static struct attribute *ddr_perf_cpumask_attrs[] = {
+ &ddr_perf_cpumask_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ddr_perf_cpumask_attr_group = {
+ .attrs = ddr_perf_cpumask_attrs,
+};
+
+PMU_EVENT_ATTR_STRING(cycles, ddr_pmu_cycles, "event="__stringify(PERF_EVENT_CYCLES))
+
+#define DFI_PMU_EVENT_ATTR(_name, _var, _str) \
+ PMU_EVENT_ATTR_STRING(_name, _var, _str); \
+ PMU_EVENT_ATTR_STRING(_name.unit, _var##_unit, "MB"); \
+ PMU_EVENT_ATTR_STRING(_name.scale, _var##_scale, "9.536743164e-07")
+
+DFI_PMU_EVENT_ATTR(read-bytes0, ddr_pmu_read_bytes0, "event="__stringify(PERF_EVENT_READ_BYTES0));
+DFI_PMU_EVENT_ATTR(write-bytes0, ddr_pmu_write_bytes0, "event="__stringify(PERF_EVENT_WRITE_BYTES0));
+
+DFI_PMU_EVENT_ATTR(read-bytes1, ddr_pmu_read_bytes1, "event="__stringify(PERF_EVENT_READ_BYTES1));
+DFI_PMU_EVENT_ATTR(write-bytes1, ddr_pmu_write_bytes1, "event="__stringify(PERF_EVENT_WRITE_BYTES1));
+
+DFI_PMU_EVENT_ATTR(read-bytes2, ddr_pmu_read_bytes2, "event="__stringify(PERF_EVENT_READ_BYTES2));
+DFI_PMU_EVENT_ATTR(write-bytes2, ddr_pmu_write_bytes2, "event="__stringify(PERF_EVENT_WRITE_BYTES2));
+
+DFI_PMU_EVENT_ATTR(read-bytes3, ddr_pmu_read_bytes3, "event="__stringify(PERF_EVENT_READ_BYTES3));
+DFI_PMU_EVENT_ATTR(write-bytes3, ddr_pmu_write_bytes3, "event="__stringify(PERF_EVENT_WRITE_BYTES3));
+
+DFI_PMU_EVENT_ATTR(read-bytes, ddr_pmu_read_bytes, "event="__stringify(PERF_EVENT_READ_BYTES));
+DFI_PMU_EVENT_ATTR(write-bytes, ddr_pmu_write_bytes, "event="__stringify(PERF_EVENT_WRITE_BYTES));
+
+DFI_PMU_EVENT_ATTR(bytes, ddr_pmu_bytes, "event="__stringify(PERF_EVENT_BYTES));
+
+#define DFI_ATTR_MB(_name) \
+ &_name.attr.attr, \
+ &_name##_unit.attr.attr, \
+ &_name##_scale.attr.attr
+
+static struct attribute *ddr_perf_events_attrs[] = {
+ &ddr_pmu_cycles.attr.attr,
+ DFI_ATTR_MB(ddr_pmu_read_bytes),
+ DFI_ATTR_MB(ddr_pmu_write_bytes),
+ DFI_ATTR_MB(ddr_pmu_read_bytes0),
+ DFI_ATTR_MB(ddr_pmu_write_bytes0),
+ DFI_ATTR_MB(ddr_pmu_read_bytes1),
+ DFI_ATTR_MB(ddr_pmu_write_bytes1),
+ DFI_ATTR_MB(ddr_pmu_read_bytes2),
+ DFI_ATTR_MB(ddr_pmu_write_bytes2),
+ DFI_ATTR_MB(ddr_pmu_read_bytes3),
+ DFI_ATTR_MB(ddr_pmu_write_bytes3),
+ DFI_ATTR_MB(ddr_pmu_bytes),
+ NULL,
+};
+
+static const struct attribute_group ddr_perf_events_attr_group = {
+ .name = "events",
+ .attrs = ddr_perf_events_attrs,
+};
+
+PMU_FORMAT_ATTR(event, "config:0-7");
+
+static struct attribute *ddr_perf_format_attrs[] = {
+ &format_attr_event.attr,
+ NULL,
+};
+
+static const struct attribute_group ddr_perf_format_attr_group = {
+ .name = "format",
+ .attrs = ddr_perf_format_attrs,
+};
+
+static const struct attribute_group *attr_groups[] = {
+ &ddr_perf_events_attr_group,
+ &ddr_perf_cpumask_attr_group,
+ &ddr_perf_format_attr_group,
+ NULL,
+};
+
+static int rockchip_ddr_perf_event_init(struct perf_event *event)
+{
+ struct rockchip_dfi *dfi = container_of(event->pmu, struct rockchip_dfi, pmu);
+
+ if (event->attr.type != event->pmu->type)
+ return -ENOENT;
+
+ if (event->attach_state & PERF_ATTACH_TASK)
+ return -EINVAL;
+
+ if (event->cpu < 0) {
+ dev_warn(dfi->dev, "Can't provide per-task data!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static u64 rockchip_ddr_perf_event_get_count(struct perf_event *event)
+{
+ struct rockchip_dfi *dfi = container_of(event->pmu, struct rockchip_dfi, pmu);
+ int blen = dfi->burst_len;
+ struct dmc_count total, now;
+ unsigned int seq;
+ u64 count = 0;
+ int i;
+
+ rockchip_dfi_read_counters(dfi, &now);
+
+ do {
+ seq = read_seqbegin(&dfi->count_seqlock);
+ rockchip_ddr_perf_counters_add(dfi, &now, &total);
+ } while (read_seqretry(&dfi->count_seqlock, seq));
+
+ switch (event->attr.config) {
+ case PERF_EVENT_CYCLES:
+ count = total.c[0].clock_cycles * dfi->count_multiplier;
+ break;
+ case PERF_EVENT_READ_BYTES:
+ for (i = 0; i < dfi->max_channels; i++)
+ count += total.c[i].read_access * blen * dfi->buswidth[i];
+ break;
+ case PERF_EVENT_WRITE_BYTES:
+ for (i = 0; i < dfi->max_channels; i++)
+ count += total.c[i].write_access * blen * dfi->buswidth[i];
+ break;
+ case PERF_EVENT_READ_BYTES0:
+ count = total.c[0].read_access * blen * dfi->buswidth[0];
+ break;
+ case PERF_EVENT_WRITE_BYTES0:
+ count = total.c[0].write_access * blen * dfi->buswidth[0];
+ break;
+ case PERF_EVENT_READ_BYTES1:
+ count = total.c[1].read_access * blen * dfi->buswidth[1];
+ break;
+ case PERF_EVENT_WRITE_BYTES1:
+ count = total.c[1].write_access * blen * dfi->buswidth[1];
+ break;
+ case PERF_EVENT_READ_BYTES2:
+ count = total.c[2].read_access * blen * dfi->buswidth[2];
+ break;
+ case PERF_EVENT_WRITE_BYTES2:
+ count = total.c[2].write_access * blen * dfi->buswidth[2];
+ break;
+ case PERF_EVENT_READ_BYTES3:
+ count = total.c[3].read_access * blen * dfi->buswidth[3];
+ break;
+ case PERF_EVENT_WRITE_BYTES3:
+ count = total.c[3].write_access * blen * dfi->buswidth[3];
+ break;
+ case PERF_EVENT_BYTES:
+ for (i = 0; i < dfi->max_channels; i++)
+ count += total.c[i].access * blen * dfi->buswidth[i];
+ break;
+ }
+
+ return count;
+}
+
+static void rockchip_ddr_perf_event_update(struct perf_event *event)
+{
+ u64 now;
+ s64 prev;
+
+ if (event->attr.config >= PERF_ACCESS_TYPE_MAX)
+ return;
+
+ now = rockchip_ddr_perf_event_get_count(event);
+ prev = local64_xchg(&event->hw.prev_count, now);
+ local64_add(now - prev, &event->count);
+}
+
+static void rockchip_ddr_perf_event_start(struct perf_event *event, int flags)
+{
+ u64 now = rockchip_ddr_perf_event_get_count(event);
+
+ local64_set(&event->hw.prev_count, now);
+}
+
+static int rockchip_ddr_perf_event_add(struct perf_event *event, int flags)
+{
+ struct rockchip_dfi *dfi = container_of(event->pmu, struct rockchip_dfi, pmu);
+
+ dfi->active_events++;
+
+ if (dfi->active_events == 1) {
+ dfi->total_count = (struct dmc_count){};
+ rockchip_dfi_read_counters(dfi, &dfi->last_perf_count);
+ hrtimer_start(&dfi->timer, ns_to_ktime(NSEC_PER_SEC), HRTIMER_MODE_REL);
+ }
+
+ if (flags & PERF_EF_START)
+ rockchip_ddr_perf_event_start(event, flags);
+
+ return 0;
+}
+
+static void rockchip_ddr_perf_event_stop(struct perf_event *event, int flags)
+{
+ rockchip_ddr_perf_event_update(event);
+}
+
+static void rockchip_ddr_perf_event_del(struct perf_event *event, int flags)
+{
+ struct rockchip_dfi *dfi = container_of(event->pmu, struct rockchip_dfi, pmu);
+
+ rockchip_ddr_perf_event_stop(event, PERF_EF_UPDATE);
+
+ dfi->active_events--;
+
+ if (dfi->active_events == 0)
+ hrtimer_cancel(&dfi->timer);
+}
+
+static enum hrtimer_restart rockchip_dfi_timer(struct hrtimer *timer)
+{
+ struct rockchip_dfi *dfi = container_of(timer, struct rockchip_dfi, timer);
+ struct dmc_count now, total;
+
+ rockchip_dfi_read_counters(dfi, &now);
+
+ write_seqlock(&dfi->count_seqlock);
+
+ rockchip_ddr_perf_counters_add(dfi, &now, &total);
+ dfi->total_count = total;
+ dfi->last_perf_count = now;
+
+ write_sequnlock(&dfi->count_seqlock);
+
+ hrtimer_forward_now(&dfi->timer, ns_to_ktime(NSEC_PER_SEC));
+
+ return HRTIMER_RESTART;
+};
+
+static int ddr_perf_offline_cpu(unsigned int cpu, struct hlist_node *node)
+{
+ struct rockchip_dfi *dfi = hlist_entry_safe(node, struct rockchip_dfi, node);
+ int target;
+
+ if (cpu != dfi->cpu)
+ return 0;
+
+ target = cpumask_any_but(cpu_online_mask, cpu);
+ if (target >= nr_cpu_ids)
+ return 0;
+
+ perf_pmu_migrate_context(&dfi->pmu, cpu, target);
+ dfi->cpu = target;
+
+ return 0;
+}
+
+static void rockchip_ddr_cpuhp_remove_state(void *data)
+{
+ struct rockchip_dfi *dfi = data;
+
+ cpuhp_remove_multi_state(dfi->cpuhp_state);
+
+ rockchip_dfi_disable(dfi);
+}
+
+static void rockchip_ddr_cpuhp_remove_instance(void *data)
+{
+ struct rockchip_dfi *dfi = data;
+
+ cpuhp_state_remove_instance_nocalls(dfi->cpuhp_state, &dfi->node);
+}
+
+static void rockchip_ddr_perf_remove(void *data)
+{
+ struct rockchip_dfi *dfi = data;
+
+ perf_pmu_unregister(&dfi->pmu);
+}
+
+static int rockchip_ddr_perf_init(struct rockchip_dfi *dfi)
+{
+ struct pmu *pmu = &dfi->pmu;
+ int ret;
+
+ seqlock_init(&dfi->count_seqlock);
+
+ pmu->module = THIS_MODULE;
+ pmu->capabilities = PERF_PMU_CAP_NO_EXCLUDE;
+ pmu->task_ctx_nr = perf_invalid_context;
+ pmu->attr_groups = attr_groups;
+ pmu->event_init = rockchip_ddr_perf_event_init;
+ pmu->add = rockchip_ddr_perf_event_add;
+ pmu->del = rockchip_ddr_perf_event_del;
+ pmu->start = rockchip_ddr_perf_event_start;
+ pmu->stop = rockchip_ddr_perf_event_stop;
+ pmu->read = rockchip_ddr_perf_event_update;
+
+ dfi->cpu = raw_smp_processor_id();
+
+ ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
+ "rockchip_ddr_perf_pmu",
+ NULL,
+ ddr_perf_offline_cpu);
+
+ if (ret < 0) {
+ dev_err(dfi->dev, "cpuhp_setup_state_multi failed: %d\n", ret);
+ return ret;
+ }
+
+ dfi->cpuhp_state = ret;
+
+ rockchip_dfi_enable(dfi);
+
+ ret = devm_add_action_or_reset(dfi->dev, rockchip_ddr_cpuhp_remove_state, dfi);
+ if (ret)
+ return ret;
+
+ ret = cpuhp_state_add_instance_nocalls(dfi->cpuhp_state, &dfi->node);
+ if (ret) {
+ dev_err(dfi->dev, "Error %d registering hotplug\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dfi->dev, rockchip_ddr_cpuhp_remove_instance, dfi);
+ if (ret)
+ return ret;
+
+ hrtimer_setup(&dfi->timer, rockchip_dfi_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+ switch (dfi->ddr_type) {
+ case ROCKCHIP_DDRTYPE_LPDDR2:
+ case ROCKCHIP_DDRTYPE_LPDDR3:
+ dfi->burst_len = 8;
+ break;
+ case ROCKCHIP_DDRTYPE_LPDDR4:
+ case ROCKCHIP_DDRTYPE_LPDDR4X:
+ case ROCKCHIP_DDRTYPE_LPDDR5:
+ dfi->burst_len = 16;
+ break;
+ }
+
+ if (!dfi->count_multiplier)
+ dfi->count_multiplier = 1;
+
+ ret = perf_pmu_register(pmu, "rockchip_ddr", -1);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dfi->dev, rockchip_ddr_perf_remove, dfi);
+}
+#else
+static int rockchip_ddr_perf_init(struct rockchip_dfi *dfi)
+{
+ return 0;
+}
+#endif
+
+static int rk3399_dfi_init(struct rockchip_dfi *dfi)
+{
+ struct regmap *regmap_pmu = dfi->regmap_pmu;
+ u32 val;
+
+ dfi->clk = devm_clk_get(dfi->dev, "pclk_ddr_mon");
+ if (IS_ERR(dfi->clk))
+ return dev_err_probe(dfi->dev, PTR_ERR(dfi->clk),
+ "Cannot get the clk pclk_ddr_mon\n");
+
+ /* get ddr type */
+ regmap_read(regmap_pmu, RK3399_PMUGRF_OS_REG2, &val);
+ dfi->ddr_type = FIELD_GET(RK3399_PMUGRF_OS_REG2_DDRTYPE, val);
+
+ dfi->channel_mask = GENMASK(1, 0);
+ dfi->max_channels = 2;
+
+ dfi->buswidth[0] = FIELD_GET(RK3399_PMUGRF_OS_REG2_BW_CH0, val) == 0 ? 4 : 2;
+ dfi->buswidth[1] = FIELD_GET(RK3399_PMUGRF_OS_REG2_BW_CH1, val) == 0 ? 4 : 2;
+
+ dfi->ddrmon_stride = 0x14;
+ dfi->ddrmon_ctrl_single = true;
+
+ return 0;
+};
+
+static int rk3568_dfi_init(struct rockchip_dfi *dfi)
+{
+ struct regmap *regmap_pmu = dfi->regmap_pmu;
+ u32 reg2, reg3;
+
+ regmap_read(regmap_pmu, RK3568_PMUGRF_OS_REG2, &reg2);
+ regmap_read(regmap_pmu, RK3568_PMUGRF_OS_REG3, &reg3);
+
+ /* lower 3 bits of the DDR type */
+ dfi->ddr_type = FIELD_GET(RK3568_PMUGRF_OS_REG2_DRAMTYPE_INFO, reg2);
+
+ /*
+ * For version three and higher the upper two bits of the DDR type are
+ * in RK3568_PMUGRF_OS_REG3
+ */
+ if (FIELD_GET(RK3568_PMUGRF_OS_REG3_SYSREG_VERSION, reg3) >= 0x3)
+ dfi->ddr_type |= FIELD_GET(RK3568_PMUGRF_OS_REG3_DRAMTYPE_INFO_V3, reg3) << 3;
+
+ dfi->channel_mask = BIT(0);
+ dfi->max_channels = 1;
+
+ dfi->buswidth[0] = FIELD_GET(RK3568_PMUGRF_OS_REG2_BW_CH0, reg2) == 0 ? 4 : 2;
+
+ dfi->ddrmon_stride = 0x0; /* not relevant, we only have a single channel on this SoC */
+ dfi->ddrmon_ctrl_single = true;
+
+ return 0;
+};
+
+static int rk3588_dfi_init(struct rockchip_dfi *dfi)
+{
+ struct regmap *regmap_pmu = dfi->regmap_pmu;
+ u32 reg2, reg3, reg4, reg6;
+
+ regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG2, &reg2);
+ regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG3, &reg3);
+ regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG4, &reg4);
+
+ /* lower 3 bits of the DDR type */
+ dfi->ddr_type = FIELD_GET(RK3588_PMUGRF_OS_REG2_DRAMTYPE_INFO, reg2);
+
+ /*
+ * For version three and higher the upper two bits of the DDR type are
+ * in RK3588_PMUGRF_OS_REG3
+ */
+ if (FIELD_GET(RK3588_PMUGRF_OS_REG3_SYSREG_VERSION, reg3) >= 0x3)
+ dfi->ddr_type |= FIELD_GET(RK3588_PMUGRF_OS_REG3_DRAMTYPE_INFO_V3, reg3) << 3;
+
+ dfi->buswidth[0] = FIELD_GET(RK3588_PMUGRF_OS_REG2_BW_CH0, reg2) == 0 ? 4 : 2;
+ dfi->buswidth[1] = FIELD_GET(RK3588_PMUGRF_OS_REG2_BW_CH1, reg2) == 0 ? 4 : 2;
+ dfi->buswidth[2] = FIELD_GET(RK3568_PMUGRF_OS_REG2_BW_CH0, reg4) == 0 ? 4 : 2;
+ dfi->buswidth[3] = FIELD_GET(RK3588_PMUGRF_OS_REG2_BW_CH1, reg4) == 0 ? 4 : 2;
+ dfi->channel_mask = FIELD_GET(RK3588_PMUGRF_OS_REG2_CH_INFO, reg2) |
+ FIELD_GET(RK3588_PMUGRF_OS_REG2_CH_INFO, reg4) << 2;
+ dfi->max_channels = 4;
+
+ dfi->ddrmon_stride = 0x4000;
+ dfi->count_multiplier = 2;
+
+ if (dfi->ddr_type == ROCKCHIP_DDRTYPE_LPDDR5) {
+ regmap_read(regmap_pmu, RK3588_PMUGRF_OS_REG6, &reg6);
+ dfi->lp5_bank_mode = FIELD_GET(RK3588_PMUGRF_OS_REG6_LP5_BANK_MODE, reg6) << 7;
+ dfi->lp5_ckr = FIELD_GET(RK3588_PMUGRF_OS_REG6_LP5_CKR, reg6);
+ if (dfi->lp5_ckr)
+ dfi->count_multiplier *= 2;
+ }
+
+ return 0;
+};
+
+static const struct of_device_id rockchip_dfi_id_match[] = {
+ { .compatible = "rockchip,rk3399-dfi", .data = rk3399_dfi_init },
+ { .compatible = "rockchip,rk3568-dfi", .data = rk3568_dfi_init },
+ { .compatible = "rockchip,rk3588-dfi", .data = rk3588_dfi_init },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_dfi_id_match);
+
+static int rockchip_dfi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_dfi *dfi;
+ struct devfreq_event_desc *desc;
+ struct device_node *np = pdev->dev.of_node, *node;
+ int (*soc_init)(struct rockchip_dfi *dfi);
+ int ret;
+
+ soc_init = of_device_get_match_data(&pdev->dev);
+ if (!soc_init)
+ return -EINVAL;
+
+ dfi = devm_kzalloc(dev, sizeof(*dfi), GFP_KERNEL);
+ if (!dfi)
+ return -ENOMEM;
+
+ dfi->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dfi->regs))
+ return PTR_ERR(dfi->regs);
+
+ node = of_parse_phandle(np, "rockchip,pmu", 0);
+ if (!node)
+ return dev_err_probe(&pdev->dev, -ENODEV, "Can't find pmu_grf registers\n");
+
+ dfi->regmap_pmu = syscon_node_to_regmap(node);
+ of_node_put(node);
+ if (IS_ERR(dfi->regmap_pmu))
+ return PTR_ERR(dfi->regmap_pmu);
+
+ dfi->dev = dev;
+ mutex_init(&dfi->mutex);
+
+ desc = &dfi->desc;
+ desc->ops = &rockchip_dfi_ops;
+ desc->driver_data = dfi;
+ desc->name = np->name;
+
+ ret = soc_init(dfi);
+ if (ret)
+ return ret;
+
+ dfi->edev = devm_devfreq_event_add_edev(&pdev->dev, desc);
+ if (IS_ERR(dfi->edev)) {
+ dev_err(&pdev->dev,
+ "failed to add devfreq-event device\n");
+ return PTR_ERR(dfi->edev);
+ }
+
+ ret = rockchip_ddr_perf_init(dfi);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, dfi);
+
+ return 0;
+}
+
+static struct platform_driver rockchip_dfi_driver = {
+ .probe = rockchip_dfi_probe,
+ .driver = {
+ .name = "rockchip-dfi",
+ .of_match_table = rockchip_dfi_id_match,
+ .suppress_bind_attrs = true,
+ },
+};
+module_platform_driver(rockchip_dfi_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip DFI driver");