162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2017 NXP
462306a36Sopenharmony_ci * Copyright 2016 Freescale Semiconductor, Inc.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/bitfield.h>
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/interrupt.h>
1062306a36Sopenharmony_ci#include <linux/io.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/of_irq.h>
1462306a36Sopenharmony_ci#include <linux/perf_event.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define COUNTER_CNTL		0x0
1962306a36Sopenharmony_ci#define COUNTER_READ		0x20
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define COUNTER_DPCR1		0x30
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define CNTL_OVER		0x1
2462306a36Sopenharmony_ci#define CNTL_CLEAR		0x2
2562306a36Sopenharmony_ci#define CNTL_EN			0x4
2662306a36Sopenharmony_ci#define CNTL_EN_MASK		0xFFFFFFFB
2762306a36Sopenharmony_ci#define CNTL_CLEAR_MASK		0xFFFFFFFD
2862306a36Sopenharmony_ci#define CNTL_OVER_MASK		0xFFFFFFFE
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define CNTL_CP_SHIFT		16
3162306a36Sopenharmony_ci#define CNTL_CP_MASK		(0xFF << CNTL_CP_SHIFT)
3262306a36Sopenharmony_ci#define CNTL_CSV_SHIFT		24
3362306a36Sopenharmony_ci#define CNTL_CSV_MASK		(0xFFU << CNTL_CSV_SHIFT)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define EVENT_CYCLES_ID		0
3662306a36Sopenharmony_ci#define EVENT_CYCLES_COUNTER	0
3762306a36Sopenharmony_ci#define NUM_COUNTERS		4
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* For removing bias if cycle counter CNTL.CP is set to 0xf0 */
4062306a36Sopenharmony_ci#define CYCLES_COUNTER_MASK	0x0FFFFFFF
4162306a36Sopenharmony_ci#define AXI_MASKING_REVERT	0xffff0000	/* AXI_MASKING(MSB 16bits) + AXI_ID(LSB 16bits) */
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define to_ddr_pmu(p)		container_of(p, struct ddr_pmu, pmu)
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define DDR_PERF_DEV_NAME	"imx8_ddr"
4662306a36Sopenharmony_ci#define DDR_CPUHP_CB_NAME	DDR_PERF_DEV_NAME "_perf_pmu"
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic DEFINE_IDA(ddr_ida);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/* DDR Perf hardware feature */
5162306a36Sopenharmony_ci#define DDR_CAP_AXI_ID_FILTER			0x1     /* support AXI ID filter */
5262306a36Sopenharmony_ci#define DDR_CAP_AXI_ID_FILTER_ENHANCED		0x3     /* support enhanced AXI ID filter */
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistruct fsl_ddr_devtype_data {
5562306a36Sopenharmony_ci	unsigned int quirks;    /* quirks needed for different DDR Perf core */
5662306a36Sopenharmony_ci	const char *identifier;	/* system PMU identifier for userspace */
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic const struct fsl_ddr_devtype_data imx8_devtype_data;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic const struct fsl_ddr_devtype_data imx8m_devtype_data = {
6262306a36Sopenharmony_ci	.quirks = DDR_CAP_AXI_ID_FILTER,
6362306a36Sopenharmony_ci};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic const struct fsl_ddr_devtype_data imx8mq_devtype_data = {
6662306a36Sopenharmony_ci	.quirks = DDR_CAP_AXI_ID_FILTER,
6762306a36Sopenharmony_ci	.identifier = "i.MX8MQ",
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic const struct fsl_ddr_devtype_data imx8mm_devtype_data = {
7162306a36Sopenharmony_ci	.quirks = DDR_CAP_AXI_ID_FILTER,
7262306a36Sopenharmony_ci	.identifier = "i.MX8MM",
7362306a36Sopenharmony_ci};
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic const struct fsl_ddr_devtype_data imx8mn_devtype_data = {
7662306a36Sopenharmony_ci	.quirks = DDR_CAP_AXI_ID_FILTER,
7762306a36Sopenharmony_ci	.identifier = "i.MX8MN",
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic const struct fsl_ddr_devtype_data imx8mp_devtype_data = {
8162306a36Sopenharmony_ci	.quirks = DDR_CAP_AXI_ID_FILTER_ENHANCED,
8262306a36Sopenharmony_ci	.identifier = "i.MX8MP",
8362306a36Sopenharmony_ci};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic const struct of_device_id imx_ddr_pmu_dt_ids[] = {
8662306a36Sopenharmony_ci	{ .compatible = "fsl,imx8-ddr-pmu", .data = &imx8_devtype_data},
8762306a36Sopenharmony_ci	{ .compatible = "fsl,imx8m-ddr-pmu", .data = &imx8m_devtype_data},
8862306a36Sopenharmony_ci	{ .compatible = "fsl,imx8mq-ddr-pmu", .data = &imx8mq_devtype_data},
8962306a36Sopenharmony_ci	{ .compatible = "fsl,imx8mm-ddr-pmu", .data = &imx8mm_devtype_data},
9062306a36Sopenharmony_ci	{ .compatible = "fsl,imx8mn-ddr-pmu", .data = &imx8mn_devtype_data},
9162306a36Sopenharmony_ci	{ .compatible = "fsl,imx8mp-ddr-pmu", .data = &imx8mp_devtype_data},
9262306a36Sopenharmony_ci	{ /* sentinel */ }
9362306a36Sopenharmony_ci};
9462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx_ddr_pmu_dt_ids);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistruct ddr_pmu {
9762306a36Sopenharmony_ci	struct pmu pmu;
9862306a36Sopenharmony_ci	void __iomem *base;
9962306a36Sopenharmony_ci	unsigned int cpu;
10062306a36Sopenharmony_ci	struct	hlist_node node;
10162306a36Sopenharmony_ci	struct	device *dev;
10262306a36Sopenharmony_ci	struct perf_event *events[NUM_COUNTERS];
10362306a36Sopenharmony_ci	enum cpuhp_state cpuhp_state;
10462306a36Sopenharmony_ci	const struct fsl_ddr_devtype_data *devtype_data;
10562306a36Sopenharmony_ci	int irq;
10662306a36Sopenharmony_ci	int id;
10762306a36Sopenharmony_ci	int active_counter;
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic ssize_t ddr_perf_identifier_show(struct device *dev,
11162306a36Sopenharmony_ci					struct device_attribute *attr,
11262306a36Sopenharmony_ci					char *page)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct ddr_pmu *pmu = dev_get_drvdata(dev);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	return sysfs_emit(page, "%s\n", pmu->devtype_data->identifier);
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic umode_t ddr_perf_identifier_attr_visible(struct kobject *kobj,
12062306a36Sopenharmony_ci						struct attribute *attr,
12162306a36Sopenharmony_ci						int n)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
12462306a36Sopenharmony_ci	struct ddr_pmu *pmu = dev_get_drvdata(dev);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (!pmu->devtype_data->identifier)
12762306a36Sopenharmony_ci		return 0;
12862306a36Sopenharmony_ci	return attr->mode;
12962306a36Sopenharmony_ci};
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic struct device_attribute ddr_perf_identifier_attr =
13262306a36Sopenharmony_ci	__ATTR(identifier, 0444, ddr_perf_identifier_show, NULL);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic struct attribute *ddr_perf_identifier_attrs[] = {
13562306a36Sopenharmony_ci	&ddr_perf_identifier_attr.attr,
13662306a36Sopenharmony_ci	NULL,
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic const struct attribute_group ddr_perf_identifier_attr_group = {
14062306a36Sopenharmony_ci	.attrs = ddr_perf_identifier_attrs,
14162306a36Sopenharmony_ci	.is_visible = ddr_perf_identifier_attr_visible,
14262306a36Sopenharmony_ci};
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cienum ddr_perf_filter_capabilities {
14562306a36Sopenharmony_ci	PERF_CAP_AXI_ID_FILTER = 0,
14662306a36Sopenharmony_ci	PERF_CAP_AXI_ID_FILTER_ENHANCED,
14762306a36Sopenharmony_ci	PERF_CAP_AXI_ID_FEAT_MAX,
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic u32 ddr_perf_filter_cap_get(struct ddr_pmu *pmu, int cap)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	u32 quirks = pmu->devtype_data->quirks;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	switch (cap) {
15562306a36Sopenharmony_ci	case PERF_CAP_AXI_ID_FILTER:
15662306a36Sopenharmony_ci		return !!(quirks & DDR_CAP_AXI_ID_FILTER);
15762306a36Sopenharmony_ci	case PERF_CAP_AXI_ID_FILTER_ENHANCED:
15862306a36Sopenharmony_ci		quirks &= DDR_CAP_AXI_ID_FILTER_ENHANCED;
15962306a36Sopenharmony_ci		return quirks == DDR_CAP_AXI_ID_FILTER_ENHANCED;
16062306a36Sopenharmony_ci	default:
16162306a36Sopenharmony_ci		WARN(1, "unknown filter cap %d\n", cap);
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return 0;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic ssize_t ddr_perf_filter_cap_show(struct device *dev,
16862306a36Sopenharmony_ci					struct device_attribute *attr,
16962306a36Sopenharmony_ci					char *buf)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct ddr_pmu *pmu = dev_get_drvdata(dev);
17262306a36Sopenharmony_ci	struct dev_ext_attribute *ea =
17362306a36Sopenharmony_ci		container_of(attr, struct dev_ext_attribute, attr);
17462306a36Sopenharmony_ci	int cap = (long)ea->var;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", ddr_perf_filter_cap_get(pmu, cap));
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci#define PERF_EXT_ATTR_ENTRY(_name, _func, _var)				\
18062306a36Sopenharmony_ci	(&((struct dev_ext_attribute) {					\
18162306a36Sopenharmony_ci		__ATTR(_name, 0444, _func, NULL), (void *)_var		\
18262306a36Sopenharmony_ci	}).attr.attr)
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci#define PERF_FILTER_EXT_ATTR_ENTRY(_name, _var)				\
18562306a36Sopenharmony_ci	PERF_EXT_ATTR_ENTRY(_name, ddr_perf_filter_cap_show, _var)
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic struct attribute *ddr_perf_filter_cap_attr[] = {
18862306a36Sopenharmony_ci	PERF_FILTER_EXT_ATTR_ENTRY(filter, PERF_CAP_AXI_ID_FILTER),
18962306a36Sopenharmony_ci	PERF_FILTER_EXT_ATTR_ENTRY(enhanced_filter, PERF_CAP_AXI_ID_FILTER_ENHANCED),
19062306a36Sopenharmony_ci	NULL,
19162306a36Sopenharmony_ci};
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic const struct attribute_group ddr_perf_filter_cap_attr_group = {
19462306a36Sopenharmony_ci	.name = "caps",
19562306a36Sopenharmony_ci	.attrs = ddr_perf_filter_cap_attr,
19662306a36Sopenharmony_ci};
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic ssize_t ddr_perf_cpumask_show(struct device *dev,
19962306a36Sopenharmony_ci				struct device_attribute *attr, char *buf)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct ddr_pmu *pmu = dev_get_drvdata(dev);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu));
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic struct device_attribute ddr_perf_cpumask_attr =
20762306a36Sopenharmony_ci	__ATTR(cpumask, 0444, ddr_perf_cpumask_show, NULL);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic struct attribute *ddr_perf_cpumask_attrs[] = {
21062306a36Sopenharmony_ci	&ddr_perf_cpumask_attr.attr,
21162306a36Sopenharmony_ci	NULL,
21262306a36Sopenharmony_ci};
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic const struct attribute_group ddr_perf_cpumask_attr_group = {
21562306a36Sopenharmony_ci	.attrs = ddr_perf_cpumask_attrs,
21662306a36Sopenharmony_ci};
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic ssize_t
21962306a36Sopenharmony_ciddr_pmu_event_show(struct device *dev, struct device_attribute *attr,
22062306a36Sopenharmony_ci		   char *page)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct perf_pmu_events_attr *pmu_attr;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
22562306a36Sopenharmony_ci	return sysfs_emit(page, "event=0x%02llx\n", pmu_attr->id);
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci#define IMX8_DDR_PMU_EVENT_ATTR(_name, _id)		\
22962306a36Sopenharmony_ci	PMU_EVENT_ATTR_ID(_name, ddr_pmu_event_show, _id)
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic struct attribute *ddr_perf_events_attrs[] = {
23262306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(cycles, EVENT_CYCLES_ID),
23362306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(selfresh, 0x01),
23462306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-accesses, 0x04),
23562306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(write-accesses, 0x05),
23662306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-queue-depth, 0x08),
23762306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(write-queue-depth, 0x09),
23862306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(lp-read-credit-cnt, 0x10),
23962306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(hp-read-credit-cnt, 0x11),
24062306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(write-credit-cnt, 0x12),
24162306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-command, 0x20),
24262306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(write-command, 0x21),
24362306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-modify-write-command, 0x22),
24462306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(hp-read, 0x23),
24562306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(hp-req-nocredit, 0x24),
24662306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(hp-xact-credit, 0x25),
24762306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(lp-req-nocredit, 0x26),
24862306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(lp-xact-credit, 0x27),
24962306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(wr-xact-credit, 0x29),
25062306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-cycles, 0x2a),
25162306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(write-cycles, 0x2b),
25262306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-write-transition, 0x30),
25362306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(precharge, 0x31),
25462306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(activate, 0x32),
25562306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(load-mode, 0x33),
25662306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(perf-mwr, 0x34),
25762306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read, 0x35),
25862306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(read-activate, 0x36),
25962306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(refresh, 0x37),
26062306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(write, 0x38),
26162306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(raw-hazard, 0x39),
26262306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(axid-read, 0x41),
26362306a36Sopenharmony_ci	IMX8_DDR_PMU_EVENT_ATTR(axid-write, 0x42),
26462306a36Sopenharmony_ci	NULL,
26562306a36Sopenharmony_ci};
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic const struct attribute_group ddr_perf_events_attr_group = {
26862306a36Sopenharmony_ci	.name = "events",
26962306a36Sopenharmony_ci	.attrs = ddr_perf_events_attrs,
27062306a36Sopenharmony_ci};
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ciPMU_FORMAT_ATTR(event, "config:0-7");
27362306a36Sopenharmony_ciPMU_FORMAT_ATTR(axi_id, "config1:0-15");
27462306a36Sopenharmony_ciPMU_FORMAT_ATTR(axi_mask, "config1:16-31");
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic struct attribute *ddr_perf_format_attrs[] = {
27762306a36Sopenharmony_ci	&format_attr_event.attr,
27862306a36Sopenharmony_ci	&format_attr_axi_id.attr,
27962306a36Sopenharmony_ci	&format_attr_axi_mask.attr,
28062306a36Sopenharmony_ci	NULL,
28162306a36Sopenharmony_ci};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic const struct attribute_group ddr_perf_format_attr_group = {
28462306a36Sopenharmony_ci	.name = "format",
28562306a36Sopenharmony_ci	.attrs = ddr_perf_format_attrs,
28662306a36Sopenharmony_ci};
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic const struct attribute_group *attr_groups[] = {
28962306a36Sopenharmony_ci	&ddr_perf_events_attr_group,
29062306a36Sopenharmony_ci	&ddr_perf_format_attr_group,
29162306a36Sopenharmony_ci	&ddr_perf_cpumask_attr_group,
29262306a36Sopenharmony_ci	&ddr_perf_filter_cap_attr_group,
29362306a36Sopenharmony_ci	&ddr_perf_identifier_attr_group,
29462306a36Sopenharmony_ci	NULL,
29562306a36Sopenharmony_ci};
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic bool ddr_perf_is_filtered(struct perf_event *event)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	return event->attr.config == 0x41 || event->attr.config == 0x42;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic u32 ddr_perf_filter_val(struct perf_event *event)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	return event->attr.config1;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic bool ddr_perf_filters_compatible(struct perf_event *a,
30862306a36Sopenharmony_ci					struct perf_event *b)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	if (!ddr_perf_is_filtered(a))
31162306a36Sopenharmony_ci		return true;
31262306a36Sopenharmony_ci	if (!ddr_perf_is_filtered(b))
31362306a36Sopenharmony_ci		return true;
31462306a36Sopenharmony_ci	return ddr_perf_filter_val(a) == ddr_perf_filter_val(b);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic bool ddr_perf_is_enhanced_filtered(struct perf_event *event)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	unsigned int filt;
32062306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	filt = pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER_ENHANCED;
32362306a36Sopenharmony_ci	return (filt == DDR_CAP_AXI_ID_FILTER_ENHANCED) &&
32462306a36Sopenharmony_ci		ddr_perf_is_filtered(event);
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic u32 ddr_perf_alloc_counter(struct ddr_pmu *pmu, int event)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	int i;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	/*
33262306a36Sopenharmony_ci	 * Always map cycle event to counter 0
33362306a36Sopenharmony_ci	 * Cycles counter is dedicated for cycle event
33462306a36Sopenharmony_ci	 * can't used for the other events
33562306a36Sopenharmony_ci	 */
33662306a36Sopenharmony_ci	if (event == EVENT_CYCLES_ID) {
33762306a36Sopenharmony_ci		if (pmu->events[EVENT_CYCLES_COUNTER] == NULL)
33862306a36Sopenharmony_ci			return EVENT_CYCLES_COUNTER;
33962306a36Sopenharmony_ci		else
34062306a36Sopenharmony_ci			return -ENOENT;
34162306a36Sopenharmony_ci	}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	for (i = 1; i < NUM_COUNTERS; i++) {
34462306a36Sopenharmony_ci		if (pmu->events[i] == NULL)
34562306a36Sopenharmony_ci			return i;
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	return -ENOENT;
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic void ddr_perf_free_counter(struct ddr_pmu *pmu, int counter)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	pmu->events[counter] = NULL;
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_cistatic u32 ddr_perf_read_counter(struct ddr_pmu *pmu, int counter)
35762306a36Sopenharmony_ci{
35862306a36Sopenharmony_ci	struct perf_event *event = pmu->events[counter];
35962306a36Sopenharmony_ci	void __iomem *base = pmu->base;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/*
36262306a36Sopenharmony_ci	 * return bytes instead of bursts from ddr transaction for
36362306a36Sopenharmony_ci	 * axid-read and axid-write event if PMU core supports enhanced
36462306a36Sopenharmony_ci	 * filter.
36562306a36Sopenharmony_ci	 */
36662306a36Sopenharmony_ci	base += ddr_perf_is_enhanced_filtered(event) ? COUNTER_DPCR1 :
36762306a36Sopenharmony_ci						       COUNTER_READ;
36862306a36Sopenharmony_ci	return readl_relaxed(base + counter * 4);
36962306a36Sopenharmony_ci}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_cistatic int ddr_perf_event_init(struct perf_event *event)
37262306a36Sopenharmony_ci{
37362306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
37462306a36Sopenharmony_ci	struct hw_perf_event *hwc = &event->hw;
37562306a36Sopenharmony_ci	struct perf_event *sibling;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	if (event->attr.type != event->pmu->type)
37862306a36Sopenharmony_ci		return -ENOENT;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK)
38162306a36Sopenharmony_ci		return -EOPNOTSUPP;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	if (event->cpu < 0) {
38462306a36Sopenharmony_ci		dev_warn(pmu->dev, "Can't provide per-task data!\n");
38562306a36Sopenharmony_ci		return -EOPNOTSUPP;
38662306a36Sopenharmony_ci	}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	/*
38962306a36Sopenharmony_ci	 * We must NOT create groups containing mixed PMUs, although software
39062306a36Sopenharmony_ci	 * events are acceptable (for example to create a CCN group
39162306a36Sopenharmony_ci	 * periodically read when a hrtimer aka cpu-clock leader triggers).
39262306a36Sopenharmony_ci	 */
39362306a36Sopenharmony_ci	if (event->group_leader->pmu != event->pmu &&
39462306a36Sopenharmony_ci			!is_software_event(event->group_leader))
39562306a36Sopenharmony_ci		return -EINVAL;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER) {
39862306a36Sopenharmony_ci		if (!ddr_perf_filters_compatible(event, event->group_leader))
39962306a36Sopenharmony_ci			return -EINVAL;
40062306a36Sopenharmony_ci		for_each_sibling_event(sibling, event->group_leader) {
40162306a36Sopenharmony_ci			if (!ddr_perf_filters_compatible(event, sibling))
40262306a36Sopenharmony_ci				return -EINVAL;
40362306a36Sopenharmony_ci		}
40462306a36Sopenharmony_ci	}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	for_each_sibling_event(sibling, event->group_leader) {
40762306a36Sopenharmony_ci		if (sibling->pmu != event->pmu &&
40862306a36Sopenharmony_ci				!is_software_event(sibling))
40962306a36Sopenharmony_ci			return -EINVAL;
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	event->cpu = pmu->cpu;
41362306a36Sopenharmony_ci	hwc->idx = -1;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	return 0;
41662306a36Sopenharmony_ci}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_cistatic void ddr_perf_counter_enable(struct ddr_pmu *pmu, int config,
41962306a36Sopenharmony_ci				  int counter, bool enable)
42062306a36Sopenharmony_ci{
42162306a36Sopenharmony_ci	u8 reg = counter * 4 + COUNTER_CNTL;
42262306a36Sopenharmony_ci	int val;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	if (enable) {
42562306a36Sopenharmony_ci		/*
42662306a36Sopenharmony_ci		 * cycle counter is special which should firstly write 0 then
42762306a36Sopenharmony_ci		 * write 1 into CLEAR bit to clear it. Other counters only
42862306a36Sopenharmony_ci		 * need write 0 into CLEAR bit and it turns out to be 1 by
42962306a36Sopenharmony_ci		 * hardware. Below enable flow is harmless for all counters.
43062306a36Sopenharmony_ci		 */
43162306a36Sopenharmony_ci		writel(0, pmu->base + reg);
43262306a36Sopenharmony_ci		val = CNTL_EN | CNTL_CLEAR;
43362306a36Sopenharmony_ci		val |= FIELD_PREP(CNTL_CSV_MASK, config);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci		/*
43662306a36Sopenharmony_ci		 * On i.MX8MP we need to bias the cycle counter to overflow more often.
43762306a36Sopenharmony_ci		 * We do this by initializing bits [23:16] of the counter value via the
43862306a36Sopenharmony_ci		 * COUNTER_CTRL Counter Parameter (CP) field.
43962306a36Sopenharmony_ci		 */
44062306a36Sopenharmony_ci		if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER_ENHANCED) {
44162306a36Sopenharmony_ci			if (counter == EVENT_CYCLES_COUNTER)
44262306a36Sopenharmony_ci				val |= FIELD_PREP(CNTL_CP_MASK, 0xf0);
44362306a36Sopenharmony_ci		}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci		writel(val, pmu->base + reg);
44662306a36Sopenharmony_ci	} else {
44762306a36Sopenharmony_ci		/* Disable counter */
44862306a36Sopenharmony_ci		val = readl_relaxed(pmu->base + reg) & CNTL_EN_MASK;
44962306a36Sopenharmony_ci		writel(val, pmu->base + reg);
45062306a36Sopenharmony_ci	}
45162306a36Sopenharmony_ci}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_cistatic bool ddr_perf_counter_overflow(struct ddr_pmu *pmu, int counter)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	int val;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	val = readl_relaxed(pmu->base + counter * 4 + COUNTER_CNTL);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	return val & CNTL_OVER;
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_cistatic void ddr_perf_counter_clear(struct ddr_pmu *pmu, int counter)
46362306a36Sopenharmony_ci{
46462306a36Sopenharmony_ci	u8 reg = counter * 4 + COUNTER_CNTL;
46562306a36Sopenharmony_ci	int val;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	val = readl_relaxed(pmu->base + reg);
46862306a36Sopenharmony_ci	val &= ~CNTL_CLEAR;
46962306a36Sopenharmony_ci	writel(val, pmu->base + reg);
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	val |= CNTL_CLEAR;
47262306a36Sopenharmony_ci	writel(val, pmu->base + reg);
47362306a36Sopenharmony_ci}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic void ddr_perf_event_update(struct perf_event *event)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
47862306a36Sopenharmony_ci	struct hw_perf_event *hwc = &event->hw;
47962306a36Sopenharmony_ci	u64 new_raw_count;
48062306a36Sopenharmony_ci	int counter = hwc->idx;
48162306a36Sopenharmony_ci	int ret;
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	new_raw_count = ddr_perf_read_counter(pmu, counter);
48462306a36Sopenharmony_ci	/* Remove the bias applied in ddr_perf_counter_enable(). */
48562306a36Sopenharmony_ci	if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER_ENHANCED) {
48662306a36Sopenharmony_ci		if (counter == EVENT_CYCLES_COUNTER)
48762306a36Sopenharmony_ci			new_raw_count &= CYCLES_COUNTER_MASK;
48862306a36Sopenharmony_ci	}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	local64_add(new_raw_count, &event->count);
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	/*
49362306a36Sopenharmony_ci	 * For legacy SoCs: event counter continue counting when overflow,
49462306a36Sopenharmony_ci	 *                  no need to clear the counter.
49562306a36Sopenharmony_ci	 * For new SoCs: event counter stop counting when overflow, need
49662306a36Sopenharmony_ci	 *               clear counter to let it count again.
49762306a36Sopenharmony_ci	 */
49862306a36Sopenharmony_ci	if (counter != EVENT_CYCLES_COUNTER) {
49962306a36Sopenharmony_ci		ret = ddr_perf_counter_overflow(pmu, counter);
50062306a36Sopenharmony_ci		if (ret)
50162306a36Sopenharmony_ci			dev_warn_ratelimited(pmu->dev,  "events lost due to counter overflow (config 0x%llx)\n",
50262306a36Sopenharmony_ci					     event->attr.config);
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	/* clear counter every time for both cycle counter and event counter */
50662306a36Sopenharmony_ci	ddr_perf_counter_clear(pmu, counter);
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cistatic void ddr_perf_event_start(struct perf_event *event, int flags)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
51262306a36Sopenharmony_ci	struct hw_perf_event *hwc = &event->hw;
51362306a36Sopenharmony_ci	int counter = hwc->idx;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	local64_set(&hwc->prev_count, 0);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	ddr_perf_counter_enable(pmu, event->attr.config, counter, true);
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	if (!pmu->active_counter++)
52062306a36Sopenharmony_ci		ddr_perf_counter_enable(pmu, EVENT_CYCLES_ID,
52162306a36Sopenharmony_ci			EVENT_CYCLES_COUNTER, true);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	hwc->state = 0;
52462306a36Sopenharmony_ci}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_cistatic int ddr_perf_event_add(struct perf_event *event, int flags)
52762306a36Sopenharmony_ci{
52862306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
52962306a36Sopenharmony_ci	struct hw_perf_event *hwc = &event->hw;
53062306a36Sopenharmony_ci	int counter;
53162306a36Sopenharmony_ci	int cfg = event->attr.config;
53262306a36Sopenharmony_ci	int cfg1 = event->attr.config1;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	if (pmu->devtype_data->quirks & DDR_CAP_AXI_ID_FILTER) {
53562306a36Sopenharmony_ci		int i;
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci		for (i = 1; i < NUM_COUNTERS; i++) {
53862306a36Sopenharmony_ci			if (pmu->events[i] &&
53962306a36Sopenharmony_ci			    !ddr_perf_filters_compatible(event, pmu->events[i]))
54062306a36Sopenharmony_ci				return -EINVAL;
54162306a36Sopenharmony_ci		}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci		if (ddr_perf_is_filtered(event)) {
54462306a36Sopenharmony_ci			/* revert axi id masking(axi_mask) value */
54562306a36Sopenharmony_ci			cfg1 ^= AXI_MASKING_REVERT;
54662306a36Sopenharmony_ci			writel(cfg1, pmu->base + COUNTER_DPCR1);
54762306a36Sopenharmony_ci		}
54862306a36Sopenharmony_ci	}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	counter = ddr_perf_alloc_counter(pmu, cfg);
55162306a36Sopenharmony_ci	if (counter < 0) {
55262306a36Sopenharmony_ci		dev_dbg(pmu->dev, "There are not enough counters\n");
55362306a36Sopenharmony_ci		return -EOPNOTSUPP;
55462306a36Sopenharmony_ci	}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	pmu->events[counter] = event;
55762306a36Sopenharmony_ci	hwc->idx = counter;
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	hwc->state |= PERF_HES_STOPPED;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	if (flags & PERF_EF_START)
56262306a36Sopenharmony_ci		ddr_perf_event_start(event, flags);
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	return 0;
56562306a36Sopenharmony_ci}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_cistatic void ddr_perf_event_stop(struct perf_event *event, int flags)
56862306a36Sopenharmony_ci{
56962306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
57062306a36Sopenharmony_ci	struct hw_perf_event *hwc = &event->hw;
57162306a36Sopenharmony_ci	int counter = hwc->idx;
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	ddr_perf_counter_enable(pmu, event->attr.config, counter, false);
57462306a36Sopenharmony_ci	ddr_perf_event_update(event);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	if (!--pmu->active_counter)
57762306a36Sopenharmony_ci		ddr_perf_counter_enable(pmu, EVENT_CYCLES_ID,
57862306a36Sopenharmony_ci			EVENT_CYCLES_COUNTER, false);
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	hwc->state |= PERF_HES_STOPPED;
58162306a36Sopenharmony_ci}
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_cistatic void ddr_perf_event_del(struct perf_event *event, int flags)
58462306a36Sopenharmony_ci{
58562306a36Sopenharmony_ci	struct ddr_pmu *pmu = to_ddr_pmu(event->pmu);
58662306a36Sopenharmony_ci	struct hw_perf_event *hwc = &event->hw;
58762306a36Sopenharmony_ci	int counter = hwc->idx;
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	ddr_perf_event_stop(event, PERF_EF_UPDATE);
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	ddr_perf_free_counter(pmu, counter);
59262306a36Sopenharmony_ci	hwc->idx = -1;
59362306a36Sopenharmony_ci}
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_cistatic void ddr_perf_pmu_enable(struct pmu *pmu)
59662306a36Sopenharmony_ci{
59762306a36Sopenharmony_ci}
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_cistatic void ddr_perf_pmu_disable(struct pmu *pmu)
60062306a36Sopenharmony_ci{
60162306a36Sopenharmony_ci}
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_cistatic int ddr_perf_init(struct ddr_pmu *pmu, void __iomem *base,
60462306a36Sopenharmony_ci			 struct device *dev)
60562306a36Sopenharmony_ci{
60662306a36Sopenharmony_ci	*pmu = (struct ddr_pmu) {
60762306a36Sopenharmony_ci		.pmu = (struct pmu) {
60862306a36Sopenharmony_ci			.module	      = THIS_MODULE,
60962306a36Sopenharmony_ci			.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
61062306a36Sopenharmony_ci			.task_ctx_nr = perf_invalid_context,
61162306a36Sopenharmony_ci			.attr_groups = attr_groups,
61262306a36Sopenharmony_ci			.event_init  = ddr_perf_event_init,
61362306a36Sopenharmony_ci			.add	     = ddr_perf_event_add,
61462306a36Sopenharmony_ci			.del	     = ddr_perf_event_del,
61562306a36Sopenharmony_ci			.start	     = ddr_perf_event_start,
61662306a36Sopenharmony_ci			.stop	     = ddr_perf_event_stop,
61762306a36Sopenharmony_ci			.read	     = ddr_perf_event_update,
61862306a36Sopenharmony_ci			.pmu_enable  = ddr_perf_pmu_enable,
61962306a36Sopenharmony_ci			.pmu_disable = ddr_perf_pmu_disable,
62062306a36Sopenharmony_ci		},
62162306a36Sopenharmony_ci		.base = base,
62262306a36Sopenharmony_ci		.dev = dev,
62362306a36Sopenharmony_ci	};
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	pmu->id = ida_alloc(&ddr_ida, GFP_KERNEL);
62662306a36Sopenharmony_ci	return pmu->id;
62762306a36Sopenharmony_ci}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_cistatic irqreturn_t ddr_perf_irq_handler(int irq, void *p)
63062306a36Sopenharmony_ci{
63162306a36Sopenharmony_ci	int i;
63262306a36Sopenharmony_ci	struct ddr_pmu *pmu = (struct ddr_pmu *) p;
63362306a36Sopenharmony_ci	struct perf_event *event;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	/* all counter will stop if cycle counter disabled */
63662306a36Sopenharmony_ci	ddr_perf_counter_enable(pmu,
63762306a36Sopenharmony_ci			      EVENT_CYCLES_ID,
63862306a36Sopenharmony_ci			      EVENT_CYCLES_COUNTER,
63962306a36Sopenharmony_ci			      false);
64062306a36Sopenharmony_ci	/*
64162306a36Sopenharmony_ci	 * When the cycle counter overflows, all counters are stopped,
64262306a36Sopenharmony_ci	 * and an IRQ is raised. If any other counter overflows, it
64362306a36Sopenharmony_ci	 * continues counting, and no IRQ is raised. But for new SoCs,
64462306a36Sopenharmony_ci	 * such as i.MX8MP, event counter would stop when overflow, so
64562306a36Sopenharmony_ci	 * we need use cycle counter to stop overflow of event counter.
64662306a36Sopenharmony_ci	 *
64762306a36Sopenharmony_ci	 * Cycles occur at least 4 times as often as other events, so we
64862306a36Sopenharmony_ci	 * can update all events on a cycle counter overflow and not
64962306a36Sopenharmony_ci	 * lose events.
65062306a36Sopenharmony_ci	 *
65162306a36Sopenharmony_ci	 */
65262306a36Sopenharmony_ci	for (i = 0; i < NUM_COUNTERS; i++) {
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci		if (!pmu->events[i])
65562306a36Sopenharmony_ci			continue;
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci		event = pmu->events[i];
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci		ddr_perf_event_update(event);
66062306a36Sopenharmony_ci	}
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	ddr_perf_counter_enable(pmu,
66362306a36Sopenharmony_ci			      EVENT_CYCLES_ID,
66462306a36Sopenharmony_ci			      EVENT_CYCLES_COUNTER,
66562306a36Sopenharmony_ci			      true);
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	return IRQ_HANDLED;
66862306a36Sopenharmony_ci}
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_cistatic int ddr_perf_offline_cpu(unsigned int cpu, struct hlist_node *node)
67162306a36Sopenharmony_ci{
67262306a36Sopenharmony_ci	struct ddr_pmu *pmu = hlist_entry_safe(node, struct ddr_pmu, node);
67362306a36Sopenharmony_ci	int target;
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	if (cpu != pmu->cpu)
67662306a36Sopenharmony_ci		return 0;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	target = cpumask_any_but(cpu_online_mask, cpu);
67962306a36Sopenharmony_ci	if (target >= nr_cpu_ids)
68062306a36Sopenharmony_ci		return 0;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	perf_pmu_migrate_context(&pmu->pmu, cpu, target);
68362306a36Sopenharmony_ci	pmu->cpu = target;
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	WARN_ON(irq_set_affinity(pmu->irq, cpumask_of(pmu->cpu)));
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	return 0;
68862306a36Sopenharmony_ci}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_cistatic int ddr_perf_probe(struct platform_device *pdev)
69162306a36Sopenharmony_ci{
69262306a36Sopenharmony_ci	struct ddr_pmu *pmu;
69362306a36Sopenharmony_ci	struct device_node *np;
69462306a36Sopenharmony_ci	void __iomem *base;
69562306a36Sopenharmony_ci	char *name;
69662306a36Sopenharmony_ci	int num;
69762306a36Sopenharmony_ci	int ret;
69862306a36Sopenharmony_ci	int irq;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	base = devm_platform_ioremap_resource(pdev, 0);
70162306a36Sopenharmony_ci	if (IS_ERR(base))
70262306a36Sopenharmony_ci		return PTR_ERR(base);
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	np = pdev->dev.of_node;
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL);
70762306a36Sopenharmony_ci	if (!pmu)
70862306a36Sopenharmony_ci		return -ENOMEM;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	num = ddr_perf_init(pmu, base, &pdev->dev);
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	platform_set_drvdata(pdev, pmu);
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	name = devm_kasprintf(&pdev->dev, GFP_KERNEL, DDR_PERF_DEV_NAME "%d",
71562306a36Sopenharmony_ci			      num);
71662306a36Sopenharmony_ci	if (!name) {
71762306a36Sopenharmony_ci		ret = -ENOMEM;
71862306a36Sopenharmony_ci		goto cpuhp_state_err;
71962306a36Sopenharmony_ci	}
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci	pmu->devtype_data = of_device_get_match_data(&pdev->dev);
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	pmu->cpu = raw_smp_processor_id();
72462306a36Sopenharmony_ci	ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
72562306a36Sopenharmony_ci				      DDR_CPUHP_CB_NAME,
72662306a36Sopenharmony_ci				      NULL,
72762306a36Sopenharmony_ci				      ddr_perf_offline_cpu);
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	if (ret < 0) {
73062306a36Sopenharmony_ci		dev_err(&pdev->dev, "cpuhp_setup_state_multi failed\n");
73162306a36Sopenharmony_ci		goto cpuhp_state_err;
73262306a36Sopenharmony_ci	}
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_ci	pmu->cpuhp_state = ret;
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	/* Register the pmu instance for cpu hotplug */
73762306a36Sopenharmony_ci	ret = cpuhp_state_add_instance_nocalls(pmu->cpuhp_state, &pmu->node);
73862306a36Sopenharmony_ci	if (ret) {
73962306a36Sopenharmony_ci		dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
74062306a36Sopenharmony_ci		goto cpuhp_instance_err;
74162306a36Sopenharmony_ci	}
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	/* Request irq */
74462306a36Sopenharmony_ci	irq = of_irq_get(np, 0);
74562306a36Sopenharmony_ci	if (irq < 0) {
74662306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to get irq: %d", irq);
74762306a36Sopenharmony_ci		ret = irq;
74862306a36Sopenharmony_ci		goto ddr_perf_err;
74962306a36Sopenharmony_ci	}
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	ret = devm_request_irq(&pdev->dev, irq,
75262306a36Sopenharmony_ci					ddr_perf_irq_handler,
75362306a36Sopenharmony_ci					IRQF_NOBALANCING | IRQF_NO_THREAD,
75462306a36Sopenharmony_ci					DDR_CPUHP_CB_NAME,
75562306a36Sopenharmony_ci					pmu);
75662306a36Sopenharmony_ci	if (ret < 0) {
75762306a36Sopenharmony_ci		dev_err(&pdev->dev, "Request irq failed: %d", ret);
75862306a36Sopenharmony_ci		goto ddr_perf_err;
75962306a36Sopenharmony_ci	}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	pmu->irq = irq;
76262306a36Sopenharmony_ci	ret = irq_set_affinity(pmu->irq, cpumask_of(pmu->cpu));
76362306a36Sopenharmony_ci	if (ret) {
76462306a36Sopenharmony_ci		dev_err(pmu->dev, "Failed to set interrupt affinity!\n");
76562306a36Sopenharmony_ci		goto ddr_perf_err;
76662306a36Sopenharmony_ci	}
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	ret = perf_pmu_register(&pmu->pmu, name, -1);
76962306a36Sopenharmony_ci	if (ret)
77062306a36Sopenharmony_ci		goto ddr_perf_err;
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	return 0;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ciddr_perf_err:
77562306a36Sopenharmony_ci	cpuhp_state_remove_instance_nocalls(pmu->cpuhp_state, &pmu->node);
77662306a36Sopenharmony_cicpuhp_instance_err:
77762306a36Sopenharmony_ci	cpuhp_remove_multi_state(pmu->cpuhp_state);
77862306a36Sopenharmony_cicpuhp_state_err:
77962306a36Sopenharmony_ci	ida_free(&ddr_ida, pmu->id);
78062306a36Sopenharmony_ci	dev_warn(&pdev->dev, "i.MX8 DDR Perf PMU failed (%d), disabled\n", ret);
78162306a36Sopenharmony_ci	return ret;
78262306a36Sopenharmony_ci}
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_cistatic int ddr_perf_remove(struct platform_device *pdev)
78562306a36Sopenharmony_ci{
78662306a36Sopenharmony_ci	struct ddr_pmu *pmu = platform_get_drvdata(pdev);
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	cpuhp_state_remove_instance_nocalls(pmu->cpuhp_state, &pmu->node);
78962306a36Sopenharmony_ci	cpuhp_remove_multi_state(pmu->cpuhp_state);
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	perf_pmu_unregister(&pmu->pmu);
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	ida_free(&ddr_ida, pmu->id);
79462306a36Sopenharmony_ci	return 0;
79562306a36Sopenharmony_ci}
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_cistatic struct platform_driver imx_ddr_pmu_driver = {
79862306a36Sopenharmony_ci	.driver         = {
79962306a36Sopenharmony_ci		.name   = "imx-ddr-pmu",
80062306a36Sopenharmony_ci		.of_match_table = imx_ddr_pmu_dt_ids,
80162306a36Sopenharmony_ci		.suppress_bind_attrs = true,
80262306a36Sopenharmony_ci	},
80362306a36Sopenharmony_ci	.probe          = ddr_perf_probe,
80462306a36Sopenharmony_ci	.remove         = ddr_perf_remove,
80562306a36Sopenharmony_ci};
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_cimodule_platform_driver(imx_ddr_pmu_driver);
80862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
809