aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/devfreq/exynos/exynos5_bus.c
diff options
context:
space:
mode:
authorAbhilash Kesavan <a.kesavan@samsung.com>2013-02-04 07:14:48 -0500
committerMyungJoo Ham <myungjoo.ham@samsung.com>2013-06-03 07:20:29 -0400
commit6ccce69955c1bf2973bdec0d023685401de543f4 (patch)
tree5b5dbed517a934b95a713029f1f9941a45994e3f /drivers/devfreq/exynos/exynos5_bus.c
parent537eb8e2b6e879d6aea58d913b2ebc3edc7af621 (diff)
PM / devfreq: Add Exynos5-bus devfreq driver for Exynos5250
Exynos5-bus device devfreq driver monitors PPMU counters and adjusts operating frequencies and voltages with OPP. ASV should be used to provide appropriate voltages as per the speed group of the SoC rather than using a constant 1.025V. Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com> [myungjoo.ham@samsung.com: minor style update] Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Cc: Jonghwan Choi <jhbird.choi@samsung.com> Cc: Kukjin Kim <kgene.kim@samsung.com>
Diffstat (limited to 'drivers/devfreq/exynos/exynos5_bus.c')
-rw-r--r--drivers/devfreq/exynos/exynos5_bus.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/drivers/devfreq/exynos/exynos5_bus.c b/drivers/devfreq/exynos/exynos5_bus.c
new file mode 100644
index 000000000000..574b16b59be5
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos5_bus.c
@@ -0,0 +1,503 @@
1/*
2 * Copyright (c) 2012 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com/
4 *
5 * EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
6 * Based on work done by Jonghwan Choi <jhbird.choi@samsung.com>
7 * Support for only EXYNOS5250 is present.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 */
14
15#include <linux/module.h>
16#include <linux/devfreq.h>
17#include <linux/io.h>
18#include <linux/opp.h>
19#include <linux/slab.h>
20#include <linux/suspend.h>
21#include <linux/opp.h>
22#include <linux/clk.h>
23#include <linux/delay.h>
24#include <linux/platform_device.h>
25#include <linux/pm_qos.h>
26#include <linux/regulator/consumer.h>
27#include <linux/of_address.h>
28#include <linux/of_platform.h>
29
30#include "exynos_ppmu.h"
31
32#define MAX_SAFEVOLT 1100000 /* 1.10V */
33/* Assume that the bus is saturated if the utilization is 25% */
34#define INT_BUS_SATURATION_RATIO 25
35
36enum int_level_idx {
37 LV_0,
38 LV_1,
39 LV_2,
40 LV_3,
41 LV_4,
42 _LV_END
43};
44
45enum exynos_ppmu_list {
46 PPMU_RIGHT,
47 PPMU_END,
48};
49
50struct busfreq_data_int {
51 struct device *dev;
52 struct devfreq *devfreq;
53 struct regulator *vdd_int;
54 struct exynos_ppmu ppmu[PPMU_END];
55 unsigned long curr_freq;
56 bool disabled;
57
58 struct notifier_block pm_notifier;
59 struct mutex lock;
60 struct pm_qos_request int_req;
61 struct clk *int_clk;
62};
63
64struct int_bus_opp_table {
65 unsigned int idx;
66 unsigned long clk;
67 unsigned long volt;
68};
69
70static struct int_bus_opp_table exynos5_int_opp_table[] = {
71 {LV_0, 266000, 1025000},
72 {LV_1, 200000, 1025000},
73 {LV_2, 160000, 1025000},
74 {LV_3, 133000, 1025000},
75 {LV_4, 100000, 1025000},
76 {0, 0, 0},
77};
78
79static void busfreq_mon_reset(struct busfreq_data_int *data)
80{
81 unsigned int i;
82
83 for (i = PPMU_RIGHT; i < PPMU_END; i++) {
84 void __iomem *ppmu_base = data->ppmu[i].hw_base;
85
86 /* Reset the performance and cycle counters */
87 exynos_ppmu_reset(ppmu_base);
88
89 /* Setup count registers to monitor read/write transactions */
90 data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
91 exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
92 data->ppmu[i].event[PPMU_PMNCNT3]);
93
94 exynos_ppmu_start(ppmu_base);
95 }
96}
97
98static void exynos5_read_ppmu(struct busfreq_data_int *data)
99{
100 int i, j;
101
102 for (i = PPMU_RIGHT; i < PPMU_END; i++) {
103 void __iomem *ppmu_base = data->ppmu[i].hw_base;
104
105 exynos_ppmu_stop(ppmu_base);
106
107 /* Update local data from PPMU */
108 data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
109
110 for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
111 if (data->ppmu[i].event[j] == 0)
112 data->ppmu[i].count[j] = 0;
113 else
114 data->ppmu[i].count[j] =
115 exynos_ppmu_read(ppmu_base, j);
116 }
117 }
118
119 busfreq_mon_reset(data);
120}
121
122static int exynos5_int_setvolt(struct busfreq_data_int *data,
123 unsigned long volt)
124{
125 return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT);
126}
127
128static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
129 u32 flags)
130{
131 int err = 0;
132 struct platform_device *pdev = container_of(dev, struct platform_device,
133 dev);
134 struct busfreq_data_int *data = platform_get_drvdata(pdev);
135 struct opp *opp;
136 unsigned long old_freq, freq;
137 unsigned long volt;
138
139 rcu_read_lock();
140 opp = devfreq_recommended_opp(dev, _freq, flags);
141 if (IS_ERR(opp)) {
142 rcu_read_unlock();
143 dev_err(dev, "%s: Invalid OPP.\n", __func__);
144 return PTR_ERR(opp);
145 }
146
147 freq = opp_get_freq(opp);
148 volt = opp_get_voltage(opp);
149 rcu_read_unlock();
150
151 old_freq = data->curr_freq;
152
153 if (old_freq == freq)
154 return 0;
155
156 dev_dbg(dev, "targetting %lukHz %luuV\n", freq, volt);
157
158 mutex_lock(&data->lock);
159
160 if (data->disabled)
161 goto out;
162
163 if (freq > exynos5_int_opp_table[0].clk)
164 pm_qos_update_request(&data->int_req, freq * 16 / 1000);
165 else
166 pm_qos_update_request(&data->int_req, -1);
167
168 if (old_freq < freq)
169 err = exynos5_int_setvolt(data, volt);
170 if (err)
171 goto out;
172
173 err = clk_set_rate(data->int_clk, freq * 1000);
174
175 if (err)
176 goto out;
177
178 if (old_freq > freq)
179 err = exynos5_int_setvolt(data, volt);
180 if (err)
181 goto out;
182
183 data->curr_freq = freq;
184out:
185 mutex_unlock(&data->lock);
186 return err;
187}
188
189static int exynos5_get_busier_dmc(struct busfreq_data_int *data)
190{
191 int i, j;
192 int busy = 0;
193 unsigned int temp = 0;
194
195 for (i = PPMU_RIGHT; i < PPMU_END; i++) {
196 for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
197 if (data->ppmu[i].count[j] > temp) {
198 temp = data->ppmu[i].count[j];
199 busy = i;
200 }
201 }
202 }
203
204 return busy;
205}
206
207static int exynos5_int_get_dev_status(struct device *dev,
208 struct devfreq_dev_status *stat)
209{
210 struct platform_device *pdev = container_of(dev, struct platform_device,
211 dev);
212 struct busfreq_data_int *data = platform_get_drvdata(pdev);
213 int busier_dmc;
214
215 exynos5_read_ppmu(data);
216 busier_dmc = exynos5_get_busier_dmc(data);
217
218 stat->current_frequency = data->curr_freq;
219
220 /* Number of cycles spent on memory access */
221 stat->busy_time = data->ppmu[busier_dmc].count[PPMU_PMNCNT3];
222 stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO;
223 stat->total_time = data->ppmu[busier_dmc].ccnt;
224
225 return 0;
226}
227static void exynos5_int_exit(struct device *dev)
228{
229 struct platform_device *pdev = container_of(dev, struct platform_device,
230 dev);
231 struct busfreq_data_int *data = platform_get_drvdata(pdev);
232
233 devfreq_unregister_opp_notifier(dev, data->devfreq);
234}
235
236static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
237 .initial_freq = 160000,
238 .polling_ms = 100,
239 .target = exynos5_busfreq_int_target,
240 .get_dev_status = exynos5_int_get_dev_status,
241 .exit = exynos5_int_exit,
242};
243
244static int exynos5250_init_int_tables(struct busfreq_data_int *data)
245{
246 int i, err = 0;
247
248 for (i = LV_0; i < _LV_END; i++) {
249 err = opp_add(data->dev, exynos5_int_opp_table[i].clk,
250 exynos5_int_opp_table[i].volt);
251 if (err) {
252 dev_err(data->dev, "Cannot add opp entries.\n");
253 return err;
254 }
255 }
256
257 return 0;
258}
259
260static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
261 unsigned long event, void *ptr)
262{
263 struct busfreq_data_int *data = container_of(this,
264 struct busfreq_data_int, pm_notifier);
265 struct opp *opp;
266 unsigned long maxfreq = ULONG_MAX;
267 unsigned long freq;
268 unsigned long volt;
269 int err = 0;
270
271 switch (event) {
272 case PM_SUSPEND_PREPARE:
273 /* Set Fastest and Deactivate DVFS */
274 mutex_lock(&data->lock);
275
276 data->disabled = true;
277
278 rcu_read_lock();
279 opp = opp_find_freq_floor(data->dev, &maxfreq);
280 if (IS_ERR(opp)) {
281 rcu_read_unlock();
282 err = PTR_ERR(opp);
283 goto unlock;
284 }
285 freq = opp_get_freq(opp);
286 volt = opp_get_voltage(opp);
287 rcu_read_unlock();
288
289 err = exynos5_int_setvolt(data, volt);
290 if (err)
291 goto unlock;
292
293 err = clk_set_rate(data->int_clk, freq * 1000);
294
295 if (err)
296 goto unlock;
297
298 data->curr_freq = freq;
299unlock:
300 mutex_unlock(&data->lock);
301 if (err)
302 return NOTIFY_BAD;
303 return NOTIFY_OK;
304 case PM_POST_RESTORE:
305 case PM_POST_SUSPEND:
306 /* Reactivate */
307 mutex_lock(&data->lock);
308 data->disabled = false;
309 mutex_unlock(&data->lock);
310 return NOTIFY_OK;
311 }
312
313 return NOTIFY_DONE;
314}
315
316static int exynos5_busfreq_int_probe(struct platform_device *pdev)
317{
318 struct busfreq_data_int *data;
319 struct opp *opp;
320 struct device *dev = &pdev->dev;
321 struct device_node *np;
322 unsigned long initial_freq;
323 unsigned long initial_volt;
324 int err = 0;
325 int i;
326
327 data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int),
328 GFP_KERNEL);
329 if (data == NULL) {
330 dev_err(dev, "Cannot allocate memory.\n");
331 return -ENOMEM;
332 }
333
334 np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu");
335 if (np == NULL) {
336 pr_err("Unable to find PPMU node\n");
337 return -ENOENT;
338 }
339
340 for (i = PPMU_RIGHT; i < PPMU_END; i++) {
341 /* map PPMU memory region */
342 data->ppmu[i].hw_base = of_iomap(np, i);
343 if (data->ppmu[i].hw_base == NULL) {
344 dev_err(&pdev->dev, "failed to map memory region\n");
345 return -ENOMEM;
346 }
347 }
348 data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event;
349 data->dev = dev;
350 mutex_init(&data->lock);
351
352 err = exynos5250_init_int_tables(data);
353 if (err)
354 goto err_regulator;
355
356 data->vdd_int = regulator_get(dev, "vdd_int");
357 if (IS_ERR(data->vdd_int)) {
358 dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
359 err = PTR_ERR(data->vdd_int);
360 goto err_regulator;
361 }
362
363 data->int_clk = clk_get(dev, "int_clk");
364 if (IS_ERR(data->int_clk)) {
365 dev_err(dev, "Cannot get clock \"int_clk\"\n");
366 err = PTR_ERR(data->int_clk);
367 goto err_clock;
368 }
369
370 rcu_read_lock();
371 opp = opp_find_freq_floor(dev,
372 &exynos5_devfreq_int_profile.initial_freq);
373 if (IS_ERR(opp)) {
374 rcu_read_unlock();
375 dev_err(dev, "Invalid initial frequency %lu kHz.\n",
376 exynos5_devfreq_int_profile.initial_freq);
377 err = PTR_ERR(opp);
378 goto err_opp_add;
379 }
380 initial_freq = opp_get_freq(opp);
381 initial_volt = opp_get_voltage(opp);
382 rcu_read_unlock();
383 data->curr_freq = initial_freq;
384
385 err = clk_set_rate(data->int_clk, initial_freq * 1000);
386 if (err) {
387 dev_err(dev, "Failed to set initial frequency\n");
388 goto err_opp_add;
389 }
390
391 err = exynos5_int_setvolt(data, initial_volt);
392 if (err)
393 goto err_opp_add;
394
395 platform_set_drvdata(pdev, data);
396
397 busfreq_mon_reset(data);
398
399 data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_int_profile,
400 "simple_ondemand", NULL);
401
402 if (IS_ERR(data->devfreq)) {
403 err = PTR_ERR(data->devfreq);
404 goto err_devfreq_add;
405 }
406
407 devfreq_register_opp_notifier(dev, data->devfreq);
408
409 err = register_pm_notifier(&data->pm_notifier);
410 if (err) {
411 dev_err(dev, "Failed to setup pm notifier\n");
412 goto err_devfreq_add;
413 }
414
415 /* TODO: Add a new QOS class for int/mif bus */
416 pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
417
418 return 0;
419
420err_devfreq_add:
421 devfreq_remove_device(data->devfreq);
422 platform_set_drvdata(pdev, NULL);
423err_opp_add:
424 clk_put(data->int_clk);
425err_clock:
426 regulator_put(data->vdd_int);
427err_regulator:
428 return err;
429}
430
431static int exynos5_busfreq_int_remove(struct platform_device *pdev)
432{
433 struct busfreq_data_int *data = platform_get_drvdata(pdev);
434
435 pm_qos_remove_request(&data->int_req);
436 unregister_pm_notifier(&data->pm_notifier);
437 devfreq_remove_device(data->devfreq);
438 regulator_put(data->vdd_int);
439 clk_put(data->int_clk);
440 platform_set_drvdata(pdev, NULL);
441
442 return 0;
443}
444
445static int exynos5_busfreq_int_resume(struct device *dev)
446{
447 struct platform_device *pdev = container_of(dev, struct platform_device,
448 dev);
449 struct busfreq_data_int *data = platform_get_drvdata(pdev);
450
451 busfreq_mon_reset(data);
452 return 0;
453}
454
455static const struct dev_pm_ops exynos5_busfreq_int_pm = {
456 .resume = exynos5_busfreq_int_resume,
457};
458
459/* platform device pointer for exynos5 devfreq device. */
460static struct platform_device *exynos5_devfreq_pdev;
461
462static struct platform_driver exynos5_busfreq_int_driver = {
463 .probe = exynos5_busfreq_int_probe,
464 .remove = exynos5_busfreq_int_remove,
465 .driver = {
466 .name = "exynos5-bus-int",
467 .owner = THIS_MODULE,
468 .pm = &exynos5_busfreq_int_pm,
469 },
470};
471
472static int __init exynos5_busfreq_int_init(void)
473{
474 int ret;
475
476 ret = platform_driver_register(&exynos5_busfreq_int_driver);
477 if (ret < 0)
478 goto out;
479
480 exynos5_devfreq_pdev =
481 platform_device_register_simple("exynos5-bus-int", -1, NULL, 0);
482 if (IS_ERR_OR_NULL(exynos5_devfreq_pdev)) {
483 ret = PTR_ERR(exynos5_devfreq_pdev);
484 goto out1;
485 }
486
487 return 0;
488out1:
489 platform_driver_unregister(&exynos5_busfreq_int_driver);
490out:
491 return ret;
492}
493late_initcall(exynos5_busfreq_int_init);
494
495static void __exit exynos5_busfreq_int_exit(void)
496{
497 platform_device_unregister(exynos5_devfreq_pdev);
498 platform_driver_unregister(&exynos5_busfreq_int_driver);
499}
500module_exit(exynos5_busfreq_int_exit);
501
502MODULE_LICENSE("GPL");
503MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework");