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