diff options
author | Chanwoo Choi <cw00.choi@samsung.com> | 2015-11-03 05:04:16 -0500 |
---|---|---|
committer | MyungJoo Ham <myungjoo.ham@samsung.com> | 2016-05-02 22:20:05 -0400 |
commit | 0722249ac1f3dcc3af9e9d7ed89792a68f066660 (patch) | |
tree | 07ae8b17b2bcfdcacd1c14cd2f0b0104e5173fa7 | |
parent | 04974df8049fc4240d22759a91e035082ccd18b4 (diff) |
PM / devfreq: exynos: Add generic exynos bus frequency driver
This patch adds the generic exynos bus frequency driver for AMBA AXI bus
of sub-blocks in exynos SoC with DEVFREQ framework. The Samsung Exynos SoC
have the common architecture for bus between DRAM and sub-blocks in SoC.
This driver can support the generic bus frequency driver for Exynos SoCs.
In devicetree, Each bus block has a bus clock, regulator, operation-point
and devfreq-event devices which measure the utilization of each bus block.
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
[m.reichl and linux.amoon: Tested it on exynos4412-odroidu3 board]
Tested-by: Markus Reichl <m.reichl@fivetechno.de>
Tested-by: Anand Moon <linux.amoon@gmail.com>
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Acked-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
-rw-r--r-- | drivers/devfreq/Kconfig | 15 | ||||
-rw-r--r-- | drivers/devfreq/Makefile | 1 | ||||
-rw-r--r-- | drivers/devfreq/exynos-bus.c | 443 |
3 files changed, 459 insertions, 0 deletions
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 4de78c552251..cedda8f1e5eb 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig | |||
@@ -66,6 +66,21 @@ config DEVFREQ_GOV_USERSPACE | |||
66 | 66 | ||
67 | comment "DEVFREQ Drivers" | 67 | comment "DEVFREQ Drivers" |
68 | 68 | ||
69 | config ARM_EXYNOS_BUS_DEVFREQ | ||
70 | bool "ARM EXYNOS Generic Memory Bus DEVFREQ Driver" | ||
71 | depends on ARCH_EXYNOS | ||
72 | select DEVFREQ_GOV_SIMPLE_ONDEMAND | ||
73 | select DEVFREQ_EVENT_EXYNOS_PPMU | ||
74 | select PM_DEVFREQ_EVENT | ||
75 | select PM_OPP | ||
76 | help | ||
77 | This adds the common DEVFREQ driver for Exynos Memory bus. Exynos | ||
78 | Memory bus has one more group of memory bus (e.g, MIF and INT block). | ||
79 | Each memory bus group could contain many memoby bus block. It reads | ||
80 | PPMU counters of memory controllers by using DEVFREQ-event device | ||
81 | and adjusts the operating frequencies and voltages with OPP support. | ||
82 | This does not yet operate with optimal voltages. | ||
83 | |||
69 | config ARM_EXYNOS4_BUS_DEVFREQ | 84 | config ARM_EXYNOS4_BUS_DEVFREQ |
70 | bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver" | 85 | bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver" |
71 | depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM | 86 | depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM |
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 5134f9ee983d..8af8aaf922a8 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile | |||
@@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o | |||
6 | obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o | 6 | obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o |
7 | 7 | ||
8 | # DEVFREQ Drivers | 8 | # DEVFREQ Drivers |
9 | obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o | ||
9 | obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/ | 10 | obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/ |
10 | obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/ | 11 | obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/ |
11 | obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o | 12 | obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o |
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c new file mode 100644 index 000000000000..137de6196af3 --- /dev/null +++ b/drivers/devfreq/exynos-bus.c | |||
@@ -0,0 +1,443 @@ | |||
1 | /* | ||
2 | * Generic Exynos Bus frequency driver with DEVFREQ Framework | ||
3 | * | ||
4 | * Copyright (c) 2015 Samsung Electronics Co., Ltd. | ||
5 | * Author : Chanwoo Choi <cw00.choi@samsung.com> | ||
6 | * | ||
7 | * This driver support Exynos Bus frequency feature by using | ||
8 | * DEVFREQ framework and is based on drivers/devfreq/exynos/exynos4_bus.c. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | */ | ||
14 | |||
15 | #include <linux/clk.h> | ||
16 | #include <linux/devfreq.h> | ||
17 | #include <linux/devfreq-event.h> | ||
18 | #include <linux/device.h> | ||
19 | #include <linux/export.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/of_device.h> | ||
22 | #include <linux/pm_opp.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/regulator/consumer.h> | ||
25 | #include <linux/slab.h> | ||
26 | |||
27 | #define DEFAULT_SATURATION_RATIO 40 | ||
28 | #define DEFAULT_VOLTAGE_TOLERANCE 2 | ||
29 | |||
30 | struct exynos_bus { | ||
31 | struct device *dev; | ||
32 | |||
33 | struct devfreq *devfreq; | ||
34 | struct devfreq_event_dev **edev; | ||
35 | unsigned int edev_count; | ||
36 | struct mutex lock; | ||
37 | |||
38 | struct dev_pm_opp *curr_opp; | ||
39 | |||
40 | struct regulator *regulator; | ||
41 | struct clk *clk; | ||
42 | unsigned int voltage_tolerance; | ||
43 | unsigned int ratio; | ||
44 | }; | ||
45 | |||
46 | /* | ||
47 | * Control the devfreq-event device to get the current state of bus | ||
48 | */ | ||
49 | #define exynos_bus_ops_edev(ops) \ | ||
50 | static int exynos_bus_##ops(struct exynos_bus *bus) \ | ||
51 | { \ | ||
52 | int i, ret; \ | ||
53 | \ | ||
54 | for (i = 0; i < bus->edev_count; i++) { \ | ||
55 | if (!bus->edev[i]) \ | ||
56 | continue; \ | ||
57 | ret = devfreq_event_##ops(bus->edev[i]); \ | ||
58 | if (ret < 0) \ | ||
59 | return ret; \ | ||
60 | } \ | ||
61 | \ | ||
62 | return 0; \ | ||
63 | } | ||
64 | exynos_bus_ops_edev(enable_edev); | ||
65 | exynos_bus_ops_edev(disable_edev); | ||
66 | exynos_bus_ops_edev(set_event); | ||
67 | |||
68 | static int exynos_bus_get_event(struct exynos_bus *bus, | ||
69 | struct devfreq_event_data *edata) | ||
70 | { | ||
71 | struct devfreq_event_data event_data; | ||
72 | unsigned long load_count = 0, total_count = 0; | ||
73 | int i, ret = 0; | ||
74 | |||
75 | for (i = 0; i < bus->edev_count; i++) { | ||
76 | if (!bus->edev[i]) | ||
77 | continue; | ||
78 | |||
79 | ret = devfreq_event_get_event(bus->edev[i], &event_data); | ||
80 | if (ret < 0) | ||
81 | return ret; | ||
82 | |||
83 | if (i == 0 || event_data.load_count > load_count) { | ||
84 | load_count = event_data.load_count; | ||
85 | total_count = event_data.total_count; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | edata->load_count = load_count; | ||
90 | edata->total_count = total_count; | ||
91 | |||
92 | return ret; | ||
93 | } | ||
94 | |||
95 | /* | ||
96 | * Must necessary function for devfreq governor | ||
97 | */ | ||
98 | static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags) | ||
99 | { | ||
100 | struct exynos_bus *bus = dev_get_drvdata(dev); | ||
101 | struct dev_pm_opp *new_opp; | ||
102 | unsigned long old_freq, new_freq, old_volt, new_volt, tol; | ||
103 | int ret = 0; | ||
104 | |||
105 | /* Get new opp-bus instance according to new bus clock */ | ||
106 | rcu_read_lock(); | ||
107 | new_opp = devfreq_recommended_opp(dev, freq, flags); | ||
108 | if (IS_ERR(new_opp)) { | ||
109 | dev_err(dev, "failed to get recommended opp instance\n"); | ||
110 | rcu_read_unlock(); | ||
111 | return PTR_ERR(new_opp); | ||
112 | } | ||
113 | |||
114 | new_freq = dev_pm_opp_get_freq(new_opp); | ||
115 | new_volt = dev_pm_opp_get_voltage(new_opp); | ||
116 | old_freq = dev_pm_opp_get_freq(bus->curr_opp); | ||
117 | old_volt = dev_pm_opp_get_voltage(bus->curr_opp); | ||
118 | rcu_read_unlock(); | ||
119 | |||
120 | if (old_freq == new_freq) | ||
121 | return 0; | ||
122 | tol = new_volt * bus->voltage_tolerance / 100; | ||
123 | |||
124 | /* Change voltage and frequency according to new OPP level */ | ||
125 | mutex_lock(&bus->lock); | ||
126 | |||
127 | if (old_freq < new_freq) { | ||
128 | ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol); | ||
129 | if (ret < 0) { | ||
130 | dev_err(bus->dev, "failed to set voltage\n"); | ||
131 | goto out; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | ret = clk_set_rate(bus->clk, new_freq); | ||
136 | if (ret < 0) { | ||
137 | dev_err(dev, "failed to change clock of bus\n"); | ||
138 | clk_set_rate(bus->clk, old_freq); | ||
139 | goto out; | ||
140 | } | ||
141 | |||
142 | if (old_freq > new_freq) { | ||
143 | ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol); | ||
144 | if (ret < 0) { | ||
145 | dev_err(bus->dev, "failed to set voltage\n"); | ||
146 | goto out; | ||
147 | } | ||
148 | } | ||
149 | bus->curr_opp = new_opp; | ||
150 | |||
151 | dev_dbg(dev, "Set the frequency of bus (%lukHz -> %lukHz)\n", | ||
152 | old_freq/1000, new_freq/1000); | ||
153 | out: | ||
154 | mutex_unlock(&bus->lock); | ||
155 | |||
156 | return ret; | ||
157 | } | ||
158 | |||
159 | static int exynos_bus_get_dev_status(struct device *dev, | ||
160 | struct devfreq_dev_status *stat) | ||
161 | { | ||
162 | struct exynos_bus *bus = dev_get_drvdata(dev); | ||
163 | struct devfreq_event_data edata; | ||
164 | int ret; | ||
165 | |||
166 | rcu_read_lock(); | ||
167 | stat->current_frequency = dev_pm_opp_get_freq(bus->curr_opp); | ||
168 | rcu_read_unlock(); | ||
169 | |||
170 | ret = exynos_bus_get_event(bus, &edata); | ||
171 | if (ret < 0) { | ||
172 | stat->total_time = stat->busy_time = 0; | ||
173 | goto err; | ||
174 | } | ||
175 | |||
176 | stat->busy_time = (edata.load_count * 100) / bus->ratio; | ||
177 | stat->total_time = edata.total_count; | ||
178 | |||
179 | dev_dbg(dev, "Usage of devfreq-event : %lu/%lu\n", stat->busy_time, | ||
180 | stat->total_time); | ||
181 | |||
182 | err: | ||
183 | ret = exynos_bus_set_event(bus); | ||
184 | if (ret < 0) { | ||
185 | dev_err(dev, "failed to set event to devfreq-event devices\n"); | ||
186 | return ret; | ||
187 | } | ||
188 | |||
189 | return ret; | ||
190 | } | ||
191 | |||
192 | static void exynos_bus_exit(struct device *dev) | ||
193 | { | ||
194 | struct exynos_bus *bus = dev_get_drvdata(dev); | ||
195 | int ret; | ||
196 | |||
197 | ret = exynos_bus_disable_edev(bus); | ||
198 | if (ret < 0) | ||
199 | dev_warn(dev, "failed to disable the devfreq-event devices\n"); | ||
200 | |||
201 | if (bus->regulator) | ||
202 | regulator_disable(bus->regulator); | ||
203 | |||
204 | dev_pm_opp_of_remove_table(dev); | ||
205 | } | ||
206 | |||
207 | static int exynos_bus_parse_of(struct device_node *np, | ||
208 | struct exynos_bus *bus) | ||
209 | { | ||
210 | struct device *dev = bus->dev; | ||
211 | unsigned long rate; | ||
212 | int i, ret, count, size; | ||
213 | |||
214 | /* Get the clock to provide each bus with source clock */ | ||
215 | bus->clk = devm_clk_get(dev, "bus"); | ||
216 | if (IS_ERR(bus->clk)) { | ||
217 | dev_err(dev, "failed to get bus clock\n"); | ||
218 | return PTR_ERR(bus->clk); | ||
219 | } | ||
220 | |||
221 | ret = clk_prepare_enable(bus->clk); | ||
222 | if (ret < 0) { | ||
223 | dev_err(dev, "failed to get enable clock\n"); | ||
224 | return ret; | ||
225 | } | ||
226 | |||
227 | /* Get the freq/voltage OPP table to scale the bus frequency */ | ||
228 | rcu_read_lock(); | ||
229 | ret = dev_pm_opp_of_add_table(dev); | ||
230 | if (ret < 0) { | ||
231 | dev_err(dev, "failed to get OPP table\n"); | ||
232 | rcu_read_unlock(); | ||
233 | goto err_clk; | ||
234 | } | ||
235 | |||
236 | rate = clk_get_rate(bus->clk); | ||
237 | bus->curr_opp = dev_pm_opp_find_freq_ceil(dev, &rate); | ||
238 | if (IS_ERR(bus->curr_opp)) { | ||
239 | dev_err(dev, "failed to find dev_pm_opp\n"); | ||
240 | rcu_read_unlock(); | ||
241 | ret = PTR_ERR(bus->curr_opp); | ||
242 | goto err_opp; | ||
243 | } | ||
244 | rcu_read_unlock(); | ||
245 | |||
246 | /* Get the regulator to provide each bus with the power */ | ||
247 | bus->regulator = devm_regulator_get(dev, "vdd"); | ||
248 | if (IS_ERR(bus->regulator)) { | ||
249 | dev_err(dev, "failed to get VDD regulator\n"); | ||
250 | ret = PTR_ERR(bus->regulator); | ||
251 | goto err_opp; | ||
252 | } | ||
253 | |||
254 | ret = regulator_enable(bus->regulator); | ||
255 | if (ret < 0) { | ||
256 | dev_err(dev, "failed to enable VDD regulator\n"); | ||
257 | goto err_opp; | ||
258 | } | ||
259 | |||
260 | /* | ||
261 | * Get the devfreq-event devices to get the current utilization of | ||
262 | * buses. This raw data will be used in devfreq ondemand governor. | ||
263 | */ | ||
264 | count = devfreq_event_get_edev_count(dev); | ||
265 | if (count < 0) { | ||
266 | dev_err(dev, "failed to get the count of devfreq-event dev\n"); | ||
267 | ret = count; | ||
268 | goto err_regulator; | ||
269 | } | ||
270 | bus->edev_count = count; | ||
271 | |||
272 | size = sizeof(*bus->edev) * count; | ||
273 | bus->edev = devm_kzalloc(dev, size, GFP_KERNEL); | ||
274 | if (!bus->edev) { | ||
275 | ret = -ENOMEM; | ||
276 | goto err_regulator; | ||
277 | } | ||
278 | |||
279 | for (i = 0; i < count; i++) { | ||
280 | bus->edev[i] = devfreq_event_get_edev_by_phandle(dev, i); | ||
281 | if (IS_ERR(bus->edev[i])) { | ||
282 | ret = -EPROBE_DEFER; | ||
283 | goto err_regulator; | ||
284 | } | ||
285 | } | ||
286 | |||
287 | /* | ||
288 | * Optionally, Get the saturation ratio according to Exynos SoC | ||
289 | * When measuring the utilization of each AXI bus with devfreq-event | ||
290 | * devices, the measured real cycle might be much lower than the | ||
291 | * total cycle of bus during sampling rate. In result, the devfreq | ||
292 | * simple-ondemand governor might not decide to change the current | ||
293 | * frequency due to too utilization (= real cycle/total cycle). | ||
294 | * So, this property is used to adjust the utilization when calculating | ||
295 | * the busy_time in exynos_bus_get_dev_status(). | ||
296 | */ | ||
297 | if (of_property_read_u32(np, "exynos,saturation-ratio", &bus->ratio)) | ||
298 | bus->ratio = DEFAULT_SATURATION_RATIO; | ||
299 | |||
300 | if (of_property_read_u32(np, "exynos,voltage-tolerance", | ||
301 | &bus->voltage_tolerance)) | ||
302 | bus->voltage_tolerance = DEFAULT_VOLTAGE_TOLERANCE; | ||
303 | |||
304 | return 0; | ||
305 | |||
306 | err_regulator: | ||
307 | regulator_disable(bus->regulator); | ||
308 | err_opp: | ||
309 | dev_pm_opp_of_remove_table(dev); | ||
310 | err_clk: | ||
311 | clk_disable_unprepare(bus->clk); | ||
312 | |||
313 | return ret; | ||
314 | } | ||
315 | |||
316 | static int exynos_bus_probe(struct platform_device *pdev) | ||
317 | { | ||
318 | struct device *dev = &pdev->dev; | ||
319 | struct device_node *np = dev->of_node; | ||
320 | struct devfreq_dev_profile *profile; | ||
321 | struct devfreq_simple_ondemand_data *ondemand_data; | ||
322 | struct exynos_bus *bus; | ||
323 | int ret; | ||
324 | |||
325 | if (!np) { | ||
326 | dev_err(dev, "failed to find devicetree node\n"); | ||
327 | return -EINVAL; | ||
328 | } | ||
329 | |||
330 | bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); | ||
331 | if (!bus) | ||
332 | return -ENOMEM; | ||
333 | mutex_init(&bus->lock); | ||
334 | bus->dev = &pdev->dev; | ||
335 | platform_set_drvdata(pdev, bus); | ||
336 | |||
337 | /* Parse the device-tree to get the resource information */ | ||
338 | ret = exynos_bus_parse_of(np, bus); | ||
339 | if (ret < 0) | ||
340 | return ret; | ||
341 | |||
342 | /* Initalize the struct profile and governor data */ | ||
343 | profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL); | ||
344 | if (!profile) | ||
345 | return -ENOMEM; | ||
346 | profile->polling_ms = 50; | ||
347 | profile->target = exynos_bus_target; | ||
348 | profile->get_dev_status = exynos_bus_get_dev_status; | ||
349 | profile->exit = exynos_bus_exit; | ||
350 | |||
351 | ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL); | ||
352 | if (!ondemand_data) | ||
353 | return -ENOMEM; | ||
354 | ondemand_data->upthreshold = 40; | ||
355 | ondemand_data->downdifferential = 5; | ||
356 | |||
357 | /* Add devfreq device to monitor and handle the exynos bus */ | ||
358 | bus->devfreq = devm_devfreq_add_device(dev, profile, "simple_ondemand", | ||
359 | ondemand_data); | ||
360 | if (IS_ERR(bus->devfreq)) { | ||
361 | dev_err(dev, "failed to add devfreq device\n"); | ||
362 | return PTR_ERR(bus->devfreq); | ||
363 | } | ||
364 | |||
365 | /* Register opp_notifier to catch the change of OPP */ | ||
366 | ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq); | ||
367 | if (ret < 0) { | ||
368 | dev_err(dev, "failed to register opp notifier\n"); | ||
369 | return ret; | ||
370 | } | ||
371 | |||
372 | /* | ||
373 | * Enable devfreq-event to get raw data which is used to determine | ||
374 | * current bus load. | ||
375 | */ | ||
376 | ret = exynos_bus_enable_edev(bus); | ||
377 | if (ret < 0) { | ||
378 | dev_err(dev, "failed to enable devfreq-event devices\n"); | ||
379 | return ret; | ||
380 | } | ||
381 | |||
382 | ret = exynos_bus_set_event(bus); | ||
383 | if (ret < 0) { | ||
384 | dev_err(dev, "failed to set event to devfreq-event devices\n"); | ||
385 | return ret; | ||
386 | } | ||
387 | |||
388 | return 0; | ||
389 | } | ||
390 | |||
391 | #ifdef CONFIG_PM_SLEEP | ||
392 | static int exynos_bus_resume(struct device *dev) | ||
393 | { | ||
394 | struct exynos_bus *bus = dev_get_drvdata(dev); | ||
395 | int ret; | ||
396 | |||
397 | ret = exynos_bus_enable_edev(bus); | ||
398 | if (ret < 0) { | ||
399 | dev_err(dev, "failed to enable the devfreq-event devices\n"); | ||
400 | return ret; | ||
401 | } | ||
402 | |||
403 | return 0; | ||
404 | } | ||
405 | |||
406 | static int exynos_bus_suspend(struct device *dev) | ||
407 | { | ||
408 | struct exynos_bus *bus = dev_get_drvdata(dev); | ||
409 | int ret; | ||
410 | |||
411 | ret = exynos_bus_disable_edev(bus); | ||
412 | if (ret < 0) { | ||
413 | dev_err(dev, "failed to disable the devfreq-event devices\n"); | ||
414 | return ret; | ||
415 | } | ||
416 | |||
417 | return 0; | ||
418 | } | ||
419 | #endif | ||
420 | |||
421 | static const struct dev_pm_ops exynos_bus_pm = { | ||
422 | SET_SYSTEM_SLEEP_PM_OPS(exynos_bus_suspend, exynos_bus_resume) | ||
423 | }; | ||
424 | |||
425 | static const struct of_device_id exynos_bus_of_match[] = { | ||
426 | { .compatible = "samsung,exynos-bus", }, | ||
427 | { /* sentinel */ }, | ||
428 | }; | ||
429 | MODULE_DEVICE_TABLE(of, exynos_bus_of_match); | ||
430 | |||
431 | static struct platform_driver exynos_bus_platdrv = { | ||
432 | .probe = exynos_bus_probe, | ||
433 | .driver = { | ||
434 | .name = "exynos-bus", | ||
435 | .pm = &exynos_bus_pm, | ||
436 | .of_match_table = of_match_ptr(exynos_bus_of_match), | ||
437 | }, | ||
438 | }; | ||
439 | module_platform_driver(exynos_bus_platdrv); | ||
440 | |||
441 | MODULE_DESCRIPTION("Generic Exynos Bus frequency driver"); | ||
442 | MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); | ||
443 | MODULE_LICENSE("GPL v2"); | ||