Commit | Line | Data |
---|---|---|
6ccce699 AK |
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> | |
e4db1c74 | 18 | #include <linux/pm_opp.h> |
6ccce699 AK |
19 | #include <linux/slab.h> |
20 | #include <linux/suspend.h> | |
6ccce699 AK |
21 | #include <linux/clk.h> |
22 | #include <linux/delay.h> | |
23 | #include <linux/platform_device.h> | |
24 | #include <linux/pm_qos.h> | |
25 | #include <linux/regulator/consumer.h> | |
26 | #include <linux/of_address.h> | |
27 | #include <linux/of_platform.h> | |
28 | ||
29 | #include "exynos_ppmu.h" | |
30 | ||
31 | #define MAX_SAFEVOLT 1100000 /* 1.10V */ | |
32 | /* Assume that the bus is saturated if the utilization is 25% */ | |
33 | #define INT_BUS_SATURATION_RATIO 25 | |
34 | ||
35 | enum int_level_idx { | |
36 | LV_0, | |
37 | LV_1, | |
38 | LV_2, | |
39 | LV_3, | |
40 | LV_4, | |
41 | _LV_END | |
42 | }; | |
43 | ||
44 | enum exynos_ppmu_list { | |
45 | PPMU_RIGHT, | |
46 | PPMU_END, | |
47 | }; | |
48 | ||
49 | struct busfreq_data_int { | |
50 | struct device *dev; | |
51 | struct devfreq *devfreq; | |
52 | struct regulator *vdd_int; | |
a94f6b4a | 53 | struct busfreq_ppmu_data ppmu_data; |
6ccce699 AK |
54 | unsigned long curr_freq; |
55 | bool disabled; | |
56 | ||
57 | struct notifier_block pm_notifier; | |
58 | struct mutex lock; | |
59 | struct pm_qos_request int_req; | |
60 | struct clk *int_clk; | |
61 | }; | |
62 | ||
63 | struct int_bus_opp_table { | |
64 | unsigned int idx; | |
65 | unsigned long clk; | |
66 | unsigned long volt; | |
67 | }; | |
68 | ||
69 | static struct int_bus_opp_table exynos5_int_opp_table[] = { | |
70 | {LV_0, 266000, 1025000}, | |
71 | {LV_1, 200000, 1025000}, | |
72 | {LV_2, 160000, 1025000}, | |
73 | {LV_3, 133000, 1025000}, | |
74 | {LV_4, 100000, 1025000}, | |
75 | {0, 0, 0}, | |
76 | }; | |
77 | ||
6ccce699 AK |
78 | static int exynos5_int_setvolt(struct busfreq_data_int *data, |
79 | unsigned long volt) | |
80 | { | |
81 | return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT); | |
82 | } | |
83 | ||
84 | static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq, | |
85 | u32 flags) | |
86 | { | |
87 | int err = 0; | |
88 | struct platform_device *pdev = container_of(dev, struct platform_device, | |
89 | dev); | |
90 | struct busfreq_data_int *data = platform_get_drvdata(pdev); | |
47d43ba7 | 91 | struct dev_pm_opp *opp; |
6ccce699 AK |
92 | unsigned long old_freq, freq; |
93 | unsigned long volt; | |
94 | ||
95 | rcu_read_lock(); | |
96 | opp = devfreq_recommended_opp(dev, _freq, flags); | |
97 | if (IS_ERR(opp)) { | |
98 | rcu_read_unlock(); | |
99 | dev_err(dev, "%s: Invalid OPP.\n", __func__); | |
100 | return PTR_ERR(opp); | |
101 | } | |
102 | ||
5d4879cd NM |
103 | freq = dev_pm_opp_get_freq(opp); |
104 | volt = dev_pm_opp_get_voltage(opp); | |
6ccce699 AK |
105 | rcu_read_unlock(); |
106 | ||
107 | old_freq = data->curr_freq; | |
108 | ||
109 | if (old_freq == freq) | |
110 | return 0; | |
111 | ||
77d84ff8 | 112 | dev_dbg(dev, "targeting %lukHz %luuV\n", freq, volt); |
6ccce699 AK |
113 | |
114 | mutex_lock(&data->lock); | |
115 | ||
116 | if (data->disabled) | |
117 | goto out; | |
118 | ||
119 | if (freq > exynos5_int_opp_table[0].clk) | |
120 | pm_qos_update_request(&data->int_req, freq * 16 / 1000); | |
121 | else | |
122 | pm_qos_update_request(&data->int_req, -1); | |
123 | ||
124 | if (old_freq < freq) | |
125 | err = exynos5_int_setvolt(data, volt); | |
126 | if (err) | |
127 | goto out; | |
128 | ||
129 | err = clk_set_rate(data->int_clk, freq * 1000); | |
130 | ||
131 | if (err) | |
132 | goto out; | |
133 | ||
134 | if (old_freq > freq) | |
135 | err = exynos5_int_setvolt(data, volt); | |
136 | if (err) | |
137 | goto out; | |
138 | ||
139 | data->curr_freq = freq; | |
140 | out: | |
141 | mutex_unlock(&data->lock); | |
142 | return err; | |
143 | } | |
144 | ||
6ccce699 AK |
145 | static int exynos5_int_get_dev_status(struct device *dev, |
146 | struct devfreq_dev_status *stat) | |
147 | { | |
148 | struct platform_device *pdev = container_of(dev, struct platform_device, | |
149 | dev); | |
150 | struct busfreq_data_int *data = platform_get_drvdata(pdev); | |
a94f6b4a | 151 | struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; |
6ccce699 AK |
152 | int busier_dmc; |
153 | ||
26d51853 BZ |
154 | exynos_read_ppmu(ppmu_data); |
155 | busier_dmc = exynos_get_busier_ppmu(ppmu_data); | |
6ccce699 AK |
156 | |
157 | stat->current_frequency = data->curr_freq; | |
158 | ||
159 | /* Number of cycles spent on memory access */ | |
a94f6b4a | 160 | stat->busy_time = ppmu_data->ppmu[busier_dmc].count[PPMU_PMNCNT3]; |
6ccce699 | 161 | stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO; |
a94f6b4a | 162 | stat->total_time = ppmu_data->ppmu[busier_dmc].ccnt; |
6ccce699 AK |
163 | |
164 | return 0; | |
165 | } | |
6ccce699 AK |
166 | |
167 | static struct devfreq_dev_profile exynos5_devfreq_int_profile = { | |
168 | .initial_freq = 160000, | |
169 | .polling_ms = 100, | |
170 | .target = exynos5_busfreq_int_target, | |
171 | .get_dev_status = exynos5_int_get_dev_status, | |
6ccce699 AK |
172 | }; |
173 | ||
174 | static int exynos5250_init_int_tables(struct busfreq_data_int *data) | |
175 | { | |
176 | int i, err = 0; | |
177 | ||
178 | for (i = LV_0; i < _LV_END; i++) { | |
5d4879cd | 179 | err = dev_pm_opp_add(data->dev, exynos5_int_opp_table[i].clk, |
6ccce699 AK |
180 | exynos5_int_opp_table[i].volt); |
181 | if (err) { | |
182 | dev_err(data->dev, "Cannot add opp entries.\n"); | |
183 | return err; | |
184 | } | |
185 | } | |
186 | ||
187 | return 0; | |
188 | } | |
189 | ||
190 | static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this, | |
191 | unsigned long event, void *ptr) | |
192 | { | |
193 | struct busfreq_data_int *data = container_of(this, | |
194 | struct busfreq_data_int, pm_notifier); | |
47d43ba7 | 195 | struct dev_pm_opp *opp; |
6ccce699 AK |
196 | unsigned long maxfreq = ULONG_MAX; |
197 | unsigned long freq; | |
198 | unsigned long volt; | |
199 | int err = 0; | |
200 | ||
201 | switch (event) { | |
202 | case PM_SUSPEND_PREPARE: | |
203 | /* Set Fastest and Deactivate DVFS */ | |
204 | mutex_lock(&data->lock); | |
205 | ||
206 | data->disabled = true; | |
207 | ||
208 | rcu_read_lock(); | |
5d4879cd | 209 | opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq); |
6ccce699 AK |
210 | if (IS_ERR(opp)) { |
211 | rcu_read_unlock(); | |
212 | err = PTR_ERR(opp); | |
213 | goto unlock; | |
214 | } | |
5d4879cd NM |
215 | freq = dev_pm_opp_get_freq(opp); |
216 | volt = dev_pm_opp_get_voltage(opp); | |
6ccce699 AK |
217 | rcu_read_unlock(); |
218 | ||
219 | err = exynos5_int_setvolt(data, volt); | |
220 | if (err) | |
221 | goto unlock; | |
222 | ||
223 | err = clk_set_rate(data->int_clk, freq * 1000); | |
224 | ||
225 | if (err) | |
226 | goto unlock; | |
227 | ||
228 | data->curr_freq = freq; | |
229 | unlock: | |
230 | mutex_unlock(&data->lock); | |
231 | if (err) | |
232 | return NOTIFY_BAD; | |
233 | return NOTIFY_OK; | |
234 | case PM_POST_RESTORE: | |
235 | case PM_POST_SUSPEND: | |
236 | /* Reactivate */ | |
237 | mutex_lock(&data->lock); | |
238 | data->disabled = false; | |
239 | mutex_unlock(&data->lock); | |
240 | return NOTIFY_OK; | |
241 | } | |
242 | ||
243 | return NOTIFY_DONE; | |
244 | } | |
245 | ||
246 | static int exynos5_busfreq_int_probe(struct platform_device *pdev) | |
247 | { | |
248 | struct busfreq_data_int *data; | |
a94f6b4a | 249 | struct busfreq_ppmu_data *ppmu_data; |
47d43ba7 | 250 | struct dev_pm_opp *opp; |
6ccce699 AK |
251 | struct device *dev = &pdev->dev; |
252 | struct device_node *np; | |
253 | unsigned long initial_freq; | |
254 | unsigned long initial_volt; | |
255 | int err = 0; | |
256 | int i; | |
257 | ||
258 | data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int), | |
259 | GFP_KERNEL); | |
260 | if (data == NULL) { | |
261 | dev_err(dev, "Cannot allocate memory.\n"); | |
262 | return -ENOMEM; | |
263 | } | |
264 | ||
a94f6b4a BZ |
265 | ppmu_data = &data->ppmu_data; |
266 | ppmu_data->ppmu_end = PPMU_END; | |
267 | ppmu_data->ppmu = devm_kzalloc(dev, | |
268 | sizeof(struct exynos_ppmu) * PPMU_END, | |
269 | GFP_KERNEL); | |
270 | if (!ppmu_data->ppmu) { | |
271 | dev_err(dev, "Failed to allocate memory for exynos_ppmu\n"); | |
272 | return -ENOMEM; | |
273 | } | |
274 | ||
6ccce699 AK |
275 | np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu"); |
276 | if (np == NULL) { | |
277 | pr_err("Unable to find PPMU node\n"); | |
278 | return -ENOENT; | |
279 | } | |
280 | ||
a94f6b4a | 281 | for (i = 0; i < ppmu_data->ppmu_end; i++) { |
6ccce699 | 282 | /* map PPMU memory region */ |
a94f6b4a BZ |
283 | ppmu_data->ppmu[i].hw_base = of_iomap(np, i); |
284 | if (ppmu_data->ppmu[i].hw_base == NULL) { | |
6ccce699 AK |
285 | dev_err(&pdev->dev, "failed to map memory region\n"); |
286 | return -ENOMEM; | |
287 | } | |
288 | } | |
289 | data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event; | |
290 | data->dev = dev; | |
291 | mutex_init(&data->lock); | |
292 | ||
293 | err = exynos5250_init_int_tables(data); | |
294 | if (err) | |
02844f74 | 295 | return err; |
6ccce699 | 296 | |
02844f74 | 297 | data->vdd_int = devm_regulator_get(dev, "vdd_int"); |
6ccce699 AK |
298 | if (IS_ERR(data->vdd_int)) { |
299 | dev_err(dev, "Cannot get the regulator \"vdd_int\"\n"); | |
02844f74 | 300 | return PTR_ERR(data->vdd_int); |
6ccce699 AK |
301 | } |
302 | ||
02844f74 | 303 | data->int_clk = devm_clk_get(dev, "int_clk"); |
6ccce699 AK |
304 | if (IS_ERR(data->int_clk)) { |
305 | dev_err(dev, "Cannot get clock \"int_clk\"\n"); | |
02844f74 | 306 | return PTR_ERR(data->int_clk); |
6ccce699 AK |
307 | } |
308 | ||
309 | rcu_read_lock(); | |
5d4879cd | 310 | opp = dev_pm_opp_find_freq_floor(dev, |
6ccce699 AK |
311 | &exynos5_devfreq_int_profile.initial_freq); |
312 | if (IS_ERR(opp)) { | |
313 | rcu_read_unlock(); | |
314 | dev_err(dev, "Invalid initial frequency %lu kHz.\n", | |
315 | exynos5_devfreq_int_profile.initial_freq); | |
02844f74 | 316 | return PTR_ERR(opp); |
6ccce699 | 317 | } |
5d4879cd NM |
318 | initial_freq = dev_pm_opp_get_freq(opp); |
319 | initial_volt = dev_pm_opp_get_voltage(opp); | |
6ccce699 AK |
320 | rcu_read_unlock(); |
321 | data->curr_freq = initial_freq; | |
322 | ||
323 | err = clk_set_rate(data->int_clk, initial_freq * 1000); | |
324 | if (err) { | |
325 | dev_err(dev, "Failed to set initial frequency\n"); | |
02844f74 | 326 | return err; |
6ccce699 AK |
327 | } |
328 | ||
329 | err = exynos5_int_setvolt(data, initial_volt); | |
330 | if (err) | |
02844f74 | 331 | return err; |
6ccce699 AK |
332 | |
333 | platform_set_drvdata(pdev, data); | |
334 | ||
a94f6b4a | 335 | busfreq_mon_reset(ppmu_data); |
6ccce699 | 336 | |
2456963c | 337 | data->devfreq = devm_devfreq_add_device(dev, &exynos5_devfreq_int_profile, |
6ccce699 | 338 | "simple_ondemand", NULL); |
2456963c CC |
339 | if (IS_ERR(data->devfreq)) |
340 | return PTR_ERR(data->devfreq); | |
6ccce699 | 341 | |
2456963c CC |
342 | err = devm_devfreq_register_opp_notifier(dev, data->devfreq); |
343 | if (err < 0) { | |
344 | dev_err(dev, "Failed to register opp notifier\n"); | |
345 | return err; | |
6ccce699 AK |
346 | } |
347 | ||
6ccce699 AK |
348 | err = register_pm_notifier(&data->pm_notifier); |
349 | if (err) { | |
350 | dev_err(dev, "Failed to setup pm notifier\n"); | |
2456963c | 351 | return err; |
6ccce699 AK |
352 | } |
353 | ||
354 | /* TODO: Add a new QOS class for int/mif bus */ | |
355 | pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1); | |
356 | ||
357 | return 0; | |
6ccce699 AK |
358 | } |
359 | ||
360 | static int exynos5_busfreq_int_remove(struct platform_device *pdev) | |
361 | { | |
362 | struct busfreq_data_int *data = platform_get_drvdata(pdev); | |
363 | ||
364 | pm_qos_remove_request(&data->int_req); | |
365 | unregister_pm_notifier(&data->pm_notifier); | |
6ccce699 AK |
366 | |
367 | return 0; | |
368 | } | |
369 | ||
edb06a2d | 370 | #ifdef CONFIG_PM_SLEEP |
6ccce699 AK |
371 | static int exynos5_busfreq_int_resume(struct device *dev) |
372 | { | |
373 | struct platform_device *pdev = container_of(dev, struct platform_device, | |
374 | dev); | |
375 | struct busfreq_data_int *data = platform_get_drvdata(pdev); | |
a94f6b4a | 376 | struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; |
6ccce699 | 377 | |
a94f6b4a | 378 | busfreq_mon_reset(ppmu_data); |
6ccce699 AK |
379 | return 0; |
380 | } | |
6ccce699 AK |
381 | static const struct dev_pm_ops exynos5_busfreq_int_pm = { |
382 | .resume = exynos5_busfreq_int_resume, | |
383 | }; | |
edb06a2d CC |
384 | #endif |
385 | static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm_ops, NULL, | |
386 | exynos5_busfreq_int_resume); | |
6ccce699 AK |
387 | |
388 | /* platform device pointer for exynos5 devfreq device. */ | |
389 | static struct platform_device *exynos5_devfreq_pdev; | |
390 | ||
391 | static struct platform_driver exynos5_busfreq_int_driver = { | |
392 | .probe = exynos5_busfreq_int_probe, | |
393 | .remove = exynos5_busfreq_int_remove, | |
394 | .driver = { | |
395 | .name = "exynos5-bus-int", | |
edb06a2d | 396 | .pm = &exynos5_busfreq_int_pm_ops, |
6ccce699 AK |
397 | }, |
398 | }; | |
399 | ||
400 | static int __init exynos5_busfreq_int_init(void) | |
401 | { | |
402 | int ret; | |
403 | ||
404 | ret = platform_driver_register(&exynos5_busfreq_int_driver); | |
405 | if (ret < 0) | |
406 | goto out; | |
407 | ||
408 | exynos5_devfreq_pdev = | |
409 | platform_device_register_simple("exynos5-bus-int", -1, NULL, 0); | |
8ab8831a | 410 | if (IS_ERR(exynos5_devfreq_pdev)) { |
6ccce699 AK |
411 | ret = PTR_ERR(exynos5_devfreq_pdev); |
412 | goto out1; | |
413 | } | |
414 | ||
415 | return 0; | |
416 | out1: | |
417 | platform_driver_unregister(&exynos5_busfreq_int_driver); | |
418 | out: | |
419 | return ret; | |
420 | } | |
421 | late_initcall(exynos5_busfreq_int_init); | |
422 | ||
423 | static void __exit exynos5_busfreq_int_exit(void) | |
424 | { | |
425 | platform_device_unregister(exynos5_devfreq_pdev); | |
426 | platform_driver_unregister(&exynos5_busfreq_int_driver); | |
427 | } | |
428 | module_exit(exynos5_busfreq_int_exit); | |
429 | ||
430 | MODULE_LICENSE("GPL"); | |
431 | MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework"); |