diff options
-rw-r--r-- | Documentation/ABI/testing/sysfs-class-devfreq | 20 | ||||
-rw-r--r-- | MAINTAINERS | 2 | ||||
-rw-r--r-- | drivers/devfreq/Kconfig | 12 | ||||
-rw-r--r-- | drivers/devfreq/Makefile | 3 | ||||
-rw-r--r-- | drivers/devfreq/devfreq.c | 25 | ||||
-rw-r--r-- | drivers/devfreq/exynos/Makefile | 3 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos4_bus.c (renamed from drivers/devfreq/exynos4_bus.c) | 1 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos5_bus.c | 503 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos_ppmu.c | 56 | ||||
-rw-r--r-- | drivers/devfreq/exynos/exynos_ppmu.h | 78 | ||||
-rw-r--r-- | include/linux/devfreq.h | 2 |
11 files changed, 699 insertions, 6 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-devfreq b/Documentation/ABI/testing/sysfs-class-devfreq index 0ba6ea2f89d9..ee39acacf6f8 100644 --- a/Documentation/ABI/testing/sysfs-class-devfreq +++ b/Documentation/ABI/testing/sysfs-class-devfreq | |||
@@ -78,3 +78,23 @@ Contact: Nishanth Menon <nm@ti.com> | |||
78 | Description: | 78 | Description: |
79 | The /sys/class/devfreq/.../available_governors shows | 79 | The /sys/class/devfreq/.../available_governors shows |
80 | currently available governors in the system. | 80 | currently available governors in the system. |
81 | |||
82 | What: /sys/class/devfreq/.../min_freq | ||
83 | Date: January 2013 | ||
84 | Contact: MyungJoo Ham <myungjoo.ham@samsung.com> | ||
85 | Description: | ||
86 | The /sys/class/devfreq/.../min_freq shows and stores | ||
87 | the minimum frequency requested by users. It is 0 if | ||
88 | the user does not care. min_freq overrides the | ||
89 | frequency requested by governors. | ||
90 | |||
91 | What: /sys/class/devfreq/.../max_freq | ||
92 | Date: January 2013 | ||
93 | Contact: MyungJoo Ham <myungjoo.ham@samsung.com> | ||
94 | Description: | ||
95 | The /sys/class/devfreq/.../max_freq shows and stores | ||
96 | the maximum frequency requested by users. It is 0 if | ||
97 | the user does not care. max_freq overrides the | ||
98 | frequency requested by governors and min_freq. | ||
99 | The max_freq overrides min_freq because max_freq may be | ||
100 | used to throttle devices to avoid overheating. | ||
diff --git a/MAINTAINERS b/MAINTAINERS index 1ad3d8c3ed83..ac6c8994714c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -2519,7 +2519,7 @@ F: drivers/usb/dwc3/ | |||
2519 | DEVICE FREQUENCY (DEVFREQ) | 2519 | DEVICE FREQUENCY (DEVFREQ) |
2520 | M: MyungJoo Ham <myungjoo.ham@samsung.com> | 2520 | M: MyungJoo Ham <myungjoo.ham@samsung.com> |
2521 | M: Kyungmin Park <kyungmin.park@samsung.com> | 2521 | M: Kyungmin Park <kyungmin.park@samsung.com> |
2522 | L: linux-kernel@vger.kernel.org | 2522 | L: linux-pm@vger.kernel.org |
2523 | S: Maintained | 2523 | S: Maintained |
2524 | F: drivers/devfreq/ | 2524 | F: drivers/devfreq/ |
2525 | 2525 | ||
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 0f079be13305..31f3adba4cf3 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig | |||
@@ -67,7 +67,7 @@ comment "DEVFREQ Drivers" | |||
67 | 67 | ||
68 | config ARM_EXYNOS4_BUS_DEVFREQ | 68 | config ARM_EXYNOS4_BUS_DEVFREQ |
69 | bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver" | 69 | bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver" |
70 | depends on CPU_EXYNOS4210 || CPU_EXYNOS4212 || CPU_EXYNOS4412 | 70 | depends on CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412 |
71 | select ARCH_HAS_OPP | 71 | select ARCH_HAS_OPP |
72 | select DEVFREQ_GOV_SIMPLE_ONDEMAND | 72 | select DEVFREQ_GOV_SIMPLE_ONDEMAND |
73 | help | 73 | help |
@@ -78,4 +78,14 @@ config ARM_EXYNOS4_BUS_DEVFREQ | |||
78 | To operate with optimal voltages, ASV support is required | 78 | To operate with optimal voltages, ASV support is required |
79 | (CONFIG_EXYNOS_ASV). | 79 | (CONFIG_EXYNOS_ASV). |
80 | 80 | ||
81 | config ARM_EXYNOS5_BUS_DEVFREQ | ||
82 | bool "ARM Exynos5250 Bus DEVFREQ Driver" | ||
83 | depends on SOC_EXYNOS5250 | ||
84 | select ARCH_HAS_OPP | ||
85 | select DEVFREQ_GOV_SIMPLE_ONDEMAND | ||
86 | help | ||
87 | This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int). | ||
88 | It reads PPMU counters of memory controllers and adjusts the | ||
89 | operating frequencies and voltages with OPP support. | ||
90 | |||
81 | endif # PM_DEVFREQ | 91 | endif # PM_DEVFREQ |
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 8c464234f7e7..16138c9e0d58 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile | |||
@@ -5,4 +5,5 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o | |||
5 | obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o | 5 | obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o |
6 | 6 | ||
7 | # DEVFREQ Drivers | 7 | # DEVFREQ Drivers |
8 | obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o | 8 | obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/ |
9 | obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/ | ||
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 3b367973a802..44c407986f64 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c | |||
@@ -271,6 +271,7 @@ void devfreq_monitor_suspend(struct devfreq *devfreq) | |||
271 | return; | 271 | return; |
272 | } | 272 | } |
273 | 273 | ||
274 | devfreq_update_status(devfreq, devfreq->previous_freq); | ||
274 | devfreq->stop_polling = true; | 275 | devfreq->stop_polling = true; |
275 | mutex_unlock(&devfreq->lock); | 276 | mutex_unlock(&devfreq->lock); |
276 | cancel_delayed_work_sync(&devfreq->work); | 277 | cancel_delayed_work_sync(&devfreq->work); |
@@ -287,6 +288,8 @@ EXPORT_SYMBOL(devfreq_monitor_suspend); | |||
287 | */ | 288 | */ |
288 | void devfreq_monitor_resume(struct devfreq *devfreq) | 289 | void devfreq_monitor_resume(struct devfreq *devfreq) |
289 | { | 290 | { |
291 | unsigned long freq; | ||
292 | |||
290 | mutex_lock(&devfreq->lock); | 293 | mutex_lock(&devfreq->lock); |
291 | if (!devfreq->stop_polling) | 294 | if (!devfreq->stop_polling) |
292 | goto out; | 295 | goto out; |
@@ -295,8 +298,14 @@ void devfreq_monitor_resume(struct devfreq *devfreq) | |||
295 | devfreq->profile->polling_ms) | 298 | devfreq->profile->polling_ms) |
296 | queue_delayed_work(devfreq_wq, &devfreq->work, | 299 | queue_delayed_work(devfreq_wq, &devfreq->work, |
297 | msecs_to_jiffies(devfreq->profile->polling_ms)); | 300 | msecs_to_jiffies(devfreq->profile->polling_ms)); |
301 | |||
302 | devfreq->last_stat_updated = jiffies; | ||
298 | devfreq->stop_polling = false; | 303 | devfreq->stop_polling = false; |
299 | 304 | ||
305 | if (devfreq->profile->get_cur_freq && | ||
306 | !devfreq->profile->get_cur_freq(devfreq->dev.parent, &freq)) | ||
307 | devfreq->previous_freq = freq; | ||
308 | |||
300 | out: | 309 | out: |
301 | mutex_unlock(&devfreq->lock); | 310 | mutex_unlock(&devfreq->lock); |
302 | } | 311 | } |
@@ -518,6 +527,8 @@ EXPORT_SYMBOL(devfreq_add_device); | |||
518 | /** | 527 | /** |
519 | * devfreq_remove_device() - Remove devfreq feature from a device. | 528 | * devfreq_remove_device() - Remove devfreq feature from a device. |
520 | * @devfreq: the devfreq instance to be removed | 529 | * @devfreq: the devfreq instance to be removed |
530 | * | ||
531 | * The opposite of devfreq_add_device(). | ||
521 | */ | 532 | */ |
522 | int devfreq_remove_device(struct devfreq *devfreq) | 533 | int devfreq_remove_device(struct devfreq *devfreq) |
523 | { | 534 | { |
@@ -533,6 +544,10 @@ EXPORT_SYMBOL(devfreq_remove_device); | |||
533 | /** | 544 | /** |
534 | * devfreq_suspend_device() - Suspend devfreq of a device. | 545 | * devfreq_suspend_device() - Suspend devfreq of a device. |
535 | * @devfreq: the devfreq instance to be suspended | 546 | * @devfreq: the devfreq instance to be suspended |
547 | * | ||
548 | * This function is intended to be called by the pm callbacks | ||
549 | * (e.g., runtime_suspend, suspend) of the device driver that | ||
550 | * holds the devfreq. | ||
536 | */ | 551 | */ |
537 | int devfreq_suspend_device(struct devfreq *devfreq) | 552 | int devfreq_suspend_device(struct devfreq *devfreq) |
538 | { | 553 | { |
@@ -550,6 +565,10 @@ EXPORT_SYMBOL(devfreq_suspend_device); | |||
550 | /** | 565 | /** |
551 | * devfreq_resume_device() - Resume devfreq of a device. | 566 | * devfreq_resume_device() - Resume devfreq of a device. |
552 | * @devfreq: the devfreq instance to be resumed | 567 | * @devfreq: the devfreq instance to be resumed |
568 | * | ||
569 | * This function is intended to be called by the pm callbacks | ||
570 | * (e.g., runtime_resume, resume) of the device driver that | ||
571 | * holds the devfreq. | ||
553 | */ | 572 | */ |
554 | int devfreq_resume_device(struct devfreq *devfreq) | 573 | int devfreq_resume_device(struct devfreq *devfreq) |
555 | { | 574 | { |
@@ -905,11 +924,11 @@ static ssize_t show_trans_table(struct device *dev, struct device_attribute *att | |||
905 | { | 924 | { |
906 | struct devfreq *devfreq = to_devfreq(dev); | 925 | struct devfreq *devfreq = to_devfreq(dev); |
907 | ssize_t len; | 926 | ssize_t len; |
908 | int i, j, err; | 927 | int i, j; |
909 | unsigned int max_state = devfreq->profile->max_state; | 928 | unsigned int max_state = devfreq->profile->max_state; |
910 | 929 | ||
911 | err = devfreq_update_status(devfreq, devfreq->previous_freq); | 930 | if (!devfreq->stop_polling && |
912 | if (err) | 931 | devfreq_update_status(devfreq, devfreq->previous_freq)) |
913 | return 0; | 932 | return 0; |
914 | 933 | ||
915 | len = sprintf(buf, " From : To\n"); | 934 | len = sprintf(buf, " From : To\n"); |
diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile new file mode 100644 index 000000000000..bfaaf5b0d61d --- /dev/null +++ b/drivers/devfreq/exynos/Makefile | |||
@@ -0,0 +1,3 @@ | |||
1 | # Exynos DEVFREQ Drivers | ||
2 | obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o | ||
3 | obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o | ||
diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos/exynos4_bus.c index 3f37f3b3f268..c5f86d8caca3 100644 --- a/drivers/devfreq/exynos4_bus.c +++ b/drivers/devfreq/exynos/exynos4_bus.c | |||
@@ -974,6 +974,7 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this, | |||
974 | rcu_read_unlock(); | 974 | rcu_read_unlock(); |
975 | dev_err(data->dev, "%s: unable to find a min freq\n", | 975 | dev_err(data->dev, "%s: unable to find a min freq\n", |
976 | __func__); | 976 | __func__); |
977 | mutex_unlock(&data->lock); | ||
977 | return PTR_ERR(opp); | 978 | return PTR_ERR(opp); |
978 | } | 979 | } |
979 | new_oppinfo.rate = opp_get_freq(opp); | 980 | new_oppinfo.rate = opp_get_freq(opp); |
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 | |||
36 | enum int_level_idx { | ||
37 | LV_0, | ||
38 | LV_1, | ||
39 | LV_2, | ||
40 | LV_3, | ||
41 | LV_4, | ||
42 | _LV_END | ||
43 | }; | ||
44 | |||
45 | enum exynos_ppmu_list { | ||
46 | PPMU_RIGHT, | ||
47 | PPMU_END, | ||
48 | }; | ||
49 | |||
50 | struct 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 | |||
64 | struct int_bus_opp_table { | ||
65 | unsigned int idx; | ||
66 | unsigned long clk; | ||
67 | unsigned long volt; | ||
68 | }; | ||
69 | |||
70 | static 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 | |||
79 | static 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 | |||
98 | static 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 | |||
122 | static 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 | |||
128 | static 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; | ||
184 | out: | ||
185 | mutex_unlock(&data->lock); | ||
186 | return err; | ||
187 | } | ||
188 | |||
189 | static 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 | |||
207 | static 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 | } | ||
227 | static 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 | |||
236 | static 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 | |||
244 | static 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 | |||
260 | static 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; | ||
299 | unlock: | ||
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 | |||
316 | static 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 | |||
420 | err_devfreq_add: | ||
421 | devfreq_remove_device(data->devfreq); | ||
422 | platform_set_drvdata(pdev, NULL); | ||
423 | err_opp_add: | ||
424 | clk_put(data->int_clk); | ||
425 | err_clock: | ||
426 | regulator_put(data->vdd_int); | ||
427 | err_regulator: | ||
428 | return err; | ||
429 | } | ||
430 | |||
431 | static 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 | |||
445 | static 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 | |||
455 | static 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. */ | ||
460 | static struct platform_device *exynos5_devfreq_pdev; | ||
461 | |||
462 | static 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 | |||
472 | static 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; | ||
488 | out1: | ||
489 | platform_driver_unregister(&exynos5_busfreq_int_driver); | ||
490 | out: | ||
491 | return ret; | ||
492 | } | ||
493 | late_initcall(exynos5_busfreq_int_init); | ||
494 | |||
495 | static void __exit exynos5_busfreq_int_exit(void) | ||
496 | { | ||
497 | platform_device_unregister(exynos5_devfreq_pdev); | ||
498 | platform_driver_unregister(&exynos5_busfreq_int_driver); | ||
499 | } | ||
500 | module_exit(exynos5_busfreq_int_exit); | ||
501 | |||
502 | MODULE_LICENSE("GPL"); | ||
503 | MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework"); | ||
diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c new file mode 100644 index 000000000000..85fc5ac1036a --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.c | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2012 Samsung Electronics Co., Ltd. | ||
3 | * http://www.samsung.com/ | ||
4 | * | ||
5 | * EXYNOS - PPMU support | ||
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 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/types.h> | ||
14 | #include <linux/io.h> | ||
15 | |||
16 | #include "exynos_ppmu.h" | ||
17 | |||
18 | void exynos_ppmu_reset(void __iomem *ppmu_base) | ||
19 | { | ||
20 | __raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base); | ||
21 | __raw_writel(PPMU_ENABLE_CYCLE | | ||
22 | PPMU_ENABLE_COUNT0 | | ||
23 | PPMU_ENABLE_COUNT1 | | ||
24 | PPMU_ENABLE_COUNT2 | | ||
25 | PPMU_ENABLE_COUNT3, | ||
26 | ppmu_base + PPMU_CNTENS); | ||
27 | } | ||
28 | |||
29 | void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch, | ||
30 | unsigned int evt) | ||
31 | { | ||
32 | __raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch)); | ||
33 | } | ||
34 | |||
35 | void exynos_ppmu_start(void __iomem *ppmu_base) | ||
36 | { | ||
37 | __raw_writel(PPMU_ENABLE, ppmu_base); | ||
38 | } | ||
39 | |||
40 | void exynos_ppmu_stop(void __iomem *ppmu_base) | ||
41 | { | ||
42 | __raw_writel(PPMU_DISABLE, ppmu_base); | ||
43 | } | ||
44 | |||
45 | unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch) | ||
46 | { | ||
47 | unsigned int total; | ||
48 | |||
49 | if (ch == PPMU_PMNCNT3) | ||
50 | total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) | | ||
51 | __raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1))); | ||
52 | else | ||
53 | total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch)); | ||
54 | |||
55 | return total; | ||
56 | } | ||
diff --git a/drivers/devfreq/exynos/exynos_ppmu.h b/drivers/devfreq/exynos/exynos_ppmu.h new file mode 100644 index 000000000000..7dfb221eaccd --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.h | |||
@@ -0,0 +1,78 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2012 Samsung Electronics Co., Ltd. | ||
3 | * http://www.samsung.com/ | ||
4 | * | ||
5 | * EXYNOS PPMU header | ||
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 | |||
12 | #ifndef __DEVFREQ_EXYNOS_PPMU_H | ||
13 | #define __DEVFREQ_EXYNOS_PPMU_H __FILE__ | ||
14 | |||
15 | #include <linux/ktime.h> | ||
16 | |||
17 | /* For PPMU Control */ | ||
18 | #define PPMU_ENABLE BIT(0) | ||
19 | #define PPMU_DISABLE 0x0 | ||
20 | #define PPMU_CYCLE_RESET BIT(1) | ||
21 | #define PPMU_COUNTER_RESET BIT(2) | ||
22 | |||
23 | #define PPMU_ENABLE_COUNT0 BIT(0) | ||
24 | #define PPMU_ENABLE_COUNT1 BIT(1) | ||
25 | #define PPMU_ENABLE_COUNT2 BIT(2) | ||
26 | #define PPMU_ENABLE_COUNT3 BIT(3) | ||
27 | #define PPMU_ENABLE_CYCLE BIT(31) | ||
28 | |||
29 | #define PPMU_CNTENS 0x10 | ||
30 | #define PPMU_FLAG 0x50 | ||
31 | #define PPMU_CCNT_OVERFLOW BIT(31) | ||
32 | #define PPMU_CCNT 0x100 | ||
33 | |||
34 | #define PPMU_PMCNT0 0x110 | ||
35 | #define PPMU_PMCNT_OFFSET 0x10 | ||
36 | #define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x)) | ||
37 | |||
38 | #define PPMU_BEVT0SEL 0x1000 | ||
39 | #define PPMU_BEVTSEL_OFFSET 0x100 | ||
40 | #define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET)) | ||
41 | |||
42 | /* For Event Selection */ | ||
43 | #define RD_DATA_COUNT 0x5 | ||
44 | #define WR_DATA_COUNT 0x6 | ||
45 | #define RDWR_DATA_COUNT 0x7 | ||
46 | |||
47 | enum ppmu_counter { | ||
48 | PPMU_PMNCNT0, | ||
49 | PPMU_PMCCNT1, | ||
50 | PPMU_PMNCNT2, | ||
51 | PPMU_PMNCNT3, | ||
52 | PPMU_PMNCNT_MAX, | ||
53 | }; | ||
54 | |||
55 | struct bus_opp_table { | ||
56 | unsigned int idx; | ||
57 | unsigned long clk; | ||
58 | unsigned long volt; | ||
59 | }; | ||
60 | |||
61 | struct exynos_ppmu { | ||
62 | void __iomem *hw_base; | ||
63 | unsigned int ccnt; | ||
64 | unsigned int event[PPMU_PMNCNT_MAX]; | ||
65 | unsigned int count[PPMU_PMNCNT_MAX]; | ||
66 | unsigned long long ns; | ||
67 | ktime_t reset_time; | ||
68 | bool ccnt_overflow; | ||
69 | bool count_overflow[PPMU_PMNCNT_MAX]; | ||
70 | }; | ||
71 | |||
72 | void exynos_ppmu_reset(void __iomem *ppmu_base); | ||
73 | void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch, | ||
74 | unsigned int evt); | ||
75 | void exynos_ppmu_start(void __iomem *ppmu_base); | ||
76 | void exynos_ppmu_stop(void __iomem *ppmu_base); | ||
77 | unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch); | ||
78 | #endif /* __DEVFREQ_EXYNOS_PPMU_H */ | ||
diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index fe8c4476f7e4..5f1ab92107e6 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h | |||
@@ -181,6 +181,8 @@ extern struct devfreq *devfreq_add_device(struct device *dev, | |||
181 | const char *governor_name, | 181 | const char *governor_name, |
182 | void *data); | 182 | void *data); |
183 | extern int devfreq_remove_device(struct devfreq *devfreq); | 183 | extern int devfreq_remove_device(struct devfreq *devfreq); |
184 | |||
185 | /* Supposed to be called by PM_SLEEP/PM_RUNTIME callbacks */ | ||
184 | extern int devfreq_suspend_device(struct devfreq *devfreq); | 186 | extern int devfreq_suspend_device(struct devfreq *devfreq); |
185 | extern int devfreq_resume_device(struct devfreq *devfreq); | 187 | extern int devfreq_resume_device(struct devfreq *devfreq); |
186 | 188 | ||