Commit | Line | Data |
---|---|---|
7b405038 MH |
1 | /* drivers/devfreq/exynos4210_memorybus.c |
2 | * | |
3 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. | |
4 | * http://www.samsung.com/ | |
5 | * MyungJoo Ham <myungjoo.ham@samsung.com> | |
6 | * | |
7 | * EXYNOS4 - Memory/Bus clock frequency scaling support in DEVFREQ framework | |
8 | * This version supports EXYNOS4210 only. This changes bus frequencies | |
9 | * and vddint voltages. Exynos4412/4212 should be able to be supported | |
10 | * with minor modifications. | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or modify | |
13 | * it under the terms of the GNU General Public License version 2 as | |
14 | * published by the Free Software Foundation. | |
15 | * | |
16 | */ | |
17 | ||
18 | #include <linux/io.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/mutex.h> | |
21 | #include <linux/suspend.h> | |
e4db1c74 | 22 | #include <linux/pm_opp.h> |
7b405038 MH |
23 | #include <linux/devfreq.h> |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/regulator/consumer.h> | |
26 | #include <linux/module.h> | |
27 | ||
ba778b37 CC |
28 | #include <mach/map.h> |
29 | ||
30 | #include "exynos_ppmu.h" | |
31 | #include "exynos4_bus.h" | |
32 | ||
7b405038 MH |
33 | #define MAX_SAFEVOLT 1200000 /* 1.2V */ |
34 | ||
35 | enum exynos4_busf_type { | |
36 | TYPE_BUSF_EXYNOS4210, | |
37 | TYPE_BUSF_EXYNOS4x12, | |
38 | }; | |
39 | ||
40 | /* Assume that the bus is saturated if the utilization is 40% */ | |
41 | #define BUS_SATURATION_RATIO 40 | |
42 | ||
7b405038 MH |
43 | enum busclk_level_idx { |
44 | LV_0 = 0, | |
45 | LV_1, | |
46 | LV_2, | |
47 | LV_3, | |
48 | LV_4, | |
49 | _LV_END | |
50 | }; | |
ba778b37 CC |
51 | |
52 | enum exynos_ppmu_idx { | |
53 | PPMU_DMC0, | |
54 | PPMU_DMC1, | |
55 | PPMU_END, | |
56 | }; | |
57 | ||
7b405038 MH |
58 | #define EX4210_LV_MAX LV_2 |
59 | #define EX4x12_LV_MAX LV_4 | |
60 | #define EX4210_LV_NUM (LV_2 + 1) | |
61 | #define EX4x12_LV_NUM (LV_4 + 1) | |
62 | ||
8fa938ac NM |
63 | /** |
64 | * struct busfreq_opp_info - opp information for bus | |
65 | * @rate: Frequency in hertz | |
66 | * @volt: Voltage in microvolts corresponding to this OPP | |
67 | */ | |
68 | struct busfreq_opp_info { | |
69 | unsigned long rate; | |
70 | unsigned long volt; | |
71 | }; | |
72 | ||
7b405038 MH |
73 | struct busfreq_data { |
74 | enum exynos4_busf_type type; | |
75 | struct device *dev; | |
76 | struct devfreq *devfreq; | |
77 | bool disabled; | |
78 | struct regulator *vdd_int; | |
79 | struct regulator *vdd_mif; /* Exynos4412/4212 only */ | |
8fa938ac | 80 | struct busfreq_opp_info curr_oppinfo; |
f4145270 | 81 | struct busfreq_ppmu_data ppmu_data; |
7b405038 MH |
82 | |
83 | struct notifier_block pm_notifier; | |
84 | struct mutex lock; | |
85 | ||
86 | /* Dividers calculated at boot/probe-time */ | |
87 | unsigned int dmc_divtable[_LV_END]; /* DMC0 */ | |
88 | unsigned int top_divtable[_LV_END]; | |
89 | }; | |
90 | ||
7b405038 MH |
91 | /* 4210 controls clock of mif and voltage of int */ |
92 | static struct bus_opp_table exynos4210_busclk_table[] = { | |
93 | {LV_0, 400000, 1150000}, | |
94 | {LV_1, 267000, 1050000}, | |
95 | {LV_2, 133000, 1025000}, | |
96 | {0, 0, 0}, | |
97 | }; | |
98 | ||
99 | /* | |
1d6c2c04 | 100 | * MIF is the main control knob clock for Exynos4x12 MIF/INT |
7b405038 MH |
101 | * clock and voltage of both mif/int are controlled. |
102 | */ | |
103 | static struct bus_opp_table exynos4x12_mifclk_table[] = { | |
104 | {LV_0, 400000, 1100000}, | |
105 | {LV_1, 267000, 1000000}, | |
106 | {LV_2, 160000, 950000}, | |
107 | {LV_3, 133000, 950000}, | |
108 | {LV_4, 100000, 950000}, | |
109 | {0, 0, 0}, | |
110 | }; | |
111 | ||
112 | /* | |
113 | * INT is not the control knob of 4x12. LV_x is not meant to represent | |
114 | * the current performance. (MIF does) | |
115 | */ | |
116 | static struct bus_opp_table exynos4x12_intclk_table[] = { | |
117 | {LV_0, 200000, 1000000}, | |
118 | {LV_1, 160000, 950000}, | |
119 | {LV_2, 133000, 925000}, | |
120 | {LV_3, 100000, 900000}, | |
121 | {0, 0, 0}, | |
122 | }; | |
123 | ||
124 | /* TODO: asv volt definitions are "__initdata"? */ | |
125 | /* Some chips have different operating voltages */ | |
126 | static unsigned int exynos4210_asv_volt[][EX4210_LV_NUM] = { | |
127 | {1150000, 1050000, 1050000}, | |
128 | {1125000, 1025000, 1025000}, | |
129 | {1100000, 1000000, 1000000}, | |
130 | {1075000, 975000, 975000}, | |
131 | {1050000, 950000, 950000}, | |
132 | }; | |
133 | ||
134 | static unsigned int exynos4x12_mif_step_50[][EX4x12_LV_NUM] = { | |
135 | /* 400 267 160 133 100 */ | |
136 | {1050000, 950000, 900000, 900000, 900000}, /* ASV0 */ | |
137 | {1050000, 950000, 900000, 900000, 900000}, /* ASV1 */ | |
138 | {1050000, 950000, 900000, 900000, 900000}, /* ASV2 */ | |
139 | {1050000, 900000, 900000, 900000, 900000}, /* ASV3 */ | |
140 | {1050000, 900000, 900000, 900000, 850000}, /* ASV4 */ | |
141 | {1050000, 900000, 900000, 850000, 850000}, /* ASV5 */ | |
142 | {1050000, 900000, 850000, 850000, 850000}, /* ASV6 */ | |
143 | {1050000, 900000, 850000, 850000, 850000}, /* ASV7 */ | |
144 | {1050000, 900000, 850000, 850000, 850000}, /* ASV8 */ | |
145 | }; | |
146 | ||
147 | static unsigned int exynos4x12_int_volt[][EX4x12_LV_NUM] = { | |
148 | /* 200 160 133 100 */ | |
149 | {1000000, 950000, 925000, 900000}, /* ASV0 */ | |
150 | {975000, 925000, 925000, 900000}, /* ASV1 */ | |
151 | {950000, 925000, 900000, 875000}, /* ASV2 */ | |
152 | {950000, 900000, 900000, 875000}, /* ASV3 */ | |
153 | {925000, 875000, 875000, 875000}, /* ASV4 */ | |
154 | {900000, 850000, 850000, 850000}, /* ASV5 */ | |
155 | {900000, 850000, 850000, 850000}, /* ASV6 */ | |
156 | {900000, 850000, 850000, 850000}, /* ASV7 */ | |
157 | {900000, 850000, 850000, 850000}, /* ASV8 */ | |
158 | }; | |
159 | ||
160 | /*** Clock Divider Data for Exynos4210 ***/ | |
161 | static unsigned int exynos4210_clkdiv_dmc0[][8] = { | |
162 | /* | |
163 | * Clock divider value for following | |
164 | * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD | |
165 | * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS } | |
166 | */ | |
167 | ||
168 | /* DMC L0: 400MHz */ | |
169 | { 3, 1, 1, 1, 1, 1, 3, 1 }, | |
170 | /* DMC L1: 266.7MHz */ | |
171 | { 4, 1, 1, 2, 1, 1, 3, 1 }, | |
172 | /* DMC L2: 133MHz */ | |
173 | { 5, 1, 1, 5, 1, 1, 3, 1 }, | |
174 | }; | |
175 | static unsigned int exynos4210_clkdiv_top[][5] = { | |
176 | /* | |
177 | * Clock divider value for following | |
178 | * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND } | |
179 | */ | |
180 | /* ACLK200 L0: 200MHz */ | |
181 | { 3, 7, 4, 5, 1 }, | |
182 | /* ACLK200 L1: 160MHz */ | |
183 | { 4, 7, 5, 6, 1 }, | |
184 | /* ACLK200 L2: 133MHz */ | |
185 | { 5, 7, 7, 7, 1 }, | |
186 | }; | |
187 | static unsigned int exynos4210_clkdiv_lr_bus[][2] = { | |
188 | /* | |
189 | * Clock divider value for following | |
190 | * { DIVGDL/R, DIVGPL/R } | |
191 | */ | |
192 | /* ACLK_GDL/R L1: 200MHz */ | |
193 | { 3, 1 }, | |
194 | /* ACLK_GDL/R L2: 160MHz */ | |
195 | { 4, 1 }, | |
196 | /* ACLK_GDL/R L3: 133MHz */ | |
197 | { 5, 1 }, | |
198 | }; | |
199 | ||
200 | /*** Clock Divider Data for Exynos4212/4412 ***/ | |
201 | static unsigned int exynos4x12_clkdiv_dmc0[][6] = { | |
202 | /* | |
203 | * Clock divider value for following | |
204 | * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD | |
205 | * DIVDMCP} | |
206 | */ | |
207 | ||
208 | /* DMC L0: 400MHz */ | |
209 | {3, 1, 1, 1, 1, 1}, | |
210 | /* DMC L1: 266.7MHz */ | |
211 | {4, 1, 1, 2, 1, 1}, | |
212 | /* DMC L2: 160MHz */ | |
213 | {5, 1, 1, 4, 1, 1}, | |
214 | /* DMC L3: 133MHz */ | |
215 | {5, 1, 1, 5, 1, 1}, | |
216 | /* DMC L4: 100MHz */ | |
217 | {7, 1, 1, 7, 1, 1}, | |
218 | }; | |
219 | static unsigned int exynos4x12_clkdiv_dmc1[][6] = { | |
220 | /* | |
221 | * Clock divider value for following | |
222 | * { G2DACP, DIVC2C, DIVC2C_ACLK } | |
223 | */ | |
224 | ||
225 | /* DMC L0: 400MHz */ | |
226 | {3, 1, 1}, | |
227 | /* DMC L1: 266.7MHz */ | |
228 | {4, 2, 1}, | |
229 | /* DMC L2: 160MHz */ | |
230 | {5, 4, 1}, | |
231 | /* DMC L3: 133MHz */ | |
232 | {5, 5, 1}, | |
233 | /* DMC L4: 100MHz */ | |
234 | {7, 7, 1}, | |
235 | }; | |
236 | static unsigned int exynos4x12_clkdiv_top[][5] = { | |
237 | /* | |
238 | * Clock divider value for following | |
239 | * { DIVACLK266_GPS, DIVACLK100, DIVACLK160, | |
240 | DIVACLK133, DIVONENAND } | |
241 | */ | |
242 | ||
243 | /* ACLK_GDL/R L0: 200MHz */ | |
244 | {2, 7, 4, 5, 1}, | |
245 | /* ACLK_GDL/R L1: 200MHz */ | |
246 | {2, 7, 4, 5, 1}, | |
247 | /* ACLK_GDL/R L2: 160MHz */ | |
248 | {4, 7, 5, 7, 1}, | |
249 | /* ACLK_GDL/R L3: 133MHz */ | |
250 | {4, 7, 5, 7, 1}, | |
251 | /* ACLK_GDL/R L4: 100MHz */ | |
252 | {7, 7, 7, 7, 1}, | |
253 | }; | |
254 | static unsigned int exynos4x12_clkdiv_lr_bus[][2] = { | |
255 | /* | |
256 | * Clock divider value for following | |
257 | * { DIVGDL/R, DIVGPL/R } | |
258 | */ | |
259 | ||
260 | /* ACLK_GDL/R L0: 200MHz */ | |
261 | {3, 1}, | |
262 | /* ACLK_GDL/R L1: 200MHz */ | |
263 | {3, 1}, | |
264 | /* ACLK_GDL/R L2: 160MHz */ | |
265 | {4, 1}, | |
266 | /* ACLK_GDL/R L3: 133MHz */ | |
267 | {5, 1}, | |
268 | /* ACLK_GDL/R L4: 100MHz */ | |
269 | {7, 1}, | |
270 | }; | |
271 | static unsigned int exynos4x12_clkdiv_sclkip[][3] = { | |
272 | /* | |
273 | * Clock divider value for following | |
274 | * { DIVMFC, DIVJPEG, DIVFIMC0~3} | |
275 | */ | |
276 | ||
277 | /* SCLK_MFC: 200MHz */ | |
278 | {3, 3, 4}, | |
279 | /* SCLK_MFC: 200MHz */ | |
280 | {3, 3, 4}, | |
281 | /* SCLK_MFC: 160MHz */ | |
282 | {4, 4, 5}, | |
283 | /* SCLK_MFC: 133MHz */ | |
284 | {5, 5, 5}, | |
285 | /* SCLK_MFC: 100MHz */ | |
286 | {7, 7, 7}, | |
287 | }; | |
288 | ||
289 | ||
8fa938ac NM |
290 | static int exynos4210_set_busclk(struct busfreq_data *data, |
291 | struct busfreq_opp_info *oppi) | |
7b405038 MH |
292 | { |
293 | unsigned int index; | |
294 | unsigned int tmp; | |
295 | ||
296 | for (index = LV_0; index < EX4210_LV_NUM; index++) | |
8fa938ac | 297 | if (oppi->rate == exynos4210_busclk_table[index].clk) |
7b405038 MH |
298 | break; |
299 | ||
300 | if (index == EX4210_LV_NUM) | |
301 | return -EINVAL; | |
302 | ||
303 | /* Change Divider - DMC0 */ | |
304 | tmp = data->dmc_divtable[index]; | |
305 | ||
5fcc9297 | 306 | __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); |
7b405038 MH |
307 | |
308 | do { | |
5fcc9297 | 309 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); |
7b405038 MH |
310 | } while (tmp & 0x11111111); |
311 | ||
312 | /* Change Divider - TOP */ | |
313 | tmp = data->top_divtable[index]; | |
314 | ||
5fcc9297 | 315 | __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); |
7b405038 MH |
316 | |
317 | do { | |
5fcc9297 | 318 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); |
7b405038 MH |
319 | } while (tmp & 0x11111); |
320 | ||
321 | /* Change Divider - LEFTBUS */ | |
5fcc9297 | 322 | tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); |
7b405038 | 323 | |
5fcc9297 | 324 | tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); |
7b405038 MH |
325 | |
326 | tmp |= ((exynos4210_clkdiv_lr_bus[index][0] << | |
5fcc9297 | 327 | EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | |
7b405038 | 328 | (exynos4210_clkdiv_lr_bus[index][1] << |
5fcc9297 | 329 | EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); |
7b405038 | 330 | |
5fcc9297 | 331 | __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); |
7b405038 MH |
332 | |
333 | do { | |
5fcc9297 | 334 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); |
7b405038 MH |
335 | } while (tmp & 0x11); |
336 | ||
337 | /* Change Divider - RIGHTBUS */ | |
5fcc9297 | 338 | tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); |
7b405038 | 339 | |
5fcc9297 | 340 | tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); |
7b405038 MH |
341 | |
342 | tmp |= ((exynos4210_clkdiv_lr_bus[index][0] << | |
5fcc9297 | 343 | EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | |
7b405038 | 344 | (exynos4210_clkdiv_lr_bus[index][1] << |
5fcc9297 | 345 | EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); |
7b405038 | 346 | |
5fcc9297 | 347 | __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); |
7b405038 MH |
348 | |
349 | do { | |
5fcc9297 | 350 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); |
7b405038 MH |
351 | } while (tmp & 0x11); |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
8fa938ac NM |
356 | static int exynos4x12_set_busclk(struct busfreq_data *data, |
357 | struct busfreq_opp_info *oppi) | |
7b405038 MH |
358 | { |
359 | unsigned int index; | |
360 | unsigned int tmp; | |
361 | ||
362 | for (index = LV_0; index < EX4x12_LV_NUM; index++) | |
8fa938ac | 363 | if (oppi->rate == exynos4x12_mifclk_table[index].clk) |
7b405038 MH |
364 | break; |
365 | ||
366 | if (index == EX4x12_LV_NUM) | |
367 | return -EINVAL; | |
368 | ||
369 | /* Change Divider - DMC0 */ | |
370 | tmp = data->dmc_divtable[index]; | |
371 | ||
5fcc9297 | 372 | __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); |
7b405038 MH |
373 | |
374 | do { | |
5fcc9297 | 375 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); |
7b405038 MH |
376 | } while (tmp & 0x11111111); |
377 | ||
378 | /* Change Divider - DMC1 */ | |
5fcc9297 | 379 | tmp = __raw_readl(EXYNOS4_CLKDIV_DMC1); |
7b405038 | 380 | |
5fcc9297 KK |
381 | tmp &= ~(EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK | |
382 | EXYNOS4_CLKDIV_DMC1_C2C_MASK | | |
383 | EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK); | |
7b405038 MH |
384 | |
385 | tmp |= ((exynos4x12_clkdiv_dmc1[index][0] << | |
5fcc9297 | 386 | EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT) | |
7b405038 | 387 | (exynos4x12_clkdiv_dmc1[index][1] << |
5fcc9297 | 388 | EXYNOS4_CLKDIV_DMC1_C2C_SHIFT) | |
7b405038 | 389 | (exynos4x12_clkdiv_dmc1[index][2] << |
5fcc9297 | 390 | EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT)); |
7b405038 | 391 | |
5fcc9297 | 392 | __raw_writel(tmp, EXYNOS4_CLKDIV_DMC1); |
7b405038 MH |
393 | |
394 | do { | |
5fcc9297 | 395 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC1); |
7b405038 MH |
396 | } while (tmp & 0x111111); |
397 | ||
398 | /* Change Divider - TOP */ | |
5fcc9297 | 399 | tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); |
7b405038 | 400 | |
5fcc9297 KK |
401 | tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK | |
402 | EXYNOS4_CLKDIV_TOP_ACLK100_MASK | | |
403 | EXYNOS4_CLKDIV_TOP_ACLK160_MASK | | |
404 | EXYNOS4_CLKDIV_TOP_ACLK133_MASK | | |
405 | EXYNOS4_CLKDIV_TOP_ONENAND_MASK); | |
7b405038 MH |
406 | |
407 | tmp |= ((exynos4x12_clkdiv_top[index][0] << | |
5fcc9297 | 408 | EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT) | |
7b405038 | 409 | (exynos4x12_clkdiv_top[index][1] << |
5fcc9297 | 410 | EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | |
7b405038 | 411 | (exynos4x12_clkdiv_top[index][2] << |
5fcc9297 | 412 | EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | |
7b405038 | 413 | (exynos4x12_clkdiv_top[index][3] << |
5fcc9297 | 414 | EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | |
7b405038 | 415 | (exynos4x12_clkdiv_top[index][4] << |
5fcc9297 | 416 | EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); |
7b405038 | 417 | |
5fcc9297 | 418 | __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); |
7b405038 MH |
419 | |
420 | do { | |
5fcc9297 | 421 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); |
7b405038 MH |
422 | } while (tmp & 0x11111); |
423 | ||
424 | /* Change Divider - LEFTBUS */ | |
5fcc9297 | 425 | tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); |
7b405038 | 426 | |
5fcc9297 | 427 | tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); |
7b405038 MH |
428 | |
429 | tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] << | |
5fcc9297 | 430 | EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | |
7b405038 | 431 | (exynos4x12_clkdiv_lr_bus[index][1] << |
5fcc9297 | 432 | EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); |
7b405038 | 433 | |
5fcc9297 | 434 | __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); |
7b405038 MH |
435 | |
436 | do { | |
5fcc9297 | 437 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); |
7b405038 MH |
438 | } while (tmp & 0x11); |
439 | ||
440 | /* Change Divider - RIGHTBUS */ | |
5fcc9297 | 441 | tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); |
7b405038 | 442 | |
5fcc9297 | 443 | tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); |
7b405038 MH |
444 | |
445 | tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] << | |
5fcc9297 | 446 | EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | |
7b405038 | 447 | (exynos4x12_clkdiv_lr_bus[index][1] << |
5fcc9297 | 448 | EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); |
7b405038 | 449 | |
5fcc9297 | 450 | __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); |
7b405038 MH |
451 | |
452 | do { | |
5fcc9297 | 453 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); |
7b405038 MH |
454 | } while (tmp & 0x11); |
455 | ||
456 | /* Change Divider - MFC */ | |
5fcc9297 | 457 | tmp = __raw_readl(EXYNOS4_CLKDIV_MFC); |
7b405038 | 458 | |
5fcc9297 | 459 | tmp &= ~(EXYNOS4_CLKDIV_MFC_MASK); |
7b405038 MH |
460 | |
461 | tmp |= ((exynos4x12_clkdiv_sclkip[index][0] << | |
5fcc9297 | 462 | EXYNOS4_CLKDIV_MFC_SHIFT)); |
7b405038 | 463 | |
5fcc9297 | 464 | __raw_writel(tmp, EXYNOS4_CLKDIV_MFC); |
7b405038 MH |
465 | |
466 | do { | |
5fcc9297 | 467 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_MFC); |
7b405038 MH |
468 | } while (tmp & 0x1); |
469 | ||
470 | /* Change Divider - JPEG */ | |
5fcc9297 | 471 | tmp = __raw_readl(EXYNOS4_CLKDIV_CAM1); |
7b405038 | 472 | |
5fcc9297 | 473 | tmp &= ~(EXYNOS4_CLKDIV_CAM1_JPEG_MASK); |
7b405038 MH |
474 | |
475 | tmp |= ((exynos4x12_clkdiv_sclkip[index][1] << | |
5fcc9297 | 476 | EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT)); |
7b405038 | 477 | |
5fcc9297 | 478 | __raw_writel(tmp, EXYNOS4_CLKDIV_CAM1); |
7b405038 MH |
479 | |
480 | do { | |
5fcc9297 | 481 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1); |
7b405038 MH |
482 | } while (tmp & 0x1); |
483 | ||
484 | /* Change Divider - FIMC0~3 */ | |
5fcc9297 | 485 | tmp = __raw_readl(EXYNOS4_CLKDIV_CAM); |
7b405038 | 486 | |
5fcc9297 KK |
487 | tmp &= ~(EXYNOS4_CLKDIV_CAM_FIMC0_MASK | EXYNOS4_CLKDIV_CAM_FIMC1_MASK | |
488 | EXYNOS4_CLKDIV_CAM_FIMC2_MASK | EXYNOS4_CLKDIV_CAM_FIMC3_MASK); | |
7b405038 MH |
489 | |
490 | tmp |= ((exynos4x12_clkdiv_sclkip[index][2] << | |
5fcc9297 | 491 | EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT) | |
7b405038 | 492 | (exynos4x12_clkdiv_sclkip[index][2] << |
5fcc9297 | 493 | EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT) | |
7b405038 | 494 | (exynos4x12_clkdiv_sclkip[index][2] << |
5fcc9297 | 495 | EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT) | |
7b405038 | 496 | (exynos4x12_clkdiv_sclkip[index][2] << |
5fcc9297 | 497 | EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT)); |
7b405038 | 498 | |
5fcc9297 | 499 | __raw_writel(tmp, EXYNOS4_CLKDIV_CAM); |
7b405038 MH |
500 | |
501 | do { | |
5fcc9297 | 502 | tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1); |
7b405038 MH |
503 | } while (tmp & 0x1111); |
504 | ||
505 | return 0; | |
506 | } | |
507 | ||
7b405038 MH |
508 | static int exynos4x12_get_intspec(unsigned long mifclk) |
509 | { | |
510 | int i = 0; | |
511 | ||
512 | while (exynos4x12_intclk_table[i].clk) { | |
513 | if (exynos4x12_intclk_table[i].clk <= mifclk) | |
514 | return i; | |
515 | i++; | |
516 | } | |
517 | ||
518 | return -EINVAL; | |
519 | } | |
520 | ||
8fa938ac NM |
521 | static int exynos4_bus_setvolt(struct busfreq_data *data, |
522 | struct busfreq_opp_info *oppi, | |
523 | struct busfreq_opp_info *oldoppi) | |
7b405038 MH |
524 | { |
525 | int err = 0, tmp; | |
8fa938ac | 526 | unsigned long volt = oppi->volt; |
7b405038 MH |
527 | |
528 | switch (data->type) { | |
529 | case TYPE_BUSF_EXYNOS4210: | |
530 | /* OPP represents DMC clock + INT voltage */ | |
531 | err = regulator_set_voltage(data->vdd_int, volt, | |
532 | MAX_SAFEVOLT); | |
533 | break; | |
534 | case TYPE_BUSF_EXYNOS4x12: | |
535 | /* OPP represents MIF clock + MIF voltage */ | |
536 | err = regulator_set_voltage(data->vdd_mif, volt, | |
537 | MAX_SAFEVOLT); | |
538 | if (err) | |
539 | break; | |
540 | ||
8fa938ac | 541 | tmp = exynos4x12_get_intspec(oppi->rate); |
7b405038 MH |
542 | if (tmp < 0) { |
543 | err = tmp; | |
544 | regulator_set_voltage(data->vdd_mif, | |
8fa938ac | 545 | oldoppi->volt, |
7b405038 MH |
546 | MAX_SAFEVOLT); |
547 | break; | |
548 | } | |
549 | err = regulator_set_voltage(data->vdd_int, | |
550 | exynos4x12_intclk_table[tmp].volt, | |
551 | MAX_SAFEVOLT); | |
552 | /* Try to recover */ | |
553 | if (err) | |
554 | regulator_set_voltage(data->vdd_mif, | |
8fa938ac | 555 | oldoppi->volt, |
7b405038 MH |
556 | MAX_SAFEVOLT); |
557 | break; | |
558 | default: | |
559 | err = -EINVAL; | |
560 | } | |
561 | ||
562 | return err; | |
563 | } | |
564 | ||
ab5f299f MH |
565 | static int exynos4_bus_target(struct device *dev, unsigned long *_freq, |
566 | u32 flags) | |
7b405038 MH |
567 | { |
568 | int err = 0; | |
ab5f299f MH |
569 | struct platform_device *pdev = container_of(dev, struct platform_device, |
570 | dev); | |
571 | struct busfreq_data *data = platform_get_drvdata(pdev); | |
47d43ba7 | 572 | struct dev_pm_opp *opp; |
8fa938ac NM |
573 | unsigned long freq; |
574 | unsigned long old_freq = data->curr_oppinfo.rate; | |
575 | struct busfreq_opp_info new_oppinfo; | |
ab5f299f | 576 | |
8fa938ac NM |
577 | rcu_read_lock(); |
578 | opp = devfreq_recommended_opp(dev, _freq, flags); | |
579 | if (IS_ERR(opp)) { | |
580 | rcu_read_unlock(); | |
ab5f299f | 581 | return PTR_ERR(opp); |
8fa938ac | 582 | } |
5d4879cd NM |
583 | new_oppinfo.rate = dev_pm_opp_get_freq(opp); |
584 | new_oppinfo.volt = dev_pm_opp_get_voltage(opp); | |
8fa938ac NM |
585 | rcu_read_unlock(); |
586 | freq = new_oppinfo.rate; | |
7b405038 MH |
587 | |
588 | if (old_freq == freq) | |
589 | return 0; | |
590 | ||
61767729 | 591 | dev_dbg(dev, "targeting %lukHz %luuV\n", freq, new_oppinfo.volt); |
7b405038 MH |
592 | |
593 | mutex_lock(&data->lock); | |
594 | ||
595 | if (data->disabled) | |
596 | goto out; | |
597 | ||
598 | if (old_freq < freq) | |
8fa938ac NM |
599 | err = exynos4_bus_setvolt(data, &new_oppinfo, |
600 | &data->curr_oppinfo); | |
7b405038 MH |
601 | if (err) |
602 | goto out; | |
603 | ||
604 | if (old_freq != freq) { | |
605 | switch (data->type) { | |
606 | case TYPE_BUSF_EXYNOS4210: | |
8fa938ac | 607 | err = exynos4210_set_busclk(data, &new_oppinfo); |
7b405038 MH |
608 | break; |
609 | case TYPE_BUSF_EXYNOS4x12: | |
8fa938ac | 610 | err = exynos4x12_set_busclk(data, &new_oppinfo); |
7b405038 MH |
611 | break; |
612 | default: | |
613 | err = -EINVAL; | |
614 | } | |
615 | } | |
616 | if (err) | |
617 | goto out; | |
618 | ||
619 | if (old_freq > freq) | |
8fa938ac NM |
620 | err = exynos4_bus_setvolt(data, &new_oppinfo, |
621 | &data->curr_oppinfo); | |
7b405038 MH |
622 | if (err) |
623 | goto out; | |
624 | ||
8fa938ac | 625 | data->curr_oppinfo = new_oppinfo; |
7b405038 MH |
626 | out: |
627 | mutex_unlock(&data->lock); | |
628 | return err; | |
629 | } | |
630 | ||
7b405038 MH |
631 | static int exynos4_bus_get_dev_status(struct device *dev, |
632 | struct devfreq_dev_status *stat) | |
633 | { | |
f0c28b00 | 634 | struct busfreq_data *data = dev_get_drvdata(dev); |
f4145270 | 635 | struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; |
ba778b37 | 636 | int busier; |
7b405038 | 637 | |
26d51853 BZ |
638 | exynos_read_ppmu(ppmu_data); |
639 | busier = exynos_get_busier_ppmu(ppmu_data); | |
8fa938ac | 640 | stat->current_frequency = data->curr_oppinfo.rate; |
7b405038 | 641 | |
7b405038 | 642 | /* Number of cycles spent on memory access */ |
f4145270 | 643 | stat->busy_time = ppmu_data->ppmu[busier].count[PPMU_PMNCNT3]; |
7b405038 | 644 | stat->busy_time *= 100 / BUS_SATURATION_RATIO; |
f4145270 | 645 | stat->total_time = ppmu_data->ppmu[busier].ccnt; |
7b405038 MH |
646 | |
647 | /* If the counters have overflown, retry */ | |
f4145270 BZ |
648 | if (ppmu_data->ppmu[busier].ccnt_overflow || |
649 | ppmu_data->ppmu[busier].count_overflow[0]) | |
7b405038 MH |
650 | return -EAGAIN; |
651 | ||
652 | return 0; | |
653 | } | |
654 | ||
7b405038 MH |
655 | static struct devfreq_dev_profile exynos4_devfreq_profile = { |
656 | .initial_freq = 400000, | |
657 | .polling_ms = 50, | |
658 | .target = exynos4_bus_target, | |
659 | .get_dev_status = exynos4_bus_get_dev_status, | |
7b405038 MH |
660 | }; |
661 | ||
662 | static int exynos4210_init_tables(struct busfreq_data *data) | |
663 | { | |
664 | u32 tmp; | |
665 | int mgrp; | |
666 | int i, err = 0; | |
667 | ||
5fcc9297 | 668 | tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); |
7b405038 | 669 | for (i = LV_0; i < EX4210_LV_NUM; i++) { |
5fcc9297 KK |
670 | tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | |
671 | EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | | |
672 | EXYNOS4_CLKDIV_DMC0_DPHY_MASK | | |
673 | EXYNOS4_CLKDIV_DMC0_DMC_MASK | | |
674 | EXYNOS4_CLKDIV_DMC0_DMCD_MASK | | |
675 | EXYNOS4_CLKDIV_DMC0_DMCP_MASK | | |
676 | EXYNOS4_CLKDIV_DMC0_COPY2_MASK | | |
677 | EXYNOS4_CLKDIV_DMC0_CORETI_MASK); | |
7b405038 MH |
678 | |
679 | tmp |= ((exynos4210_clkdiv_dmc0[i][0] << | |
5fcc9297 | 680 | EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | |
7b405038 | 681 | (exynos4210_clkdiv_dmc0[i][1] << |
5fcc9297 | 682 | EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | |
7b405038 | 683 | (exynos4210_clkdiv_dmc0[i][2] << |
5fcc9297 | 684 | EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | |
7b405038 | 685 | (exynos4210_clkdiv_dmc0[i][3] << |
5fcc9297 | 686 | EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | |
7b405038 | 687 | (exynos4210_clkdiv_dmc0[i][4] << |
5fcc9297 | 688 | EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | |
7b405038 | 689 | (exynos4210_clkdiv_dmc0[i][5] << |
5fcc9297 | 690 | EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) | |
7b405038 | 691 | (exynos4210_clkdiv_dmc0[i][6] << |
5fcc9297 | 692 | EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) | |
7b405038 | 693 | (exynos4210_clkdiv_dmc0[i][7] << |
5fcc9297 | 694 | EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT)); |
7b405038 MH |
695 | |
696 | data->dmc_divtable[i] = tmp; | |
697 | } | |
698 | ||
5fcc9297 | 699 | tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); |
7b405038 | 700 | for (i = LV_0; i < EX4210_LV_NUM; i++) { |
5fcc9297 KK |
701 | tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK200_MASK | |
702 | EXYNOS4_CLKDIV_TOP_ACLK100_MASK | | |
703 | EXYNOS4_CLKDIV_TOP_ACLK160_MASK | | |
704 | EXYNOS4_CLKDIV_TOP_ACLK133_MASK | | |
705 | EXYNOS4_CLKDIV_TOP_ONENAND_MASK); | |
7b405038 MH |
706 | |
707 | tmp |= ((exynos4210_clkdiv_top[i][0] << | |
5fcc9297 | 708 | EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) | |
7b405038 | 709 | (exynos4210_clkdiv_top[i][1] << |
5fcc9297 | 710 | EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | |
7b405038 | 711 | (exynos4210_clkdiv_top[i][2] << |
5fcc9297 | 712 | EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | |
7b405038 | 713 | (exynos4210_clkdiv_top[i][3] << |
5fcc9297 | 714 | EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | |
7b405038 | 715 | (exynos4210_clkdiv_top[i][4] << |
5fcc9297 | 716 | EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); |
7b405038 MH |
717 | |
718 | data->top_divtable[i] = tmp; | |
719 | } | |
720 | ||
cb7063f4 PB |
721 | /* |
722 | * TODO: init tmp based on busfreq_data | |
723 | * (device-tree or platform-data) | |
724 | */ | |
7b405038 | 725 | tmp = 0; /* Max voltages for the reliability of the unknown */ |
7b405038 MH |
726 | |
727 | pr_debug("ASV Group of Exynos4 is %d\n", tmp); | |
728 | /* Use merged grouping for voltage */ | |
729 | switch (tmp) { | |
730 | case 0: | |
731 | mgrp = 0; | |
732 | break; | |
733 | case 1: | |
734 | case 2: | |
735 | mgrp = 1; | |
736 | break; | |
737 | case 3: | |
738 | case 4: | |
739 | mgrp = 2; | |
740 | break; | |
741 | case 5: | |
742 | case 6: | |
743 | mgrp = 3; | |
744 | break; | |
745 | case 7: | |
746 | mgrp = 4; | |
747 | break; | |
748 | default: | |
749 | pr_warn("Unknown ASV Group. Use max voltage.\n"); | |
750 | mgrp = 0; | |
751 | } | |
752 | ||
753 | for (i = LV_0; i < EX4210_LV_NUM; i++) | |
754 | exynos4210_busclk_table[i].volt = exynos4210_asv_volt[mgrp][i]; | |
755 | ||
756 | for (i = LV_0; i < EX4210_LV_NUM; i++) { | |
5d4879cd | 757 | err = dev_pm_opp_add(data->dev, exynos4210_busclk_table[i].clk, |
7b405038 MH |
758 | exynos4210_busclk_table[i].volt); |
759 | if (err) { | |
760 | dev_err(data->dev, "Cannot add opp entries.\n"); | |
761 | return err; | |
762 | } | |
763 | } | |
764 | ||
765 | ||
766 | return 0; | |
767 | } | |
768 | ||
769 | static int exynos4x12_init_tables(struct busfreq_data *data) | |
770 | { | |
771 | unsigned int i; | |
772 | unsigned int tmp; | |
773 | int ret; | |
774 | ||
775 | /* Enable pause function for DREX2 DVFS */ | |
a2b9676d MH |
776 | tmp = __raw_readl(EXYNOS4_DMC_PAUSE_CTRL); |
777 | tmp |= EXYNOS4_DMC_PAUSE_ENABLE; | |
778 | __raw_writel(tmp, EXYNOS4_DMC_PAUSE_CTRL); | |
7b405038 | 779 | |
5fcc9297 | 780 | tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); |
7b405038 MH |
781 | |
782 | for (i = 0; i < EX4x12_LV_NUM; i++) { | |
5fcc9297 KK |
783 | tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | |
784 | EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | | |
785 | EXYNOS4_CLKDIV_DMC0_DPHY_MASK | | |
786 | EXYNOS4_CLKDIV_DMC0_DMC_MASK | | |
787 | EXYNOS4_CLKDIV_DMC0_DMCD_MASK | | |
788 | EXYNOS4_CLKDIV_DMC0_DMCP_MASK); | |
7b405038 MH |
789 | |
790 | tmp |= ((exynos4x12_clkdiv_dmc0[i][0] << | |
5fcc9297 | 791 | EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | |
7b405038 | 792 | (exynos4x12_clkdiv_dmc0[i][1] << |
5fcc9297 | 793 | EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | |
7b405038 | 794 | (exynos4x12_clkdiv_dmc0[i][2] << |
5fcc9297 | 795 | EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | |
7b405038 | 796 | (exynos4x12_clkdiv_dmc0[i][3] << |
5fcc9297 | 797 | EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | |
7b405038 | 798 | (exynos4x12_clkdiv_dmc0[i][4] << |
5fcc9297 | 799 | EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | |
7b405038 | 800 | (exynos4x12_clkdiv_dmc0[i][5] << |
5fcc9297 | 801 | EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT)); |
7b405038 MH |
802 | |
803 | data->dmc_divtable[i] = tmp; | |
804 | } | |
805 | ||
7b405038 | 806 | tmp = 0; /* Max voltages for the reliability of the unknown */ |
7b405038 MH |
807 | |
808 | if (tmp > 8) | |
809 | tmp = 0; | |
810 | pr_debug("ASV Group of Exynos4x12 is %d\n", tmp); | |
811 | ||
812 | for (i = 0; i < EX4x12_LV_NUM; i++) { | |
813 | exynos4x12_mifclk_table[i].volt = | |
814 | exynos4x12_mif_step_50[tmp][i]; | |
815 | exynos4x12_intclk_table[i].volt = | |
816 | exynos4x12_int_volt[tmp][i]; | |
817 | } | |
818 | ||
819 | for (i = 0; i < EX4x12_LV_NUM; i++) { | |
5d4879cd | 820 | ret = dev_pm_opp_add(data->dev, exynos4x12_mifclk_table[i].clk, |
7b405038 MH |
821 | exynos4x12_mifclk_table[i].volt); |
822 | if (ret) { | |
823 | dev_err(data->dev, "Fail to add opp entries.\n"); | |
824 | return ret; | |
825 | } | |
826 | } | |
827 | ||
828 | return 0; | |
829 | } | |
830 | ||
831 | static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this, | |
832 | unsigned long event, void *ptr) | |
833 | { | |
834 | struct busfreq_data *data = container_of(this, struct busfreq_data, | |
835 | pm_notifier); | |
47d43ba7 | 836 | struct dev_pm_opp *opp; |
8fa938ac | 837 | struct busfreq_opp_info new_oppinfo; |
7b405038 MH |
838 | unsigned long maxfreq = ULONG_MAX; |
839 | int err = 0; | |
840 | ||
841 | switch (event) { | |
842 | case PM_SUSPEND_PREPARE: | |
843 | /* Set Fastest and Deactivate DVFS */ | |
844 | mutex_lock(&data->lock); | |
845 | ||
846 | data->disabled = true; | |
847 | ||
8fa938ac | 848 | rcu_read_lock(); |
5d4879cd | 849 | opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq); |
8fa938ac NM |
850 | if (IS_ERR(opp)) { |
851 | rcu_read_unlock(); | |
852 | dev_err(data->dev, "%s: unable to find a min freq\n", | |
853 | __func__); | |
5751cdc0 | 854 | mutex_unlock(&data->lock); |
8fa938ac NM |
855 | return PTR_ERR(opp); |
856 | } | |
5d4879cd NM |
857 | new_oppinfo.rate = dev_pm_opp_get_freq(opp); |
858 | new_oppinfo.volt = dev_pm_opp_get_voltage(opp); | |
8fa938ac | 859 | rcu_read_unlock(); |
7b405038 | 860 | |
8fa938ac NM |
861 | err = exynos4_bus_setvolt(data, &new_oppinfo, |
862 | &data->curr_oppinfo); | |
7b405038 MH |
863 | if (err) |
864 | goto unlock; | |
865 | ||
866 | switch (data->type) { | |
867 | case TYPE_BUSF_EXYNOS4210: | |
8fa938ac | 868 | err = exynos4210_set_busclk(data, &new_oppinfo); |
7b405038 MH |
869 | break; |
870 | case TYPE_BUSF_EXYNOS4x12: | |
8fa938ac | 871 | err = exynos4x12_set_busclk(data, &new_oppinfo); |
7b405038 MH |
872 | break; |
873 | default: | |
874 | err = -EINVAL; | |
875 | } | |
876 | if (err) | |
877 | goto unlock; | |
878 | ||
8fa938ac | 879 | data->curr_oppinfo = new_oppinfo; |
7b405038 MH |
880 | unlock: |
881 | mutex_unlock(&data->lock); | |
882 | if (err) | |
883 | return err; | |
884 | return NOTIFY_OK; | |
885 | case PM_POST_RESTORE: | |
886 | case PM_POST_SUSPEND: | |
887 | /* Reactivate */ | |
888 | mutex_lock(&data->lock); | |
889 | data->disabled = false; | |
890 | mutex_unlock(&data->lock); | |
891 | return NOTIFY_OK; | |
892 | } | |
893 | ||
894 | return NOTIFY_DONE; | |
895 | } | |
896 | ||
0fe763c5 | 897 | static int exynos4_busfreq_probe(struct platform_device *pdev) |
7b405038 MH |
898 | { |
899 | struct busfreq_data *data; | |
f4145270 | 900 | struct busfreq_ppmu_data *ppmu_data; |
47d43ba7 | 901 | struct dev_pm_opp *opp; |
7b405038 MH |
902 | struct device *dev = &pdev->dev; |
903 | int err = 0; | |
904 | ||
d7895052 | 905 | data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data), GFP_KERNEL); |
7b405038 MH |
906 | if (data == NULL) { |
907 | dev_err(dev, "Cannot allocate memory.\n"); | |
908 | return -ENOMEM; | |
909 | } | |
910 | ||
f4145270 BZ |
911 | ppmu_data = &data->ppmu_data; |
912 | ppmu_data->ppmu_end = PPMU_END; | |
913 | ppmu_data->ppmu = devm_kzalloc(dev, | |
914 | sizeof(struct exynos_ppmu) * PPMU_END, | |
915 | GFP_KERNEL); | |
916 | if (!ppmu_data->ppmu) { | |
917 | dev_err(dev, "Failed to allocate memory for exynos_ppmu\n"); | |
918 | return -ENOMEM; | |
919 | } | |
920 | ||
7b405038 | 921 | data->type = pdev->id_entry->driver_data; |
f4145270 BZ |
922 | ppmu_data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0; |
923 | ppmu_data->ppmu[PPMU_DMC1].hw_base = S5P_VA_DMC1; | |
7b405038 MH |
924 | data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event; |
925 | data->dev = dev; | |
926 | mutex_init(&data->lock); | |
927 | ||
928 | switch (data->type) { | |
929 | case TYPE_BUSF_EXYNOS4210: | |
930 | err = exynos4210_init_tables(data); | |
931 | break; | |
932 | case TYPE_BUSF_EXYNOS4x12: | |
933 | err = exynos4x12_init_tables(data); | |
934 | break; | |
935 | default: | |
936 | dev_err(dev, "Cannot determine the device id %d\n", data->type); | |
937 | err = -EINVAL; | |
938 | } | |
45c58e93 CC |
939 | if (err) { |
940 | dev_err(dev, "Cannot initialize busfreq table %d\n", | |
941 | data->type); | |
d7895052 | 942 | return err; |
45c58e93 | 943 | } |
7b405038 | 944 | |
d7895052 | 945 | data->vdd_int = devm_regulator_get(dev, "vdd_int"); |
7b405038 MH |
946 | if (IS_ERR(data->vdd_int)) { |
947 | dev_err(dev, "Cannot get the regulator \"vdd_int\"\n"); | |
d7895052 | 948 | return PTR_ERR(data->vdd_int); |
7b405038 MH |
949 | } |
950 | if (data->type == TYPE_BUSF_EXYNOS4x12) { | |
d7895052 | 951 | data->vdd_mif = devm_regulator_get(dev, "vdd_mif"); |
7b405038 MH |
952 | if (IS_ERR(data->vdd_mif)) { |
953 | dev_err(dev, "Cannot get the regulator \"vdd_mif\"\n"); | |
d7895052 | 954 | return PTR_ERR(data->vdd_mif); |
7b405038 MH |
955 | } |
956 | } | |
957 | ||
8fa938ac | 958 | rcu_read_lock(); |
5d4879cd NM |
959 | opp = dev_pm_opp_find_freq_floor(dev, |
960 | &exynos4_devfreq_profile.initial_freq); | |
7b405038 | 961 | if (IS_ERR(opp)) { |
8fa938ac | 962 | rcu_read_unlock(); |
7b405038 | 963 | dev_err(dev, "Invalid initial frequency %lu kHz.\n", |
dce9dc3a | 964 | exynos4_devfreq_profile.initial_freq); |
d7895052 | 965 | return PTR_ERR(opp); |
7b405038 | 966 | } |
5d4879cd NM |
967 | data->curr_oppinfo.rate = dev_pm_opp_get_freq(opp); |
968 | data->curr_oppinfo.volt = dev_pm_opp_get_voltage(opp); | |
8fa938ac | 969 | rcu_read_unlock(); |
7b405038 MH |
970 | |
971 | platform_set_drvdata(pdev, data); | |
972 | ||
c1b13d4e | 973 | data->devfreq = devm_devfreq_add_device(dev, &exynos4_devfreq_profile, |
1b5c1be2 | 974 | "simple_ondemand", NULL); |
d7895052 SK |
975 | if (IS_ERR(data->devfreq)) |
976 | return PTR_ERR(data->devfreq); | |
7b405038 | 977 | |
ba778b37 CC |
978 | /* |
979 | * Start PPMU (Performance Profiling Monitoring Unit) to check | |
980 | * utilization of each IP in the Exynos4 SoC. | |
981 | */ | |
f4145270 | 982 | busfreq_mon_reset(ppmu_data); |
ba778b37 | 983 | |
45c58e93 | 984 | /* Register opp_notifier for Exynos4 busfreq */ |
c1b13d4e | 985 | err = devm_devfreq_register_opp_notifier(dev, data->devfreq); |
45c58e93 CC |
986 | if (err < 0) { |
987 | dev_err(dev, "Failed to register opp notifier\n"); | |
c1b13d4e | 988 | return err; |
45c58e93 | 989 | } |
7b405038 | 990 | |
45c58e93 | 991 | /* Register pm_notifier for Exynos4 busfreq */ |
7b405038 MH |
992 | err = register_pm_notifier(&data->pm_notifier); |
993 | if (err) { | |
994 | dev_err(dev, "Failed to setup pm notifier\n"); | |
c1b13d4e | 995 | return err; |
7b405038 MH |
996 | } |
997 | ||
998 | return 0; | |
7b405038 MH |
999 | } |
1000 | ||
0fe763c5 | 1001 | static int exynos4_busfreq_remove(struct platform_device *pdev) |
7b405038 MH |
1002 | { |
1003 | struct busfreq_data *data = platform_get_drvdata(pdev); | |
1004 | ||
45c58e93 | 1005 | /* Unregister all of notifier chain */ |
7b405038 | 1006 | unregister_pm_notifier(&data->pm_notifier); |
7b405038 MH |
1007 | |
1008 | return 0; | |
1009 | } | |
1010 | ||
60d6977c | 1011 | #ifdef CONFIG_PM_SLEEP |
7b405038 MH |
1012 | static int exynos4_busfreq_resume(struct device *dev) |
1013 | { | |
f0c28b00 | 1014 | struct busfreq_data *data = dev_get_drvdata(dev); |
f4145270 | 1015 | struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data; |
7b405038 | 1016 | |
f4145270 | 1017 | busfreq_mon_reset(ppmu_data); |
7b405038 MH |
1018 | return 0; |
1019 | } | |
60d6977c | 1020 | #endif |
7b405038 | 1021 | |
60d6977c | 1022 | static SIMPLE_DEV_PM_OPS(exynos4_busfreq_pm_ops, NULL, exynos4_busfreq_resume); |
7b405038 MH |
1023 | |
1024 | static const struct platform_device_id exynos4_busfreq_id[] = { | |
1025 | { "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 }, | |
1026 | { "exynos4412-busfreq", TYPE_BUSF_EXYNOS4x12 }, | |
1027 | { "exynos4212-busfreq", TYPE_BUSF_EXYNOS4x12 }, | |
1028 | { }, | |
1029 | }; | |
1030 | ||
1031 | static struct platform_driver exynos4_busfreq_driver = { | |
1032 | .probe = exynos4_busfreq_probe, | |
0fe763c5 | 1033 | .remove = exynos4_busfreq_remove, |
7b405038 MH |
1034 | .id_table = exynos4_busfreq_id, |
1035 | .driver = { | |
1036 | .name = "exynos4-busfreq", | |
60d6977c | 1037 | .pm = &exynos4_busfreq_pm_ops, |
7b405038 MH |
1038 | }, |
1039 | }; | |
1040 | ||
1041 | static int __init exynos4_busfreq_init(void) | |
1042 | { | |
1043 | return platform_driver_register(&exynos4_busfreq_driver); | |
1044 | } | |
1045 | late_initcall(exynos4_busfreq_init); | |
1046 | ||
1047 | static void __exit exynos4_busfreq_exit(void) | |
1048 | { | |
1049 | platform_driver_unregister(&exynos4_busfreq_driver); | |
1050 | } | |
1051 | module_exit(exynos4_busfreq_exit); | |
1052 | ||
1053 | MODULE_LICENSE("GPL"); | |
1054 | MODULE_DESCRIPTION("EXYNOS4 busfreq driver with devfreq framework"); | |
1055 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); |