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