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 | |
45 | HISI_PMU_EVENT_ATTR_EXTRACTOR(tgtid_min, config1, 10, 0); |
46 | HISI_PMU_EVENT_ATTR_EXTRACTOR(tgtid_max, config1, 21, 11); |
47 | HISI_PMU_EVENT_ATTR_EXTRACTOR(srcid_cmd, config1, 32, 22); |
48 | HISI_PMU_EVENT_ATTR_EXTRACTOR(srcid_msk, config1, 43, 33); |
49 | HISI_PMU_EVENT_ATTR_EXTRACTOR(tracetag_en, config1, 44, 44); |
50 | |
51 | static bool tgtid_is_valid(u32 max, u32 min) |
52 | { |
53 | return max > 0 && max >= min; |
54 | } |
55 | |
56 | static 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(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
65 | val |= SLLC_TRACETAG_EN | SLLC_FILT_EN; |
66 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
67 | } |
68 | } |
69 | |
70 | static 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(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
79 | val &= ~(SLLC_TRACETAG_EN | SLLC_FILT_EN); |
80 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
81 | } |
82 | } |
83 | |
84 | static 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, addr: sllc_pmu->base + SLLC_TGTID_CTRL); |
94 | /* Enable the tgtid */ |
95 | val = readl(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
96 | val |= SLLC_TGTID_EN | SLLC_FILT_EN; |
97 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
98 | } |
99 | } |
100 | |
101 | static 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, addr: sllc_pmu->base + SLLC_TGTID_CTRL); |
111 | /* Disable the tgtid */ |
112 | val = readl(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
113 | val &= ~(SLLC_TGTID_EN | SLLC_FILT_EN); |
114 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
115 | } |
116 | } |
117 | |
118 | static 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, addr: sllc_pmu->base + SLLC_SRCID_CTRL); |
129 | /* Enable the srcid */ |
130 | val = readl(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
131 | val |= SLLC_SRCID_EN | SLLC_FILT_EN; |
132 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
133 | } |
134 | } |
135 | |
136 | static 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, addr: sllc_pmu->base + SLLC_SRCID_CTRL); |
145 | /* Disable the srcid */ |
146 | val = readl(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
147 | val &= ~(SLLC_SRCID_EN | SLLC_FILT_EN); |
148 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
149 | } |
150 | } |
151 | |
152 | static 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 | |
161 | static 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 | |
170 | static u32 hisi_sllc_pmu_get_counter_offset(int idx) |
171 | { |
172 | return (SLLC_EVENT_CNT0_L + idx * 8); |
173 | } |
174 | |
175 | static u64 hisi_sllc_pmu_read_counter(struct hisi_pmu *sllc_pmu, |
176 | struct hw_perf_event *hwc) |
177 | { |
178 | return readq(addr: sllc_pmu->base + |
179 | hisi_sllc_pmu_get_counter_offset(idx: hwc->idx)); |
180 | } |
181 | |
182 | static void hisi_sllc_pmu_write_counter(struct hisi_pmu *sllc_pmu, |
183 | struct hw_perf_event *hwc, u64 val) |
184 | { |
185 | writeq(val, addr: sllc_pmu->base + |
186 | hisi_sllc_pmu_get_counter_offset(idx: hwc->idx)); |
187 | } |
188 | |
189 | static 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(addr: sllc_pmu->base + reg); |
207 | val &= ~(SLLC_EVTYPE_MASK << shift); |
208 | val |= (type << shift); |
209 | writel(val, addr: sllc_pmu->base + reg); |
210 | } |
211 | |
212 | static void hisi_sllc_pmu_start_counters(struct hisi_pmu *sllc_pmu) |
213 | { |
214 | u32 val; |
215 | |
216 | val = readl(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
217 | val |= SLLC_PERF_CTRL_EN; |
218 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
219 | } |
220 | |
221 | static void hisi_sllc_pmu_stop_counters(struct hisi_pmu *sllc_pmu) |
222 | { |
223 | u32 val; |
224 | |
225 | val = readl(addr: sllc_pmu->base + SLLC_PERF_CTRL); |
226 | val &= ~(SLLC_PERF_CTRL_EN); |
227 | writel(val, addr: sllc_pmu->base + SLLC_PERF_CTRL); |
228 | } |
229 | |
230 | static 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(addr: sllc_pmu->base + SLLC_EVENT_CTRL); |
236 | val |= 1 << hwc->idx; |
237 | writel(val, addr: sllc_pmu->base + SLLC_EVENT_CTRL); |
238 | } |
239 | |
240 | static 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(addr: sllc_pmu->base + SLLC_EVENT_CTRL); |
246 | val &= ~(1 << hwc->idx); |
247 | writel(val, addr: sllc_pmu->base + SLLC_EVENT_CTRL); |
248 | } |
249 | |
250 | static 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(addr: sllc_pmu->base + SLLC_INT_MASK); |
256 | /* Write 0 to enable interrupt */ |
257 | val &= ~(1 << hwc->idx); |
258 | writel(val, addr: sllc_pmu->base + SLLC_INT_MASK); |
259 | } |
260 | |
261 | static 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(addr: sllc_pmu->base + SLLC_INT_MASK); |
267 | /* Write 1 to mask interrupt */ |
268 | val |= 1 << hwc->idx; |
269 | writel(val, addr: sllc_pmu->base + SLLC_INT_MASK); |
270 | } |
271 | |
272 | static u32 hisi_sllc_pmu_get_int_status(struct hisi_pmu *sllc_pmu) |
273 | { |
274 | return readl(addr: sllc_pmu->base + SLLC_INT_STATUS); |
275 | } |
276 | |
277 | static void hisi_sllc_pmu_clear_int_status(struct hisi_pmu *sllc_pmu, int idx) |
278 | { |
279 | writel(val: 1 << idx, addr: sllc_pmu->base + SLLC_INT_CLEAR); |
280 | } |
281 | |
282 | static const struct acpi_device_id hisi_sllc_pmu_acpi_match[] = { |
283 | { "HISI0263" , }, |
284 | {} |
285 | }; |
286 | MODULE_DEVICE_TABLE(acpi, hisi_sllc_pmu_acpi_match); |
287 | |
288 | static 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(dev: &pdev->dev, propname: "hisilicon,scl-id" , |
296 | val: &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(dev: &pdev->dev, propname: "hisilicon,idx-id" , |
302 | val: &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, index: 0); |
311 | if (IS_ERR(ptr: sllc_pmu->base)) { |
312 | dev_err(&pdev->dev, "ioremap failed for sllc_pmu resource.\n" ); |
313 | return PTR_ERR(ptr: sllc_pmu->base); |
314 | } |
315 | |
316 | sllc_pmu->identifier = readl(addr: sllc_pmu->base + SLLC_VERSION); |
317 | |
318 | return 0; |
319 | } |
320 | |
321 | static 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 | |
331 | static const struct attribute_group hisi_sllc_pmu_v2_format_group = { |
332 | .name = "format" , |
333 | .attrs = hisi_sllc_pmu_v2_format_attr, |
334 | }; |
335 | |
336 | static 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 | |
345 | static const struct attribute_group hisi_sllc_pmu_v2_events_group = { |
346 | .name = "events" , |
347 | .attrs = hisi_sllc_pmu_v2_events_attr, |
348 | }; |
349 | |
350 | static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL); |
351 | |
352 | static struct attribute *hisi_sllc_pmu_cpumask_attrs[] = { |
353 | &dev_attr_cpumask.attr, |
354 | NULL |
355 | }; |
356 | |
357 | static const struct attribute_group hisi_sllc_pmu_cpumask_attr_group = { |
358 | .attrs = hisi_sllc_pmu_cpumask_attrs, |
359 | }; |
360 | |
361 | static struct device_attribute hisi_sllc_pmu_identifier_attr = |
362 | __ATTR(identifier, 0444, hisi_uncore_pmu_identifier_attr_show, NULL); |
363 | |
364 | static struct attribute *hisi_sllc_pmu_identifier_attrs[] = { |
365 | &hisi_sllc_pmu_identifier_attr.attr, |
366 | NULL |
367 | }; |
368 | |
369 | static const struct attribute_group hisi_sllc_pmu_identifier_group = { |
370 | .attrs = hisi_sllc_pmu_identifier_attrs, |
371 | }; |
372 | |
373 | static 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 | |
381 | static 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 | |
398 | static 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(hisi_pmu: 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 | |
422 | static 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(dev: &pdev->dev, size: 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(dev: &pdev->dev, GFP_KERNEL, fmt: "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(state: CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, |
442 | node: &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(hisi_pmu: sllc_pmu, THIS_MODULE); |
449 | |
450 | ret = perf_pmu_register(pmu: &sllc_pmu->pmu, name, type: -1); |
451 | if (ret) { |
452 | dev_err(sllc_pmu->dev, "PMU register failed, ret = %d\n" , ret); |
453 | cpuhp_state_remove_instance_nocalls(state: CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, |
454 | node: &sllc_pmu->node); |
455 | return ret; |
456 | } |
457 | |
458 | platform_set_drvdata(pdev, data: sllc_pmu); |
459 | |
460 | return ret; |
461 | } |
462 | |
463 | static 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(pmu: &sllc_pmu->pmu); |
468 | cpuhp_state_remove_instance_nocalls(state: CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, |
469 | node: &sllc_pmu->node); |
470 | } |
471 | |
472 | static 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 | |
482 | static int __init hisi_sllc_pmu_module_init(void) |
483 | { |
484 | int ret; |
485 | |
486 | ret = cpuhp_setup_state_multi(state: CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, |
487 | name: "AP_PERF_ARM_HISI_SLLC_ONLINE" , |
488 | startup: hisi_uncore_pmu_online_cpu, |
489 | teardown: 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(state: CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE); |
498 | |
499 | return ret; |
500 | } |
501 | module_init(hisi_sllc_pmu_module_init); |
502 | |
503 | static void __exit hisi_sllc_pmu_module_exit(void) |
504 | { |
505 | platform_driver_unregister(&hisi_sllc_pmu_driver); |
506 | cpuhp_remove_multi_state(state: CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE); |
507 | } |
508 | module_exit(hisi_sllc_pmu_module_exit); |
509 | |
510 | MODULE_DESCRIPTION("HiSilicon SLLC uncore PMU driver" ); |
511 | MODULE_LICENSE("GPL v2" ); |
512 | MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>" ); |
513 | MODULE_AUTHOR("Qi Liu <liuqi115@huawei.com>" ); |
514 | |