1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * exynos_ppmu.c - Exynos PPMU (Platform Performance Monitoring Unit) support |
4 | * |
5 | * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd. |
6 | * Author : Chanwoo Choi <cw00.choi@samsung.com> |
7 | * |
8 | * This driver is based on drivers/devfreq/exynos/exynos_ppmu.c |
9 | */ |
10 | |
11 | #include <linux/clk.h> |
12 | #include <linux/io.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/property.h> |
18 | #include <linux/regmap.h> |
19 | #include <linux/suspend.h> |
20 | #include <linux/devfreq-event.h> |
21 | |
22 | #include "exynos-ppmu.h" |
23 | |
24 | enum exynos_ppmu_type { |
25 | EXYNOS_TYPE_PPMU, |
26 | EXYNOS_TYPE_PPMU_V2, |
27 | }; |
28 | |
29 | struct exynos_ppmu_data { |
30 | struct clk *clk; |
31 | }; |
32 | |
33 | struct exynos_ppmu { |
34 | struct devfreq_event_dev **edev; |
35 | struct devfreq_event_desc *desc; |
36 | unsigned int num_events; |
37 | |
38 | struct device *dev; |
39 | struct regmap *regmap; |
40 | |
41 | struct exynos_ppmu_data ppmu; |
42 | enum exynos_ppmu_type ppmu_type; |
43 | }; |
44 | |
45 | #define PPMU_EVENT(name) \ |
46 | { "ppmu-event0-"#name, PPMU_PMNCNT0 }, \ |
47 | { "ppmu-event1-"#name, PPMU_PMNCNT1 }, \ |
48 | { "ppmu-event2-"#name, PPMU_PMNCNT2 }, \ |
49 | { "ppmu-event3-"#name, PPMU_PMNCNT3 } |
50 | |
51 | static struct __exynos_ppmu_events { |
52 | char *name; |
53 | int id; |
54 | } ppmu_events[] = { |
55 | /* For Exynos3250, Exynos4 and Exynos5260 */ |
56 | PPMU_EVENT(g3d), |
57 | PPMU_EVENT(fsys), |
58 | |
59 | /* For Exynos4 SoCs and Exynos3250 */ |
60 | PPMU_EVENT(dmc0), |
61 | PPMU_EVENT(dmc1), |
62 | PPMU_EVENT(cpu), |
63 | PPMU_EVENT(rightbus), |
64 | PPMU_EVENT(leftbus), |
65 | PPMU_EVENT(lcd0), |
66 | PPMU_EVENT(camif), |
67 | |
68 | /* Only for Exynos3250 and Exynos5260 */ |
69 | PPMU_EVENT(mfc), |
70 | |
71 | /* Only for Exynos4 SoCs */ |
72 | PPMU_EVENT(mfc-left), |
73 | PPMU_EVENT(mfc-right), |
74 | |
75 | /* Only for Exynos5260 SoCs */ |
76 | PPMU_EVENT(drex0-s0), |
77 | PPMU_EVENT(drex0-s1), |
78 | PPMU_EVENT(drex1-s0), |
79 | PPMU_EVENT(drex1-s1), |
80 | PPMU_EVENT(eagle), |
81 | PPMU_EVENT(kfc), |
82 | PPMU_EVENT(isp), |
83 | PPMU_EVENT(fimc), |
84 | PPMU_EVENT(gscl), |
85 | PPMU_EVENT(mscl), |
86 | PPMU_EVENT(fimd0x), |
87 | PPMU_EVENT(fimd1x), |
88 | |
89 | /* Only for Exynos5433 SoCs */ |
90 | PPMU_EVENT(d0-cpu), |
91 | PPMU_EVENT(d0-general), |
92 | PPMU_EVENT(d0-rt), |
93 | PPMU_EVENT(d1-cpu), |
94 | PPMU_EVENT(d1-general), |
95 | PPMU_EVENT(d1-rt), |
96 | |
97 | /* For Exynos5422 SoC, deprecated (backwards compatible) */ |
98 | PPMU_EVENT(dmc0_0), |
99 | PPMU_EVENT(dmc0_1), |
100 | PPMU_EVENT(dmc1_0), |
101 | PPMU_EVENT(dmc1_1), |
102 | /* For Exynos5422 SoC */ |
103 | PPMU_EVENT(dmc0-0), |
104 | PPMU_EVENT(dmc0-1), |
105 | PPMU_EVENT(dmc1-0), |
106 | PPMU_EVENT(dmc1-1), |
107 | }; |
108 | |
109 | static int __exynos_ppmu_find_ppmu_id(const char *edev_name) |
110 | { |
111 | int i; |
112 | |
113 | for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) |
114 | if (!strcmp(edev_name, ppmu_events[i].name)) |
115 | return ppmu_events[i].id; |
116 | |
117 | return -EINVAL; |
118 | } |
119 | |
120 | static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) |
121 | { |
122 | return __exynos_ppmu_find_ppmu_id(edev_name: edev->desc->name); |
123 | } |
124 | |
125 | /* |
126 | * The devfreq-event ops structure for PPMU v1.1 |
127 | */ |
128 | static int exynos_ppmu_disable(struct devfreq_event_dev *edev) |
129 | { |
130 | struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); |
131 | int ret; |
132 | u32 pmnc; |
133 | |
134 | /* Disable all counters */ |
135 | ret = regmap_write(map: info->regmap, reg: PPMU_CNTENC, |
136 | PPMU_CCNT_MASK | |
137 | PPMU_PMCNT0_MASK | |
138 | PPMU_PMCNT1_MASK | |
139 | PPMU_PMCNT2_MASK | |
140 | PPMU_PMCNT3_MASK); |
141 | if (ret < 0) |
142 | return ret; |
143 | |
144 | /* Disable PPMU */ |
145 | ret = regmap_read(map: info->regmap, reg: PPMU_PMNC, val: &pmnc); |
146 | if (ret < 0) |
147 | return ret; |
148 | |
149 | pmnc &= ~PPMU_PMNC_ENABLE_MASK; |
150 | ret = regmap_write(map: info->regmap, reg: PPMU_PMNC, val: pmnc); |
151 | if (ret < 0) |
152 | return ret; |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | static int exynos_ppmu_set_event(struct devfreq_event_dev *edev) |
158 | { |
159 | struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); |
160 | int id = exynos_ppmu_find_ppmu_id(edev); |
161 | int ret; |
162 | u32 pmnc, cntens; |
163 | |
164 | if (id < 0) |
165 | return id; |
166 | |
167 | /* Enable specific counter */ |
168 | ret = regmap_read(map: info->regmap, reg: PPMU_CNTENS, val: &cntens); |
169 | if (ret < 0) |
170 | return ret; |
171 | |
172 | cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); |
173 | ret = regmap_write(map: info->regmap, reg: PPMU_CNTENS, val: cntens); |
174 | if (ret < 0) |
175 | return ret; |
176 | |
177 | /* Set the event of proper data type monitoring */ |
178 | ret = regmap_write(map: info->regmap, PPMU_BEVTxSEL(id), |
179 | val: edev->desc->event_type); |
180 | if (ret < 0) |
181 | return ret; |
182 | |
183 | /* Reset cycle counter/performance counter and enable PPMU */ |
184 | ret = regmap_read(map: info->regmap, reg: PPMU_PMNC, val: &pmnc); |
185 | if (ret < 0) |
186 | return ret; |
187 | |
188 | pmnc &= ~(PPMU_PMNC_ENABLE_MASK |
189 | | PPMU_PMNC_COUNTER_RESET_MASK |
190 | | PPMU_PMNC_CC_RESET_MASK); |
191 | pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT); |
192 | pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT); |
193 | pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT); |
194 | ret = regmap_write(map: info->regmap, reg: PPMU_PMNC, val: pmnc); |
195 | if (ret < 0) |
196 | return ret; |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | static int exynos_ppmu_get_event(struct devfreq_event_dev *edev, |
202 | struct devfreq_event_data *edata) |
203 | { |
204 | struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); |
205 | int id = exynos_ppmu_find_ppmu_id(edev); |
206 | unsigned int total_count, load_count; |
207 | unsigned int pmcnt3_high, pmcnt3_low; |
208 | unsigned int pmnc, cntenc; |
209 | int ret; |
210 | |
211 | if (id < 0) |
212 | return -EINVAL; |
213 | |
214 | /* Disable PPMU */ |
215 | ret = regmap_read(map: info->regmap, reg: PPMU_PMNC, val: &pmnc); |
216 | if (ret < 0) |
217 | return ret; |
218 | |
219 | pmnc &= ~PPMU_PMNC_ENABLE_MASK; |
220 | ret = regmap_write(map: info->regmap, reg: PPMU_PMNC, val: pmnc); |
221 | if (ret < 0) |
222 | return ret; |
223 | |
224 | /* Read cycle count */ |
225 | ret = regmap_read(map: info->regmap, reg: PPMU_CCNT, val: &total_count); |
226 | if (ret < 0) |
227 | return ret; |
228 | edata->total_count = total_count; |
229 | |
230 | /* Read performance count */ |
231 | switch (id) { |
232 | case PPMU_PMNCNT0: |
233 | case PPMU_PMNCNT1: |
234 | case PPMU_PMNCNT2: |
235 | ret = regmap_read(map: info->regmap, PPMU_PMNCT(id), val: &load_count); |
236 | if (ret < 0) |
237 | return ret; |
238 | edata->load_count = load_count; |
239 | break; |
240 | case PPMU_PMNCNT3: |
241 | ret = regmap_read(map: info->regmap, reg: PPMU_PMCNT3_HIGH, val: &pmcnt3_high); |
242 | if (ret < 0) |
243 | return ret; |
244 | |
245 | ret = regmap_read(map: info->regmap, reg: PPMU_PMCNT3_LOW, val: &pmcnt3_low); |
246 | if (ret < 0) |
247 | return ret; |
248 | |
249 | edata->load_count = ((pmcnt3_high << 8) | pmcnt3_low); |
250 | break; |
251 | default: |
252 | return -EINVAL; |
253 | } |
254 | |
255 | /* Disable specific counter */ |
256 | ret = regmap_read(map: info->regmap, reg: PPMU_CNTENC, val: &cntenc); |
257 | if (ret < 0) |
258 | return ret; |
259 | |
260 | cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); |
261 | ret = regmap_write(map: info->regmap, reg: PPMU_CNTENC, val: cntenc); |
262 | if (ret < 0) |
263 | return ret; |
264 | |
265 | dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n" , edev->desc->name, |
266 | edata->load_count, edata->total_count); |
267 | |
268 | return 0; |
269 | } |
270 | |
271 | static const struct devfreq_event_ops exynos_ppmu_ops = { |
272 | .disable = exynos_ppmu_disable, |
273 | .set_event = exynos_ppmu_set_event, |
274 | .get_event = exynos_ppmu_get_event, |
275 | }; |
276 | |
277 | /* |
278 | * The devfreq-event ops structure for PPMU v2.0 |
279 | */ |
280 | static int exynos_ppmu_v2_disable(struct devfreq_event_dev *edev) |
281 | { |
282 | struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); |
283 | int ret; |
284 | u32 pmnc, clear; |
285 | |
286 | /* Disable all counters */ |
287 | clear = (PPMU_CCNT_MASK | PPMU_PMCNT0_MASK | PPMU_PMCNT1_MASK |
288 | | PPMU_PMCNT2_MASK | PPMU_PMCNT3_MASK); |
289 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_FLAG, val: clear); |
290 | if (ret < 0) |
291 | return ret; |
292 | |
293 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_INTENC, val: clear); |
294 | if (ret < 0) |
295 | return ret; |
296 | |
297 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CNTENC, val: clear); |
298 | if (ret < 0) |
299 | return ret; |
300 | |
301 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CNT_RESET, val: clear); |
302 | if (ret < 0) |
303 | return ret; |
304 | |
305 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CIG_CFG0, val: 0x0); |
306 | if (ret < 0) |
307 | return ret; |
308 | |
309 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CIG_CFG1, val: 0x0); |
310 | if (ret < 0) |
311 | return ret; |
312 | |
313 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CIG_CFG2, val: 0x0); |
314 | if (ret < 0) |
315 | return ret; |
316 | |
317 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CIG_RESULT, val: 0x0); |
318 | if (ret < 0) |
319 | return ret; |
320 | |
321 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CNT_AUTO, val: 0x0); |
322 | if (ret < 0) |
323 | return ret; |
324 | |
325 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CH_EV0_TYPE, val: 0x0); |
326 | if (ret < 0) |
327 | return ret; |
328 | |
329 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CH_EV1_TYPE, val: 0x0); |
330 | if (ret < 0) |
331 | return ret; |
332 | |
333 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CH_EV2_TYPE, val: 0x0); |
334 | if (ret < 0) |
335 | return ret; |
336 | |
337 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CH_EV3_TYPE, val: 0x0); |
338 | if (ret < 0) |
339 | return ret; |
340 | |
341 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_SM_ID_V, val: 0x0); |
342 | if (ret < 0) |
343 | return ret; |
344 | |
345 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_SM_ID_A, val: 0x0); |
346 | if (ret < 0) |
347 | return ret; |
348 | |
349 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_SM_OTHERS_V, val: 0x0); |
350 | if (ret < 0) |
351 | return ret; |
352 | |
353 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_SM_OTHERS_A, val: 0x0); |
354 | if (ret < 0) |
355 | return ret; |
356 | |
357 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_INTERRUPT_RESET, val: 0x0); |
358 | if (ret < 0) |
359 | return ret; |
360 | |
361 | /* Disable PPMU */ |
362 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_PMNC, val: &pmnc); |
363 | if (ret < 0) |
364 | return ret; |
365 | |
366 | pmnc &= ~PPMU_PMNC_ENABLE_MASK; |
367 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_PMNC, val: pmnc); |
368 | if (ret < 0) |
369 | return ret; |
370 | |
371 | return 0; |
372 | } |
373 | |
374 | static int exynos_ppmu_v2_set_event(struct devfreq_event_dev *edev) |
375 | { |
376 | struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); |
377 | unsigned int pmnc, cntens; |
378 | int id = exynos_ppmu_find_ppmu_id(edev); |
379 | int ret; |
380 | |
381 | /* Enable all counters */ |
382 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_CNTENS, val: &cntens); |
383 | if (ret < 0) |
384 | return ret; |
385 | |
386 | cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); |
387 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CNTENS, val: cntens); |
388 | if (ret < 0) |
389 | return ret; |
390 | |
391 | /* Set the event of proper data type monitoring */ |
392 | ret = regmap_write(map: info->regmap, PPMU_V2_CH_EVx_TYPE(id), |
393 | val: edev->desc->event_type); |
394 | if (ret < 0) |
395 | return ret; |
396 | |
397 | /* Reset cycle counter/performance counter and enable PPMU */ |
398 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_PMNC, val: &pmnc); |
399 | if (ret < 0) |
400 | return ret; |
401 | |
402 | pmnc &= ~(PPMU_PMNC_ENABLE_MASK |
403 | | PPMU_PMNC_COUNTER_RESET_MASK |
404 | | PPMU_PMNC_CC_RESET_MASK |
405 | | PPMU_PMNC_CC_DIVIDER_MASK |
406 | | PPMU_V2_PMNC_START_MODE_MASK); |
407 | pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT); |
408 | pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT); |
409 | pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT); |
410 | pmnc |= (PPMU_V2_MODE_MANUAL << PPMU_V2_PMNC_START_MODE_SHIFT); |
411 | |
412 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_PMNC, val: pmnc); |
413 | if (ret < 0) |
414 | return ret; |
415 | |
416 | return 0; |
417 | } |
418 | |
419 | static int exynos_ppmu_v2_get_event(struct devfreq_event_dev *edev, |
420 | struct devfreq_event_data *edata) |
421 | { |
422 | struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); |
423 | int id = exynos_ppmu_find_ppmu_id(edev); |
424 | int ret; |
425 | unsigned int pmnc, cntenc; |
426 | unsigned int pmcnt_high, pmcnt_low; |
427 | unsigned int total_count, count; |
428 | unsigned long load_count = 0; |
429 | |
430 | /* Disable PPMU */ |
431 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_PMNC, val: &pmnc); |
432 | if (ret < 0) |
433 | return ret; |
434 | |
435 | pmnc &= ~PPMU_PMNC_ENABLE_MASK; |
436 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_PMNC, val: pmnc); |
437 | if (ret < 0) |
438 | return ret; |
439 | |
440 | /* Read cycle count and performance count */ |
441 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_CCNT, val: &total_count); |
442 | if (ret < 0) |
443 | return ret; |
444 | edata->total_count = total_count; |
445 | |
446 | switch (id) { |
447 | case PPMU_PMNCNT0: |
448 | case PPMU_PMNCNT1: |
449 | case PPMU_PMNCNT2: |
450 | ret = regmap_read(map: info->regmap, PPMU_V2_PMNCT(id), val: &count); |
451 | if (ret < 0) |
452 | return ret; |
453 | load_count = count; |
454 | break; |
455 | case PPMU_PMNCNT3: |
456 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_PMCNT3_HIGH, |
457 | val: &pmcnt_high); |
458 | if (ret < 0) |
459 | return ret; |
460 | |
461 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_PMCNT3_LOW, val: &pmcnt_low); |
462 | if (ret < 0) |
463 | return ret; |
464 | |
465 | load_count = ((u64)((pmcnt_high & 0xff)) << 32)+ (u64)pmcnt_low; |
466 | break; |
467 | } |
468 | edata->load_count = load_count; |
469 | |
470 | /* Disable all counters */ |
471 | ret = regmap_read(map: info->regmap, reg: PPMU_V2_CNTENC, val: &cntenc); |
472 | if (ret < 0) |
473 | return 0; |
474 | |
475 | cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); |
476 | ret = regmap_write(map: info->regmap, reg: PPMU_V2_CNTENC, val: cntenc); |
477 | if (ret < 0) |
478 | return ret; |
479 | |
480 | dev_dbg(&edev->dev, "%25s (load: %ld / %ld)\n" , edev->desc->name, |
481 | edata->load_count, edata->total_count); |
482 | return 0; |
483 | } |
484 | |
485 | static const struct devfreq_event_ops exynos_ppmu_v2_ops = { |
486 | .disable = exynos_ppmu_v2_disable, |
487 | .set_event = exynos_ppmu_v2_set_event, |
488 | .get_event = exynos_ppmu_v2_get_event, |
489 | }; |
490 | |
491 | static const struct of_device_id exynos_ppmu_id_match[] = { |
492 | { |
493 | .compatible = "samsung,exynos-ppmu" , |
494 | .data = (void *)EXYNOS_TYPE_PPMU, |
495 | }, { |
496 | .compatible = "samsung,exynos-ppmu-v2" , |
497 | .data = (void *)EXYNOS_TYPE_PPMU_V2, |
498 | }, |
499 | { /* sentinel */ }, |
500 | }; |
501 | MODULE_DEVICE_TABLE(of, exynos_ppmu_id_match); |
502 | |
503 | static int of_get_devfreq_events(struct device_node *np, |
504 | struct exynos_ppmu *info) |
505 | { |
506 | struct devfreq_event_desc *desc; |
507 | struct device *dev = info->dev; |
508 | struct device_node *events_np, *node; |
509 | int i, j, count; |
510 | int ret; |
511 | |
512 | events_np = of_get_child_by_name(node: np, name: "events" ); |
513 | if (!events_np) { |
514 | dev_err(dev, |
515 | "failed to get child node of devfreq-event devices\n" ); |
516 | return -EINVAL; |
517 | } |
518 | |
519 | count = of_get_child_count(np: events_np); |
520 | desc = devm_kcalloc(dev, n: count, size: sizeof(*desc), GFP_KERNEL); |
521 | if (!desc) { |
522 | of_node_put(node: events_np); |
523 | return -ENOMEM; |
524 | } |
525 | info->num_events = count; |
526 | |
527 | info->ppmu_type = (enum exynos_ppmu_type)device_get_match_data(dev); |
528 | |
529 | j = 0; |
530 | for_each_child_of_node(events_np, node) { |
531 | for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) { |
532 | if (!ppmu_events[i].name) |
533 | continue; |
534 | |
535 | if (of_node_name_eq(np: node, name: ppmu_events[i].name)) |
536 | break; |
537 | } |
538 | |
539 | if (i == ARRAY_SIZE(ppmu_events)) { |
540 | dev_warn(dev, |
541 | "don't know how to configure events : %pOFn\n" , |
542 | node); |
543 | continue; |
544 | } |
545 | |
546 | switch (info->ppmu_type) { |
547 | case EXYNOS_TYPE_PPMU: |
548 | desc[j].ops = &exynos_ppmu_ops; |
549 | break; |
550 | case EXYNOS_TYPE_PPMU_V2: |
551 | desc[j].ops = &exynos_ppmu_v2_ops; |
552 | break; |
553 | } |
554 | |
555 | desc[j].driver_data = info; |
556 | |
557 | of_property_read_string(np: node, propname: "event-name" , out_string: &desc[j].name); |
558 | ret = of_property_read_u32(np: node, propname: "event-data-type" , |
559 | out_value: &desc[j].event_type); |
560 | if (ret) { |
561 | /* Set the event of proper data type counting. |
562 | * Check if the data type has been defined in DT, |
563 | * use default if not. |
564 | */ |
565 | if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) { |
566 | /* Not all registers take the same value for |
567 | * read+write data count. |
568 | */ |
569 | switch (ppmu_events[i].id) { |
570 | case PPMU_PMNCNT0: |
571 | case PPMU_PMNCNT1: |
572 | case PPMU_PMNCNT2: |
573 | desc[j].event_type = PPMU_V2_RO_DATA_CNT |
574 | | PPMU_V2_WO_DATA_CNT; |
575 | break; |
576 | case PPMU_PMNCNT3: |
577 | desc[j].event_type = |
578 | PPMU_V2_EVT3_RW_DATA_CNT; |
579 | break; |
580 | } |
581 | } else { |
582 | desc[j].event_type = PPMU_RO_DATA_CNT | |
583 | PPMU_WO_DATA_CNT; |
584 | } |
585 | } |
586 | |
587 | j++; |
588 | } |
589 | info->desc = desc; |
590 | |
591 | of_node_put(node: events_np); |
592 | |
593 | return 0; |
594 | } |
595 | |
596 | static struct regmap_config exynos_ppmu_regmap_config = { |
597 | .reg_bits = 32, |
598 | .val_bits = 32, |
599 | .reg_stride = 4, |
600 | }; |
601 | |
602 | static int exynos_ppmu_parse_dt(struct platform_device *pdev, |
603 | struct exynos_ppmu *info) |
604 | { |
605 | struct device *dev = info->dev; |
606 | struct device_node *np = dev->of_node; |
607 | struct resource *res; |
608 | void __iomem *base; |
609 | int ret = 0; |
610 | |
611 | if (!np) { |
612 | dev_err(dev, "failed to find devicetree node\n" ); |
613 | return -EINVAL; |
614 | } |
615 | |
616 | /* Maps the memory mapped IO to control PPMU register */ |
617 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
618 | if (IS_ERR(ptr: base)) |
619 | return PTR_ERR(ptr: base); |
620 | |
621 | exynos_ppmu_regmap_config.max_register = resource_size(res) - 4; |
622 | info->regmap = devm_regmap_init_mmio(dev, base, |
623 | &exynos_ppmu_regmap_config); |
624 | if (IS_ERR(ptr: info->regmap)) { |
625 | dev_err(dev, "failed to initialize regmap\n" ); |
626 | return PTR_ERR(ptr: info->regmap); |
627 | } |
628 | |
629 | info->ppmu.clk = devm_clk_get(dev, id: "ppmu" ); |
630 | if (IS_ERR(ptr: info->ppmu.clk)) { |
631 | info->ppmu.clk = NULL; |
632 | dev_warn(dev, "cannot get PPMU clock\n" ); |
633 | } |
634 | |
635 | ret = of_get_devfreq_events(np, info); |
636 | if (ret < 0) { |
637 | dev_err(dev, "failed to parse exynos ppmu dt node\n" ); |
638 | return ret; |
639 | } |
640 | |
641 | return 0; |
642 | } |
643 | |
644 | static int exynos_ppmu_probe(struct platform_device *pdev) |
645 | { |
646 | struct exynos_ppmu *info; |
647 | struct devfreq_event_dev **edev; |
648 | struct devfreq_event_desc *desc; |
649 | int i, ret = 0, size; |
650 | |
651 | info = devm_kzalloc(dev: &pdev->dev, size: sizeof(*info), GFP_KERNEL); |
652 | if (!info) |
653 | return -ENOMEM; |
654 | |
655 | info->dev = &pdev->dev; |
656 | |
657 | /* Parse dt data to get resource */ |
658 | ret = exynos_ppmu_parse_dt(pdev, info); |
659 | if (ret < 0) { |
660 | dev_err(&pdev->dev, |
661 | "failed to parse devicetree for resource\n" ); |
662 | return ret; |
663 | } |
664 | desc = info->desc; |
665 | |
666 | size = sizeof(struct devfreq_event_dev *) * info->num_events; |
667 | info->edev = devm_kzalloc(dev: &pdev->dev, size, GFP_KERNEL); |
668 | if (!info->edev) |
669 | return -ENOMEM; |
670 | |
671 | edev = info->edev; |
672 | platform_set_drvdata(pdev, data: info); |
673 | |
674 | for (i = 0; i < info->num_events; i++) { |
675 | edev[i] = devm_devfreq_event_add_edev(dev: &pdev->dev, desc: &desc[i]); |
676 | if (IS_ERR(ptr: edev[i])) { |
677 | dev_err(&pdev->dev, |
678 | "failed to add devfreq-event device\n" ); |
679 | return PTR_ERR(ptr: edev[i]); |
680 | } |
681 | |
682 | pr_info("exynos-ppmu: new PPMU device registered %s (%s)\n" , |
683 | dev_name(&pdev->dev), desc[i].name); |
684 | } |
685 | |
686 | ret = clk_prepare_enable(clk: info->ppmu.clk); |
687 | if (ret) { |
688 | dev_err(&pdev->dev, "failed to prepare ppmu clock\n" ); |
689 | return ret; |
690 | } |
691 | |
692 | return 0; |
693 | } |
694 | |
695 | static int exynos_ppmu_remove(struct platform_device *pdev) |
696 | { |
697 | struct exynos_ppmu *info = platform_get_drvdata(pdev); |
698 | |
699 | clk_disable_unprepare(clk: info->ppmu.clk); |
700 | |
701 | return 0; |
702 | } |
703 | |
704 | static struct platform_driver exynos_ppmu_driver = { |
705 | .probe = exynos_ppmu_probe, |
706 | .remove = exynos_ppmu_remove, |
707 | .driver = { |
708 | .name = "exynos-ppmu" , |
709 | .of_match_table = exynos_ppmu_id_match, |
710 | }, |
711 | }; |
712 | module_platform_driver(exynos_ppmu_driver); |
713 | |
714 | MODULE_DESCRIPTION("Exynos PPMU(Platform Performance Monitoring Unit) driver" ); |
715 | MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>" ); |
716 | MODULE_LICENSE("GPL" ); |
717 | |