1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * HiSilicon SLLC uncore Hardware event counters support
4 *
5 * Copyright (C) 2020 HiSilicon Limited
6 * Author: Shaokun Zhang <zhangshaokun@hisilicon.com>
7 *
8 * This code is based on the uncore PMUs like arm-cci and arm-ccn.
9 */
10#include <linux/acpi.h>
11#include <linux/cpuhotplug.h>
12#include <linux/interrupt.h>
13#include <linux/irq.h>
14#include <linux/list.h>
15#include <linux/smp.h>
16
17#include "hisi_uncore_pmu.h"
18
19/* SLLC register definition */
20#define SLLC_INT_MASK			0x0814
21#define SLLC_INT_STATUS			0x0818
22#define SLLC_INT_CLEAR			0x081c
23#define SLLC_PERF_CTRL			0x1c00
24#define SLLC_SRCID_CTRL			0x1c04
25#define SLLC_TGTID_CTRL			0x1c08
26#define SLLC_EVENT_CTRL			0x1c14
27#define SLLC_EVENT_TYPE0		0x1c18
28#define SLLC_VERSION			0x1cf0
29#define SLLC_EVENT_CNT0_L		0x1d00
30
31#define SLLC_EVTYPE_MASK		0xff
32#define SLLC_PERF_CTRL_EN		BIT(0)
33#define SLLC_FILT_EN			BIT(1)
34#define SLLC_TRACETAG_EN		BIT(2)
35#define SLLC_SRCID_EN			BIT(4)
36#define SLLC_SRCID_NONE			0x0
37#define SLLC_TGTID_EN			BIT(5)
38#define SLLC_TGTID_NONE			0x0
39#define SLLC_TGTID_MIN_SHIFT		1
40#define SLLC_TGTID_MAX_SHIFT		12
41#define SLLC_SRCID_CMD_SHIFT		1
42#define SLLC_SRCID_MSK_SHIFT		12
43#define SLLC_NR_EVENTS			0x80
44
45HISI_PMU_EVENT_ATTR_EXTRACTOR(tgtid_min, config1, 10, 0);
46HISI_PMU_EVENT_ATTR_EXTRACTOR(tgtid_max, config1, 21, 11);
47HISI_PMU_EVENT_ATTR_EXTRACTOR(srcid_cmd, config1, 32, 22);
48HISI_PMU_EVENT_ATTR_EXTRACTOR(srcid_msk, config1, 43, 33);
49HISI_PMU_EVENT_ATTR_EXTRACTOR(tracetag_en, config1, 44, 44);
50
51static bool tgtid_is_valid(u32 max, u32 min)
52{
53	return max > 0 && max >= min;
54}
55
56static void hisi_sllc_pmu_enable_tracetag(struct perf_event *event)
57{
58	struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
59	u32 tt_en = hisi_get_tracetag_en(event);
60
61	if (tt_en) {
62		u32 val;
63
64		val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
65		val |= SLLC_TRACETAG_EN | SLLC_FILT_EN;
66		writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
67	}
68}
69
70static void hisi_sllc_pmu_disable_tracetag(struct perf_event *event)
71{
72	struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
73	u32 tt_en = hisi_get_tracetag_en(event);
74
75	if (tt_en) {
76		u32 val;
77
78		val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
79		val &= ~(SLLC_TRACETAG_EN | SLLC_FILT_EN);
80		writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
81	}
82}
83
84static void hisi_sllc_pmu_config_tgtid(struct perf_event *event)
85{
86	struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
87	u32 min = hisi_get_tgtid_min(event);
88	u32 max = hisi_get_tgtid_max(event);
89
90	if (tgtid_is_valid(max, min)) {
91		u32 val = (max << SLLC_TGTID_MAX_SHIFT) | (min << SLLC_TGTID_MIN_SHIFT);
92
93		writel(val, sllc_pmu->base + SLLC_TGTID_CTRL);
94		/* Enable the tgtid */
95		val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
96		val |= SLLC_TGTID_EN | SLLC_FILT_EN;
97		writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
98	}
99}
100
101static void hisi_sllc_pmu_clear_tgtid(struct perf_event *event)
102{
103	struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
104	u32 min = hisi_get_tgtid_min(event);
105	u32 max = hisi_get_tgtid_max(event);
106
107	if (tgtid_is_valid(max, min)) {
108		u32 val;
109
110		writel(SLLC_TGTID_NONE, sllc_pmu->base + SLLC_TGTID_CTRL);
111		/* Disable the tgtid */
112		val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
113		val &= ~(SLLC_TGTID_EN | SLLC_FILT_EN);
114		writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
115	}
116}
117
118static void hisi_sllc_pmu_config_srcid(struct perf_event *event)
119{
120	struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
121	u32 cmd = hisi_get_srcid_cmd(event);
122
123	if (cmd) {
124		u32 val, msk;
125
126		msk = hisi_get_srcid_msk(event);
127		val = (cmd << SLLC_SRCID_CMD_SHIFT) | (msk << SLLC_SRCID_MSK_SHIFT);
128		writel(val, sllc_pmu->base + SLLC_SRCID_CTRL);
129		/* Enable the srcid */
130		val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
131		val |= SLLC_SRCID_EN | SLLC_FILT_EN;
132		writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
133	}
134}
135
136static void hisi_sllc_pmu_clear_srcid(struct perf_event *event)
137{
138	struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
139	u32 cmd = hisi_get_srcid_cmd(event);
140
141	if (cmd) {
142		u32 val;
143
144		writel(SLLC_SRCID_NONE, sllc_pmu->base + SLLC_SRCID_CTRL);
145		/* Disable the srcid */
146		val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
147		val &= ~(SLLC_SRCID_EN | SLLC_FILT_EN);
148		writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
149	}
150}
151
152static void hisi_sllc_pmu_enable_filter(struct perf_event *event)
153{
154	if (event->attr.config1 != 0x0) {
155		hisi_sllc_pmu_enable_tracetag(event);
156		hisi_sllc_pmu_config_srcid(event);
157		hisi_sllc_pmu_config_tgtid(event);
158	}
159}
160
161static void hisi_sllc_pmu_clear_filter(struct perf_event *event)
162{
163	if (event->attr.config1 != 0x0) {
164		hisi_sllc_pmu_disable_tracetag(event);
165		hisi_sllc_pmu_clear_srcid(event);
166		hisi_sllc_pmu_clear_tgtid(event);
167	}
168}
169
170static u32 hisi_sllc_pmu_get_counter_offset(int idx)
171{
172	return (SLLC_EVENT_CNT0_L + idx * 8);
173}
174
175static u64 hisi_sllc_pmu_read_counter(struct hisi_pmu *sllc_pmu,
176				      struct hw_perf_event *hwc)
177{
178	return readq(sllc_pmu->base +
179		     hisi_sllc_pmu_get_counter_offset(hwc->idx));
180}
181
182static void hisi_sllc_pmu_write_counter(struct hisi_pmu *sllc_pmu,
183					struct hw_perf_event *hwc, u64 val)
184{
185	writeq(val, sllc_pmu->base +
186	       hisi_sllc_pmu_get_counter_offset(hwc->idx));
187}
188
189static void hisi_sllc_pmu_write_evtype(struct hisi_pmu *sllc_pmu, int idx,
190				       u32 type)
191{
192	u32 reg, reg_idx, shift, val;
193
194	/*
195	 * Select the appropriate event select register(SLLC_EVENT_TYPE0/1).
196	 * There are 2 event select registers for the 8 hardware counters.
197	 * Event code is 8-bits and for the former 4 hardware counters,
198	 * SLLC_EVENT_TYPE0 is chosen. For the latter 4 hardware counters,
199	 * SLLC_EVENT_TYPE1 is chosen.
200	 */
201	reg = SLLC_EVENT_TYPE0 + (idx / 4) * 4;
202	reg_idx = idx % 4;
203	shift = 8 * reg_idx;
204
205	/* Write event code to SLLC_EVENT_TYPEx Register */
206	val = readl(sllc_pmu->base + reg);
207	val &= ~(SLLC_EVTYPE_MASK << shift);
208	val |= (type << shift);
209	writel(val, sllc_pmu->base + reg);
210}
211
212static void hisi_sllc_pmu_start_counters(struct hisi_pmu *sllc_pmu)
213{
214	u32 val;
215
216	val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
217	val |= SLLC_PERF_CTRL_EN;
218	writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
219}
220
221static void hisi_sllc_pmu_stop_counters(struct hisi_pmu *sllc_pmu)
222{
223	u32 val;
224
225	val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
226	val &= ~(SLLC_PERF_CTRL_EN);
227	writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
228}
229
230static void hisi_sllc_pmu_enable_counter(struct hisi_pmu *sllc_pmu,
231					 struct hw_perf_event *hwc)
232{
233	u32 val;
234
235	val = readl(sllc_pmu->base + SLLC_EVENT_CTRL);
236	val |= 1 << hwc->idx;
237	writel(val, sllc_pmu->base + SLLC_EVENT_CTRL);
238}
239
240static void hisi_sllc_pmu_disable_counter(struct hisi_pmu *sllc_pmu,
241					  struct hw_perf_event *hwc)
242{
243	u32 val;
244
245	val = readl(sllc_pmu->base + SLLC_EVENT_CTRL);
246	val &= ~(1 << hwc->idx);
247	writel(val, sllc_pmu->base + SLLC_EVENT_CTRL);
248}
249
250static void hisi_sllc_pmu_enable_counter_int(struct hisi_pmu *sllc_pmu,
251					     struct hw_perf_event *hwc)
252{
253	u32 val;
254
255	val = readl(sllc_pmu->base + SLLC_INT_MASK);
256	/* Write 0 to enable interrupt */
257	val &= ~(1 << hwc->idx);
258	writel(val, sllc_pmu->base + SLLC_INT_MASK);
259}
260
261static void hisi_sllc_pmu_disable_counter_int(struct hisi_pmu *sllc_pmu,
262					      struct hw_perf_event *hwc)
263{
264	u32 val;
265
266	val = readl(sllc_pmu->base + SLLC_INT_MASK);
267	/* Write 1 to mask interrupt */
268	val |= 1 << hwc->idx;
269	writel(val, sllc_pmu->base + SLLC_INT_MASK);
270}
271
272static u32 hisi_sllc_pmu_get_int_status(struct hisi_pmu *sllc_pmu)
273{
274	return readl(sllc_pmu->base + SLLC_INT_STATUS);
275}
276
277static void hisi_sllc_pmu_clear_int_status(struct hisi_pmu *sllc_pmu, int idx)
278{
279	writel(1 << idx, sllc_pmu->base + SLLC_INT_CLEAR);
280}
281
282static const struct acpi_device_id hisi_sllc_pmu_acpi_match[] = {
283	{ "HISI0263", },
284	{}
285};
286MODULE_DEVICE_TABLE(acpi, hisi_sllc_pmu_acpi_match);
287
288static int hisi_sllc_pmu_init_data(struct platform_device *pdev,
289				   struct hisi_pmu *sllc_pmu)
290{
291	/*
292	 * Use the SCCL_ID and the index ID to identify the SLLC PMU,
293	 * while SCCL_ID is from MPIDR_EL1 by CPU.
294	 */
295	if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
296				     &sllc_pmu->sccl_id)) {
297		dev_err(&pdev->dev, "Cannot read sccl-id!\n");
298		return -EINVAL;
299	}
300
301	if (device_property_read_u32(&pdev->dev, "hisilicon,idx-id",
302				     &sllc_pmu->index_id)) {
303		dev_err(&pdev->dev, "Cannot read idx-id!\n");
304		return -EINVAL;
305	}
306
307	/* SLLC PMUs only share the same SCCL */
308	sllc_pmu->ccl_id = -1;
309
310	sllc_pmu->base = devm_platform_ioremap_resource(pdev, 0);
311	if (IS_ERR(sllc_pmu->base)) {
312		dev_err(&pdev->dev, "ioremap failed for sllc_pmu resource.\n");
313		return PTR_ERR(sllc_pmu->base);
314	}
315
316	sllc_pmu->identifier = readl(sllc_pmu->base + SLLC_VERSION);
317
318	return 0;
319}
320
321static struct attribute *hisi_sllc_pmu_v2_format_attr[] = {
322	HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
323	HISI_PMU_FORMAT_ATTR(tgtid_min, "config1:0-10"),
324	HISI_PMU_FORMAT_ATTR(tgtid_max, "config1:11-21"),
325	HISI_PMU_FORMAT_ATTR(srcid_cmd, "config1:22-32"),
326	HISI_PMU_FORMAT_ATTR(srcid_msk, "config1:33-43"),
327	HISI_PMU_FORMAT_ATTR(tracetag_en, "config1:44"),
328	NULL
329};
330
331static const struct attribute_group hisi_sllc_pmu_v2_format_group = {
332	.name = "format",
333	.attrs = hisi_sllc_pmu_v2_format_attr,
334};
335
336static struct attribute *hisi_sllc_pmu_v2_events_attr[] = {
337	HISI_PMU_EVENT_ATTR(rx_req,             0x30),
338	HISI_PMU_EVENT_ATTR(rx_data,            0x31),
339	HISI_PMU_EVENT_ATTR(tx_req,             0x34),
340	HISI_PMU_EVENT_ATTR(tx_data,            0x35),
341	HISI_PMU_EVENT_ATTR(cycles,             0x09),
342	NULL
343};
344
345static const struct attribute_group hisi_sllc_pmu_v2_events_group = {
346	.name = "events",
347	.attrs = hisi_sllc_pmu_v2_events_attr,
348};
349
350static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL);
351
352static struct attribute *hisi_sllc_pmu_cpumask_attrs[] = {
353	&dev_attr_cpumask.attr,
354	NULL
355};
356
357static const struct attribute_group hisi_sllc_pmu_cpumask_attr_group = {
358	.attrs = hisi_sllc_pmu_cpumask_attrs,
359};
360
361static struct device_attribute hisi_sllc_pmu_identifier_attr =
362	__ATTR(identifier, 0444, hisi_uncore_pmu_identifier_attr_show, NULL);
363
364static struct attribute *hisi_sllc_pmu_identifier_attrs[] = {
365	&hisi_sllc_pmu_identifier_attr.attr,
366	NULL
367};
368
369static const struct attribute_group hisi_sllc_pmu_identifier_group = {
370	.attrs = hisi_sllc_pmu_identifier_attrs,
371};
372
373static const struct attribute_group *hisi_sllc_pmu_v2_attr_groups[] = {
374	&hisi_sllc_pmu_v2_format_group,
375	&hisi_sllc_pmu_v2_events_group,
376	&hisi_sllc_pmu_cpumask_attr_group,
377	&hisi_sllc_pmu_identifier_group,
378	NULL
379};
380
381static const struct hisi_uncore_ops hisi_uncore_sllc_ops = {
382	.write_evtype		= hisi_sllc_pmu_write_evtype,
383	.get_event_idx		= hisi_uncore_pmu_get_event_idx,
384	.start_counters		= hisi_sllc_pmu_start_counters,
385	.stop_counters		= hisi_sllc_pmu_stop_counters,
386	.enable_counter		= hisi_sllc_pmu_enable_counter,
387	.disable_counter	= hisi_sllc_pmu_disable_counter,
388	.enable_counter_int	= hisi_sllc_pmu_enable_counter_int,
389	.disable_counter_int	= hisi_sllc_pmu_disable_counter_int,
390	.write_counter		= hisi_sllc_pmu_write_counter,
391	.read_counter		= hisi_sllc_pmu_read_counter,
392	.get_int_status		= hisi_sllc_pmu_get_int_status,
393	.clear_int_status	= hisi_sllc_pmu_clear_int_status,
394	.enable_filter		= hisi_sllc_pmu_enable_filter,
395	.disable_filter		= hisi_sllc_pmu_clear_filter,
396};
397
398static int hisi_sllc_pmu_dev_probe(struct platform_device *pdev,
399				   struct hisi_pmu *sllc_pmu)
400{
401	int ret;
402
403	ret = hisi_sllc_pmu_init_data(pdev, sllc_pmu);
404	if (ret)
405		return ret;
406
407	ret = hisi_uncore_pmu_init_irq(sllc_pmu, pdev);
408	if (ret)
409		return ret;
410
411	sllc_pmu->pmu_events.attr_groups = hisi_sllc_pmu_v2_attr_groups;
412	sllc_pmu->ops = &hisi_uncore_sllc_ops;
413	sllc_pmu->check_event = SLLC_NR_EVENTS;
414	sllc_pmu->counter_bits = 64;
415	sllc_pmu->num_counters = 8;
416	sllc_pmu->dev = &pdev->dev;
417	sllc_pmu->on_cpu = -1;
418
419	return 0;
420}
421
422static int hisi_sllc_pmu_probe(struct platform_device *pdev)
423{
424	struct hisi_pmu *sllc_pmu;
425	char *name;
426	int ret;
427
428	sllc_pmu = devm_kzalloc(&pdev->dev, sizeof(*sllc_pmu), GFP_KERNEL);
429	if (!sllc_pmu)
430		return -ENOMEM;
431
432	ret = hisi_sllc_pmu_dev_probe(pdev, sllc_pmu);
433	if (ret)
434		return ret;
435
436	name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%u_sllc%u",
437			      sllc_pmu->sccl_id, sllc_pmu->index_id);
438	if (!name)
439		return -ENOMEM;
440
441	ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
442				       &sllc_pmu->node);
443	if (ret) {
444		dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
445		return ret;
446	}
447
448	hisi_pmu_init(sllc_pmu, THIS_MODULE);
449
450	ret = perf_pmu_register(&sllc_pmu->pmu, name, -1);
451	if (ret) {
452		dev_err(sllc_pmu->dev, "PMU register failed, ret = %d\n", ret);
453		cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
454						    &sllc_pmu->node);
455		return ret;
456	}
457
458	platform_set_drvdata(pdev, sllc_pmu);
459
460	return ret;
461}
462
463static void hisi_sllc_pmu_remove(struct platform_device *pdev)
464{
465	struct hisi_pmu *sllc_pmu = platform_get_drvdata(pdev);
466
467	perf_pmu_unregister(&sllc_pmu->pmu);
468	cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
469					    &sllc_pmu->node);
470}
471
472static struct platform_driver hisi_sllc_pmu_driver = {
473	.driver = {
474		.name = "hisi_sllc_pmu",
475		.acpi_match_table = hisi_sllc_pmu_acpi_match,
476		.suppress_bind_attrs = true,
477	},
478	.probe = hisi_sllc_pmu_probe,
479	.remove_new = hisi_sllc_pmu_remove,
480};
481
482static int __init hisi_sllc_pmu_module_init(void)
483{
484	int ret;
485
486	ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
487				      "AP_PERF_ARM_HISI_SLLC_ONLINE",
488				      hisi_uncore_pmu_online_cpu,
489				      hisi_uncore_pmu_offline_cpu);
490	if (ret) {
491		pr_err("SLLC PMU: cpuhp state setup failed, ret = %d\n", ret);
492		return ret;
493	}
494
495	ret = platform_driver_register(&hisi_sllc_pmu_driver);
496	if (ret)
497		cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE);
498
499	return ret;
500}
501module_init(hisi_sllc_pmu_module_init);
502
503static void __exit hisi_sllc_pmu_module_exit(void)
504{
505	platform_driver_unregister(&hisi_sllc_pmu_driver);
506	cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE);
507}
508module_exit(hisi_sllc_pmu_module_exit);
509
510MODULE_DESCRIPTION("HiSilicon SLLC uncore PMU driver");
511MODULE_LICENSE("GPL v2");
512MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>");
513MODULE_AUTHOR("Qi Liu <liuqi115@huawei.com>");
514