aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmit Daniel Kachhap <amit.daniel@samsung.com>2013-04-08 05:57:34 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2013-04-10 07:19:26 -0400
commit49d7b5bfb79cd5f8141a2998e2f4003d0ed65e5c (patch)
tree73d90435e6f32db332c0ca3ba034d475ee498daa
parent9c5320c8ea8b8423edca2c40cd559f1ce9496dab (diff)
cpufreq: exynos: Add cpufreq driver for exynos5440
This patch adds dvfs support for exynos5440 SOC. This soc has 4 cores and they scale at same frequency. The nature of exynos5440 clock controller is different from previous exynos controllers so not using the common exynos cpufreq framework. The major difference being interrupt notification for frequency change. Also, OPP library is used for device tree parsing to get different parameters like frequency, voltage etc. Since the opp library sorts the frequency table in ascending order so they are again re-arranged in descending order. This will have one-to-one mapping with the clock controller state management logic. Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com> Acked-by: Kukjin Kim <kgene.kim@samsung.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r--Documentation/devicetree/bindings/cpufreq/cpufreq-exynos5440.txt28
-rw-r--r--drivers/cpufreq/Kconfig.arm9
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/exynos5440-cpufreq.c478
4 files changed, 516 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-exynos5440.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-exynos5440.txt
new file mode 100644
index 000000000000..caff1a57436f
--- /dev/null
+++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-exynos5440.txt
@@ -0,0 +1,28 @@
1
2Exynos5440 cpufreq driver
3-------------------
4
5Exynos5440 SoC cpufreq driver for CPU frequency scaling.
6
7Required properties:
8- interrupts: Interrupt to know the completion of cpu frequency change.
9- operating-points: Table of frequencies and voltage CPU could be transitioned into,
10 in the decreasing order. Frequency should be in KHz units and voltage
11 should be in microvolts.
12
13Optional properties:
14- clock-latency: Clock monitor latency in microsecond.
15
16All the required listed above must be defined under node cpufreq.
17
18Example:
19--------
20 cpufreq@160000 {
21 compatible = "samsung,exynos5440-cpufreq";
22 reg = <0x160000 0x1000>;
23 interrupts = <0 57 0>;
24 operating-points = <
25 1000000 975000
26 800000 925000>;
27 clock-latency = <100000>;
28 };
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 09da6a3f0e8f..83bd5c148d85 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -42,6 +42,15 @@ config ARM_EXYNOS5250_CPUFREQ
42 This adds the CPUFreq driver for Samsung EXYNOS5250 42 This adds the CPUFreq driver for Samsung EXYNOS5250
43 SoC. 43 SoC.
44 44
45config ARM_EXYNOS5440_CPUFREQ
46 def_bool SOC_EXYNOS5440
47 depends on HAVE_CLK && PM_OPP && OF
48 help
49 This adds the CPUFreq driver for Samsung EXYNOS5440
50 SoC. The nature of exynos5440 clock controller is
51 different than previous exynos controllers so not using
52 the common exynos framework.
53
45config ARM_HIGHBANK_CPUFREQ 54config ARM_HIGHBANK_CPUFREQ
46 tristate "Calxeda Highbank-based" 55 tristate "Calxeda Highbank-based"
47 depends on ARCH_HIGHBANK 56 depends on ARCH_HIGHBANK
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index a264dd302c34..315b9231feb1 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o
56obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o 56obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o
57obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o 57obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o
58obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o 58obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o
59obj-$(CONFIG_ARM_EXYNOS5440_CPUFREQ) += exynos5440-cpufreq.o
59obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o 60obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o
60obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o 61obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o
61obj-$(CONFIG_ARM_INTEGRATOR) += integrator-cpufreq.o 62obj-$(CONFIG_ARM_INTEGRATOR) += integrator-cpufreq.o
diff --git a/drivers/cpufreq/exynos5440-cpufreq.c b/drivers/cpufreq/exynos5440-cpufreq.c
new file mode 100644
index 000000000000..ead7ed43e238
--- /dev/null
+++ b/drivers/cpufreq/exynos5440-cpufreq.c
@@ -0,0 +1,478 @@
1/*
2 * Copyright (c) 2013 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com
4 *
5 * Amit Daniel Kachhap <amit.daniel@samsung.com>
6 *
7 * EXYNOS5440 - CPU frequency scaling support
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#include <linux/clk.h>
15#include <linux/cpu.h>
16#include <linux/cpufreq.h>
17#include <linux/err.h>
18#include <linux/interrupt.h>
19#include <linux/io.h>
20#include <linux/module.h>
21#include <linux/of_address.h>
22#include <linux/of_irq.h>
23#include <linux/opp.h>
24#include <linux/platform_device.h>
25#include <linux/slab.h>
26
27/* Register definitions */
28#define XMU_DVFS_CTRL 0x0060
29#define XMU_PMU_P0_7 0x0064
30#define XMU_C0_3_PSTATE 0x0090
31#define XMU_P_LIMIT 0x00a0
32#define XMU_P_STATUS 0x00a4
33#define XMU_PMUEVTEN 0x00d0
34#define XMU_PMUIRQEN 0x00d4
35#define XMU_PMUIRQ 0x00d8
36
37/* PMU mask and shift definations */
38#define P_VALUE_MASK 0x7
39
40#define XMU_DVFS_CTRL_EN_SHIFT 0
41
42#define P0_7_CPUCLKDEV_SHIFT 21
43#define P0_7_CPUCLKDEV_MASK 0x7
44#define P0_7_ATBCLKDEV_SHIFT 18
45#define P0_7_ATBCLKDEV_MASK 0x7
46#define P0_7_CSCLKDEV_SHIFT 15
47#define P0_7_CSCLKDEV_MASK 0x7
48#define P0_7_CPUEMA_SHIFT 28
49#define P0_7_CPUEMA_MASK 0xf
50#define P0_7_L2EMA_SHIFT 24
51#define P0_7_L2EMA_MASK 0xf
52#define P0_7_VDD_SHIFT 8
53#define P0_7_VDD_MASK 0x7f
54#define P0_7_FREQ_SHIFT 0
55#define P0_7_FREQ_MASK 0xff
56
57#define C0_3_PSTATE_VALID_SHIFT 8
58#define C0_3_PSTATE_CURR_SHIFT 4
59#define C0_3_PSTATE_NEW_SHIFT 0
60
61#define PSTATE_CHANGED_EVTEN_SHIFT 0
62
63#define PSTATE_CHANGED_IRQEN_SHIFT 0
64
65#define PSTATE_CHANGED_SHIFT 0
66
67/* some constant values for clock divider calculation */
68#define CPU_DIV_FREQ_MAX 500
69#define CPU_DBG_FREQ_MAX 375
70#define CPU_ATB_FREQ_MAX 500
71
72#define PMIC_LOW_VOLT 0x30
73#define PMIC_HIGH_VOLT 0x28
74
75#define CPUEMA_HIGH 0x2
76#define CPUEMA_MID 0x4
77#define CPUEMA_LOW 0x7
78
79#define L2EMA_HIGH 0x1
80#define L2EMA_MID 0x3
81#define L2EMA_LOW 0x4
82
83#define DIV_TAB_MAX 2
84/* frequency unit is 20MHZ */
85#define FREQ_UNIT 20
86#define MAX_VOLTAGE 1550000 /* In microvolt */
87#define VOLTAGE_STEP 12500 /* In microvolt */
88
89#define CPUFREQ_NAME "exynos5440_dvfs"
90#define DEF_TRANS_LATENCY 100000
91
92enum cpufreq_level_index {
93 L0, L1, L2, L3, L4,
94 L5, L6, L7, L8, L9,
95};
96#define CPUFREQ_LEVEL_END (L7 + 1)
97
98struct exynos_dvfs_data {
99 void __iomem *base;
100 struct resource *mem;
101 int irq;
102 struct clk *cpu_clk;
103 unsigned int cur_frequency;
104 unsigned int latency;
105 struct cpufreq_frequency_table *freq_table;
106 unsigned int freq_count;
107 struct device *dev;
108 bool dvfs_enabled;
109 struct work_struct irq_work;
110};
111
112static struct exynos_dvfs_data *dvfs_info;
113static DEFINE_MUTEX(cpufreq_lock);
114static struct cpufreq_freqs freqs;
115
116static int init_div_table(void)
117{
118 struct cpufreq_frequency_table *freq_tbl = dvfs_info->freq_table;
119 unsigned int tmp, clk_div, ema_div, freq, volt_id;
120 int i = 0;
121 struct opp *opp;
122
123 for (i = 0; freq_tbl[i].frequency != CPUFREQ_TABLE_END; i++) {
124
125 opp = opp_find_freq_exact(dvfs_info->dev,
126 freq_tbl[i].frequency * 1000, true);
127 if (IS_ERR(opp)) {
128 dev_err(dvfs_info->dev,
129 "failed to find valid OPP for %u KHZ\n",
130 freq_tbl[i].frequency);
131 return PTR_ERR(opp);
132 }
133
134 freq = freq_tbl[i].frequency / 1000; /* In MHZ */
135 clk_div = ((freq / CPU_DIV_FREQ_MAX) & P0_7_CPUCLKDEV_MASK)
136 << P0_7_CPUCLKDEV_SHIFT;
137 clk_div |= ((freq / CPU_ATB_FREQ_MAX) & P0_7_ATBCLKDEV_MASK)
138 << P0_7_ATBCLKDEV_SHIFT;
139 clk_div |= ((freq / CPU_DBG_FREQ_MAX) & P0_7_CSCLKDEV_MASK)
140 << P0_7_CSCLKDEV_SHIFT;
141
142 /* Calculate EMA */
143 volt_id = opp_get_voltage(opp);
144 volt_id = (MAX_VOLTAGE - volt_id) / VOLTAGE_STEP;
145 if (volt_id < PMIC_HIGH_VOLT) {
146 ema_div = (CPUEMA_HIGH << P0_7_CPUEMA_SHIFT) |
147 (L2EMA_HIGH << P0_7_L2EMA_SHIFT);
148 } else if (volt_id > PMIC_LOW_VOLT) {
149 ema_div = (CPUEMA_LOW << P0_7_CPUEMA_SHIFT) |
150 (L2EMA_LOW << P0_7_L2EMA_SHIFT);
151 } else {
152 ema_div = (CPUEMA_MID << P0_7_CPUEMA_SHIFT) |
153 (L2EMA_MID << P0_7_L2EMA_SHIFT);
154 }
155
156 tmp = (clk_div | ema_div | (volt_id << P0_7_VDD_SHIFT)
157 | ((freq / FREQ_UNIT) << P0_7_FREQ_SHIFT));
158
159 __raw_writel(tmp, dvfs_info->base + XMU_PMU_P0_7 + 4 * i);
160 }
161
162 return 0;
163}
164
165static void exynos_enable_dvfs(void)
166{
167 unsigned int tmp, i, cpu;
168 struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table;
169 /* Disable DVFS */
170 __raw_writel(0, dvfs_info->base + XMU_DVFS_CTRL);
171
172 /* Enable PSTATE Change Event */
173 tmp = __raw_readl(dvfs_info->base + XMU_PMUEVTEN);
174 tmp |= (1 << PSTATE_CHANGED_EVTEN_SHIFT);
175 __raw_writel(tmp, dvfs_info->base + XMU_PMUEVTEN);
176
177 /* Enable PSTATE Change IRQ */
178 tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQEN);
179 tmp |= (1 << PSTATE_CHANGED_IRQEN_SHIFT);
180 __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQEN);
181
182 /* Set initial performance index */
183 for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++)
184 if (freq_table[i].frequency == dvfs_info->cur_frequency)
185 break;
186
187 if (freq_table[i].frequency == CPUFREQ_TABLE_END) {
188 dev_crit(dvfs_info->dev, "Boot up frequency not supported\n");
189 /* Assign the highest frequency */
190 i = 0;
191 dvfs_info->cur_frequency = freq_table[i].frequency;
192 }
193
194 dev_info(dvfs_info->dev, "Setting dvfs initial frequency = %uKHZ",
195 dvfs_info->cur_frequency);
196
197 for (cpu = 0; cpu < CONFIG_NR_CPUS; cpu++) {
198 tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4);
199 tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT);
200 tmp |= (i << C0_3_PSTATE_NEW_SHIFT);
201 __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4);
202 }
203
204 /* Enable DVFS */
205 __raw_writel(1 << XMU_DVFS_CTRL_EN_SHIFT,
206 dvfs_info->base + XMU_DVFS_CTRL);
207}
208
209static int exynos_verify_speed(struct cpufreq_policy *policy)
210{
211 return cpufreq_frequency_table_verify(policy,
212 dvfs_info->freq_table);
213}
214
215static unsigned int exynos_getspeed(unsigned int cpu)
216{
217 return dvfs_info->cur_frequency;
218}
219
220static int exynos_target(struct cpufreq_policy *policy,
221 unsigned int target_freq,
222 unsigned int relation)
223{
224 unsigned int index, tmp;
225 int ret = 0, i;
226 struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table;
227
228 mutex_lock(&cpufreq_lock);
229
230 ret = cpufreq_frequency_table_target(policy, freq_table,
231 target_freq, relation, &index);
232 if (ret)
233 goto out;
234
235 freqs.old = dvfs_info->cur_frequency;
236 freqs.new = freq_table[index].frequency;
237
238 cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
239
240 /* Set the target frequency in all C0_3_PSTATE register */
241 for_each_cpu(i, policy->cpus) {
242 tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + i * 4);
243 tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT);
244 tmp |= (index << C0_3_PSTATE_NEW_SHIFT);
245
246 __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + i * 4);
247 }
248out:
249 mutex_unlock(&cpufreq_lock);
250 return ret;
251}
252
253static void exynos_cpufreq_work(struct work_struct *work)
254{
255 unsigned int cur_pstate, index;
256 struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */
257 struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table;
258
259 /* Ensure we can access cpufreq structures */
260 if (unlikely(dvfs_info->dvfs_enabled == false))
261 goto skip_work;
262
263 mutex_lock(&cpufreq_lock);
264 freqs.old = dvfs_info->cur_frequency;
265
266 cur_pstate = __raw_readl(dvfs_info->base + XMU_P_STATUS);
267 if (cur_pstate >> C0_3_PSTATE_VALID_SHIFT & 0x1)
268 index = (cur_pstate >> C0_3_PSTATE_CURR_SHIFT) & P_VALUE_MASK;
269 else
270 index = (cur_pstate >> C0_3_PSTATE_NEW_SHIFT) & P_VALUE_MASK;
271
272 if (likely(index < dvfs_info->freq_count)) {
273 freqs.new = freq_table[index].frequency;
274 dvfs_info->cur_frequency = freqs.new;
275 } else {
276 dev_crit(dvfs_info->dev, "New frequency out of range\n");
277 freqs.new = dvfs_info->cur_frequency;
278 }
279 cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
280
281 cpufreq_cpu_put(policy);
282 mutex_unlock(&cpufreq_lock);
283skip_work:
284 enable_irq(dvfs_info->irq);
285}
286
287static irqreturn_t exynos_cpufreq_irq(int irq, void *id)
288{
289 unsigned int tmp;
290
291 tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQ);
292 if (tmp >> PSTATE_CHANGED_SHIFT & 0x1) {
293 __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQ);
294 disable_irq_nosync(irq);
295 schedule_work(&dvfs_info->irq_work);
296 }
297 return IRQ_HANDLED;
298}
299
300static void exynos_sort_descend_freq_table(void)
301{
302 struct cpufreq_frequency_table *freq_tbl = dvfs_info->freq_table;
303 int i = 0, index;
304 unsigned int tmp_freq;
305 /*
306 * Exynos5440 clock controller state logic expects the cpufreq table to
307 * be in descending order. But the OPP library constructs the table in
308 * ascending order. So to make the table descending we just need to
309 * swap the i element with the N - i element.
310 */
311 for (i = 0; i < dvfs_info->freq_count / 2; i++) {
312 index = dvfs_info->freq_count - i - 1;
313 tmp_freq = freq_tbl[i].frequency;
314 freq_tbl[i].frequency = freq_tbl[index].frequency;
315 freq_tbl[index].frequency = tmp_freq;
316 }
317}
318
319static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy)
320{
321 int ret;
322
323 ret = cpufreq_frequency_table_cpuinfo(policy, dvfs_info->freq_table);
324 if (ret) {
325 dev_err(dvfs_info->dev, "Invalid frequency table: %d\n", ret);
326 return ret;
327 }
328
329 policy->cur = dvfs_info->cur_frequency;
330 policy->cpuinfo.transition_latency = dvfs_info->latency;
331 cpumask_setall(policy->cpus);
332
333 cpufreq_frequency_table_get_attr(dvfs_info->freq_table, policy->cpu);
334
335 return 0;
336}
337
338static struct cpufreq_driver exynos_driver = {
339 .flags = CPUFREQ_STICKY,
340 .verify = exynos_verify_speed,
341 .target = exynos_target,
342 .get = exynos_getspeed,
343 .init = exynos_cpufreq_cpu_init,
344 .name = CPUFREQ_NAME,
345};
346
347static const struct of_device_id exynos_cpufreq_match[] = {
348 {
349 .compatible = "samsung,exynos5440-cpufreq",
350 },
351 {},
352};
353MODULE_DEVICE_TABLE(of, exynos_cpufreq_match);
354
355static int exynos_cpufreq_probe(struct platform_device *pdev)
356{
357 int ret = -EINVAL;
358 struct device_node *np;
359 struct resource res;
360
361 np = pdev->dev.of_node;
362 if (!np)
363 return -ENODEV;
364
365 dvfs_info = devm_kzalloc(&pdev->dev, sizeof(*dvfs_info), GFP_KERNEL);
366 if (!dvfs_info) {
367 ret = -ENOMEM;
368 goto err_put_node;
369 }
370
371 dvfs_info->dev = &pdev->dev;
372
373 ret = of_address_to_resource(np, 0, &res);
374 if (ret)
375 goto err_put_node;
376
377 dvfs_info->base = devm_ioremap_resource(dvfs_info->dev, &res);
378 if (IS_ERR(dvfs_info->base)) {
379 ret = PTR_ERR(dvfs_info->base);
380 goto err_put_node;
381 }
382
383 dvfs_info->irq = irq_of_parse_and_map(np, 0);
384 if (!dvfs_info->irq) {
385 dev_err(dvfs_info->dev, "No cpufreq irq found\n");
386 ret = -ENODEV;
387 goto err_put_node;
388 }
389
390 ret = of_init_opp_table(dvfs_info->dev);
391 if (ret) {
392 dev_err(dvfs_info->dev, "failed to init OPP table: %d\n", ret);
393 goto err_put_node;
394 }
395
396 ret = opp_init_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table);
397 if (ret) {
398 dev_err(dvfs_info->dev,
399 "failed to init cpufreq table: %d\n", ret);
400 goto err_put_node;
401 }
402 dvfs_info->freq_count = opp_get_opp_count(dvfs_info->dev);
403 exynos_sort_descend_freq_table();
404
405 if (of_property_read_u32(np, "clock-latency", &dvfs_info->latency))
406 dvfs_info->latency = DEF_TRANS_LATENCY;
407
408 dvfs_info->cpu_clk = devm_clk_get(dvfs_info->dev, "armclk");
409 if (IS_ERR(dvfs_info->cpu_clk)) {
410 dev_err(dvfs_info->dev, "Failed to get cpu clock\n");
411 ret = PTR_ERR(dvfs_info->cpu_clk);
412 goto err_free_table;
413 }
414
415 dvfs_info->cur_frequency = clk_get_rate(dvfs_info->cpu_clk);
416 if (!dvfs_info->cur_frequency) {
417 dev_err(dvfs_info->dev, "Failed to get clock rate\n");
418 ret = -EINVAL;
419 goto err_free_table;
420 }
421 dvfs_info->cur_frequency /= 1000;
422
423 INIT_WORK(&dvfs_info->irq_work, exynos_cpufreq_work);
424 ret = devm_request_irq(dvfs_info->dev, dvfs_info->irq,
425 exynos_cpufreq_irq, IRQF_TRIGGER_NONE,
426 CPUFREQ_NAME, dvfs_info);
427 if (ret) {
428 dev_err(dvfs_info->dev, "Failed to register IRQ\n");
429 goto err_free_table;
430 }
431
432 ret = init_div_table();
433 if (ret) {
434 dev_err(dvfs_info->dev, "Failed to initialise div table\n");
435 goto err_free_table;
436 }
437
438 exynos_enable_dvfs();
439 ret = cpufreq_register_driver(&exynos_driver);
440 if (ret) {
441 dev_err(dvfs_info->dev,
442 "%s: failed to register cpufreq driver\n", __func__);
443 goto err_free_table;
444 }
445
446 of_node_put(np);
447 dvfs_info->dvfs_enabled = true;
448 return 0;
449
450err_free_table:
451 opp_free_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table);
452err_put_node:
453 of_node_put(np);
454 dev_err(dvfs_info->dev, "%s: failed initialization\n", __func__);
455 return ret;
456}
457
458static int exynos_cpufreq_remove(struct platform_device *pdev)
459{
460 cpufreq_unregister_driver(&exynos_driver);
461 opp_free_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table);
462 return 0;
463}
464
465static struct platform_driver exynos_cpufreq_platdrv = {
466 .driver = {
467 .name = "exynos5440-cpufreq",
468 .owner = THIS_MODULE,
469 .of_match_table = exynos_cpufreq_match,
470 },
471 .probe = exynos_cpufreq_probe,
472 .remove = exynos_cpufreq_remove,
473};
474module_platform_driver(exynos_cpufreq_platdrv);
475
476MODULE_AUTHOR("Amit Daniel Kachhap <amit.daniel@samsung.com>");
477MODULE_DESCRIPTION("Exynos5440 cpufreq driver");
478MODULE_LICENSE("GPL");