blob: d5f82f2c33ec078cff470f5234fa86869f5a4071 [file] [log] [blame]
xjb04a4022021-11-25 15:01:52 +08001// SPDX-License-Identifier: GPL-2.0
2/*
3 * mediatek-mt6880-cpufreq.c - MT6880 CPUFreq Driver
4 *
5 * Copyright (c) 2020 MediaTek Inc.
6 * Wei-Chia Su <Wei-Chia.Su@mediatek.com>
7 */
8
9#include <asm-generic/delay.h>
10#include <clk-mtk.h>
11#include <mt6880-clk.h>
12#include <linux/clk.h>
13#include <linux/clk-provider.h>
14#include <linux/cpu.h>
15#include <linux/cpu_cooling.h>
16#include <linux/cpufreq.h>
17#include <linux/cpumask.h>
18#include <linux/energy_model.h>
19#include <linux/io.h>
20#include <linux/module.h>
21#include <linux/of.h>
22#include <linux/of_address.h>
23#include <linux/of_platform.h>
24#include <linux/platform_device.h>
25#include <linux/pm_opp.h>
26#include <linux/regulator/consumer.h>
27#include <linux/slab.h>
28#include <linux/thermal.h>
29
30enum cpu_level {
31 CPU_LEVEL_0,
32
33 NUM_CPU_LEVEL
34};
35
36enum opp_idx_type {
37 CUR_OPP_IDX = 0,
38 TARGET_OPP_IDX = 1,
39
40 NR_OPP_IDX,
41};
42
43enum mt_cpu_dvfs_id {
44 MT_CPU_DVFS_LL,
45 MT_CPU_DVFS_CCI,
46
47 NR_MT_CPU_DVFS,
48};
49
50#define FHCTL (1)
51#define mt_reg_sync_writel(v, a) \
52 do { \
53 __raw_writel((v), (void __force __iomem *)((a))); \
54 mb(); \
55 } while (0)
56#define _BIT_(_bit_) (unsigned)(1 << (_bit_))
57#define _BITS_(_bits_, _val_) ((((unsigned) -1 >> (31 - ((1) ? _bits_))) & ~((1U << ((0) ? _bits_)) - 1)) & ((_val_)<<((0) ? _bits_)))
58#define _BITMASK_(_bits_) (((unsigned) -1 >> (31 - ((1) ? _bits_))) & ~((1U << ((0) ? _bits_)) - 1))
59#define _GET_BITS_VAL_(_bits_, _val_) (((_val_) & (_BITMASK_(_bits_))) >> ((0) ? _bits_))
60#define IOMEM(a) ((void __force __iomem *)((a)))
61#define cpufreq_read(addr) __raw_readl(IOMEM(addr))
62#define cpufreq_write(addr, val) mt_reg_sync_writel((val), ((void *)addr))
63#define cpufreq_write_mask(addr, mask, val) cpufreq_write(addr, (cpufreq_read(addr) & ~(_BITMASK_(mask))) | _BITS_(mask, val))
64#define PLL_SETTLE_TIME 20
65#define POS_SETTLE_TIME 1
66#define APMIXED_NODE "mediatek,apmixed"
67#define MCUCFG_NODE "mediatek,mcucfg"
68#define ARMPLL_LL_CON2 (apmixed_base+ 0x20c)
69#define CCIPLL_CON2 (apmixed_base+ 0x220)
70#define CKDIV1_LL_CFG (mcucfg_base + 0xa2a0)
71#define CKDIV1_CCI_CFG (mcucfg_base + 0xa2e0)
72#define cpu_dvfs_is(p, id) (p == &cpu_dvfs[id])
73#define for_each_cpu_dvfs(i, p) for (i = 0, p = cpu_dvfs; i < NR_MT_CPU_DVFS; i++, p = &cpu_dvfs[i])
74#define for_each_cpu_dvfs_only(i, p) \
75 for (i = 0, p = cpu_dvfs; (i < NR_MT_CPU_DVFS) && (i != MT_CPU_DVFS_CCI); i++, p = &cpu_dvfs[i])
76#define FP(pos, clk) { \
77 .pos_div = pos, \
78 .clk_div = clk, \
79}
80
81struct mt_cpu_freq_info {
82 /*const*/ unsigned int cpufreq_khz;
83 unsigned int cpufreq_volt;
84};
85
86struct mt_cpu_dvfs {
87 const char *name;
88 const enum mt_cpu_dvfs_id id;
89 unsigned int *armpll_addr;
90 unsigned int *ckdiv_addr;
91 struct mt_cpu_freq_method *freq_tbl;
92};
93
94struct cpufreq_frequency_table *cci_freq_table;
95struct clk *cci_clk;
96struct mtk_cpu_dvfs_info {
97 struct cpumask cpus;
98 struct device *cpu_dev;
99 struct regulator *proc_reg;
100 struct clk *cpu_clk;
101 struct list_head list_head;
102};
103
104static LIST_HEAD(dvfs_info_list);
105
106static struct mt_cpu_dvfs cpu_dvfs[] = {
107 [MT_CPU_DVFS_LL] = {
108 .name = __stringify(MT_CPU_DVFS_LL),
109 .id = MT_CPU_DVFS_LL,
110 },
111 [MT_CPU_DVFS_CCI] = {
112 .name = __stringify(MT_CPU_DVFS_CCI),
113 .id = MT_CPU_DVFS_CCI,
114 },
115};
116
117static unsigned long apmixed_base;
118static unsigned long mcucfg_base;
119
120static struct mt_cpu_dvfs *id_to_cpu_dvfs(enum mt_cpu_dvfs_id id)
121{
122 return (id < NR_MT_CPU_DVFS) ? &cpu_dvfs[id] : NULL;
123}
124
125struct mt_cpu_freq_method {
126 const unsigned int pos_div;
127 const unsigned int clk_div;
128};
129
130struct opp_idx_tbl {
131 struct mt_cpu_dvfs *p;
132 struct mt_cpu_freq_method *slot;
133};
134
135static struct opp_idx_tbl opp_tbl_m[NR_OPP_IDX];
136static struct mt_cpu_freq_method opp_tbl_method_LL_FY[] = {
137 FP(4, 1),
138 FP(4, 1),
139 FP(4, 1),
140 FP(2, 1),
141 FP(2, 1),
142};
143
144static struct mt_cpu_freq_method opp_tbl_method_CCI_FY[] = {
145 FP(4, 1),
146 FP(4, 1),
147 FP(4, 1),
148 FP(4, 1),
149 FP(2, 1),
150};
151
152struct opp_tbl_info {
153 struct mt_cpu_freq_info *const opp_tbl;
154 const int size;
155};
156
157struct opp_tbl_m_info {
158 struct mt_cpu_freq_method *const opp_tbl_m;
159};
160
161static struct opp_tbl_m_info opp_tbls_m[NR_MT_CPU_DVFS][NUM_CPU_LEVEL] = {
162 {
163 [CPU_LEVEL_0] = { opp_tbl_method_LL_FY },
164 },
165 {
166 [CPU_LEVEL_0] = { opp_tbl_method_CCI_FY },
167 },
168};
169
170static unsigned int _cpu_dds_calc(unsigned int khz)
171{
172 unsigned int dds;
173
174 dds = ((khz / 1000) << 14) / 26;
175
176 return dds;
177}
178
179#if !FHCTL
180static void adjust_armpll_dds(struct mt_cpu_dvfs *p, unsigned int vco, unsigned int pos_div)
181{
182 unsigned int dds;
183 unsigned int val;
184
185 dds = _GET_BITS_VAL_(21:0, vco);
186 val = cpufreq_read(p->armpll_addr) & ~(_BITMASK_(21:0));
187 val |= dds;
188 cpufreq_write(p->armpll_addr, val | _BIT_(31) /* CHG */);
189 udelay(PLL_SETTLE_TIME);
190}
191#endif
192
193static void adjust_posdiv(struct mt_cpu_dvfs *p, unsigned int pos_div)
194{
195 unsigned int sel;
196
197 sel = (pos_div == 1 ? 0 :
198 pos_div == 2 ? 1 :
199 pos_div == 4 ? 2 : 0);
200 cpufreq_write_mask(p->armpll_addr, 26:24, sel);
201 udelay(POS_SETTLE_TIME);
202}
203
204static void adjust_clkdiv(struct mt_cpu_dvfs *p, unsigned int clk_div)
205{
206 unsigned int sel;
207
208 sel = (clk_div == 1 ? 8 :
209 clk_div == 2 ? 10 :
210 clk_div == 4 ? 11 : 8);
211 cpufreq_write_mask(p->ckdiv_addr, 21:17, sel);
212}
213
214static void set_cur_freq(struct mt_cpu_dvfs *p, unsigned int target_khz, int idx)
215{
216 unsigned int sel, cur_posdiv, cur_clkdiv, dds;
217
218 sel = _GET_BITS_VAL_(26:24, cpufreq_read(p->armpll_addr));
219 cur_posdiv = (sel == 0 ? 1 :
220 sel == 1 ? 2 :
221 sel == 2 ? 4 : 1);
222
223 sel = _GET_BITS_VAL_(21:17, cpufreq_read(p->ckdiv_addr));
224 cur_clkdiv = (sel == 8 ? 1 :
225 sel == 10 ? 2 :
226 sel == 11 ? 4 : 1);
227
228 opp_tbl_m[TARGET_OPP_IDX].p = p;
229 opp_tbl_m[TARGET_OPP_IDX].slot = &p->freq_tbl[idx];
230
231 /* post_div 1 -> 2 */
232 if (cur_posdiv < opp_tbl_m[TARGET_OPP_IDX].slot->pos_div)
233 adjust_posdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->pos_div);
234
235 /* armpll_div 1 -> 2 */
236 if (cur_clkdiv < opp_tbl_m[TARGET_OPP_IDX].slot->clk_div)
237 adjust_clkdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->clk_div);
238
239 dds = _cpu_dds_calc(target_khz *
240 opp_tbl_m[TARGET_OPP_IDX].slot->pos_div * opp_tbl_m[TARGET_OPP_IDX].slot->clk_div);
241
242#if !FHCTL
243 adjust_armpll_dds(p, dds, opp_tbl_m[TARGET_OPP_IDX].slot->pos_div);
244#else
245 if (cpu_dvfs_is(p, MT_CPU_DVFS_CCI))
246 mtk_fh_set_rate(CLK_TOP_CCIPLL_CK_VRPOC_CCI, dds,-1);
247 else if (cpu_dvfs_is(p, MT_CPU_DVFS_LL))
248 mtk_fh_set_rate(CLK_TOP_ARMPLL_LL_CK_VRPOC, dds,-1);
249#endif
250
251 /* armpll_div 2 -> 1 */
252 if (cur_clkdiv > opp_tbl_m[TARGET_OPP_IDX].slot->clk_div)
253 adjust_clkdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->clk_div);
254
255 /* post_div 2 -> 1 */
256 if (cur_posdiv > opp_tbl_m[TARGET_OPP_IDX].slot->pos_div)
257 adjust_posdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->pos_div);
258}
259
260static struct mtk_cpu_dvfs_info *mtk_cpu_dvfs_info_lookup(int cpu)
261{
262 struct mtk_cpu_dvfs_info *info;
263
264 list_for_each_entry(info, &dvfs_info_list, list_head) {
265 if (cpumask_test_cpu(cpu, &info->cpus))
266 return info;
267 }
268
269 return NULL;
270}
271
272static int mtk_cpufreq_set_voltage(struct mtk_cpu_dvfs_info *info, int vproc)
273{
274 return regulator_set_voltage(info->proc_reg, vproc, INT_MAX);
275}
276
277#if 0
278static unsigned int pll_to_clk(unsigned int pll_f, unsigned int ckdiv1)
279{
280 unsigned int freq = pll_f;
281
282 switch (ckdiv1) {
283 case 8:
284 break;
285 case 9:
286
287 freq = freq * 3 / 4;
288 break;
289 case 10:
290 freq = freq * 2 / 4;
291 break;
292 case 11:
293 freq = freq * 1 / 4;
294 break;
295 case 16:
296 break;
297 case 17:
298 freq = freq * 4 / 5;
299 break;
300 case 18:
301 freq = freq * 3 / 5;
302 break;
303 case 19:
304 freq = freq * 2 / 5;
305 break;
306 case 20:
307 freq = freq * 1 / 5;
308 break;
309 case 24:
310 break;
311 case 25:
312 freq = freq * 5 / 6;
313 break;
314 case 26:
315 freq = freq * 4 / 6;
316 break;
317 case 27:
318 freq = freq * 3 / 6;
319 break;
320 case 28:
321 freq = freq * 2 / 6;
322 break;
323 case 29:
324 freq = freq * 1 / 6;
325 break;
326 default:
327 break;
328 }
329
330 return freq;
331}
332
333static unsigned int _cpu_freq_calc(unsigned int con1, unsigned int ckdiv1)
334{
335 unsigned int freq;
336 unsigned int posdiv;
337
338 posdiv = _GET_BITS_VAL_(26:24, con1);
339
340 con1 &= _BITMASK_(21:0);
341 freq = ((con1 * 26) >> 14) * 1000;
342
343 switch (posdiv) {
344 case 0:
345 break;
346 case 1:
347 freq = freq / 2;
348 break;
349 case 2:
350 freq = freq / 4;
351 break;
352 case 3:
353 freq = freq / 8;
354 break;
355 default:
356 freq = freq / 16;
357 break;
358 };
359
360 return pll_to_clk(freq, ckdiv1);
361}
362
363static unsigned int get_cur_phy_freq(struct mt_cpu_dvfs *p)
364{
365 unsigned int con1;
366 unsigned int ckdiv1;
367 unsigned int cur_khz;
368
369 con1 = cpufreq_read(p->armpll_addr);
370 ckdiv1 = cpufreq_read(p->ckdiv_addr);
371 ckdiv1 = _GET_BITS_VAL_(21:17, ckdiv1);
372
373 cur_khz = _cpu_freq_calc(con1, ckdiv1);
374
375 return cur_khz;
376}
377#endif
378
379static int mtk_cpufreq_set_target(struct cpufreq_policy *policy,
380 unsigned int index)
381{
382 struct cpufreq_frequency_table *freq_table = policy->freq_table;
383 struct mtk_cpu_dvfs_info *info = policy->driver_data;
384 struct device *cpu_dev = info->cpu_dev;
385 struct dev_pm_opp *opp, *old_opp;
386 long freq_hz, old_freq_hz, freq_hz_cci;
387 int vproc, old_vproc, target_vproc, ret;
388
389 freq_hz_cci = cci_freq_table[index].frequency * 1000;
390 freq_hz = freq_table[index].frequency * 1000;
391
392 old_freq_hz = policy->cur * 1000;
393 old_opp = dev_pm_opp_find_freq_ceil(cpu_dev, &old_freq_hz);
394 if (IS_ERR(old_opp))
395 return PTR_ERR(old_opp);
396 old_vproc = dev_pm_opp_get_voltage(old_opp);
397 dev_pm_opp_put(old_opp);
398
399 opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
400 if (IS_ERR(opp))
401 return PTR_ERR(opp);
402 vproc = dev_pm_opp_get_voltage(opp);
403 dev_pm_opp_put(opp);
404
405 target_vproc = vproc;
406 if (old_vproc < target_vproc) {
407 ret = mtk_cpufreq_set_voltage(info, target_vproc);
408 if (ret) {
409 mtk_cpufreq_set_voltage(info, old_vproc);
410 return ret;
411 }
412 }
413
414 set_cur_freq(id_to_cpu_dvfs(MT_CPU_DVFS_CCI), freq_hz_cci/1000, index);
415 set_cur_freq(id_to_cpu_dvfs(MT_CPU_DVFS_LL), freq_hz/1000, index);
416
417 if (vproc < old_vproc) {
418 ret = mtk_cpufreq_set_voltage(info, vproc);
419 if (ret)
420 return ret;
421 }
422
423 return 0;
424}
425
426static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu)
427{
428 struct device *cpu_dev;
429 struct regulator *proc_reg = ERR_PTR(-ENODEV);
430 struct clk *cpu_clk = ERR_PTR(-ENODEV);
431 struct platform_device *npdev;
432 struct device_node *node;
433 int ret;
434
435 cpu_dev = get_cpu_device(cpu);
436 if (!cpu_dev)
437 return -ENODEV;
438
439 cpu_clk = clk_get(cpu_dev, "cpu");
440 if (IS_ERR(cpu_clk))
441 return PTR_ERR(cpu_clk);
442
443 proc_reg = regulator_get_optional(cpu_dev, "proc");
444 if (IS_ERR(proc_reg)) {
445 ret = PTR_ERR(proc_reg);
446 goto out_free_resources;
447 }
448
449 node = of_find_node_by_name(NULL, "cci");
450 if (!node) {
451 ret = -ENODEV;
452 goto out_free_resources;
453 }
454
455 npdev = of_device_alloc(node, NULL, NULL);
456 if (!npdev) {
457 ret = -ENODEV;
458 goto out_free_resources;
459 }
460
461 if (of_device_is_compatible(node, "mediatek,mt6880-cci"))
462 npdev->dev.of_node = node;
463 else {
464 ret = -ENODEV;
465 goto out_free_resources;
466 }
467
468 ret = dev_pm_opp_of_add_table(&npdev->dev);
469 if (ret) {
470 ret = -ENODEV;
471 goto out_free_resources;
472 }
473
474 ret = dev_pm_opp_init_cpufreq_table(&npdev->dev, &cci_freq_table);
475 if (ret) {
476 ret = -ENODEV;
477 goto out_free_resources;
478 }
479
480 ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, &info->cpus);
481 if (ret) {
482 ret = -ENODEV;
483 goto out_free_cpufreq_table;
484 }
485
486 ret = dev_pm_opp_of_cpumask_add_table(&info->cpus);
487 if (ret) {
488 ret = -ENODEV;
489 goto out_free_cpufreq_table;
490 }
491
492 info->cpu_dev = cpu_dev;
493 info->proc_reg = proc_reg;
494 info->cpu_clk = cpu_clk;
495
496 if (npdev) {
497 of_platform_device_destroy(&npdev->dev, NULL);
498 of_dev_put(npdev);
499 }
500
501 return 0;
502
503out_free_cpufreq_table:
504 dev_pm_opp_free_cpufreq_table(cpu_dev, &cci_freq_table);
505out_free_resources:
506 if (npdev) {
507 of_platform_device_destroy(&npdev->dev, NULL);
508 of_dev_put(npdev);
509 }
510 if (!IS_ERR(proc_reg))
511 regulator_put(proc_reg);
512 if (!IS_ERR(cpu_clk))
513 clk_put(cpu_clk);
514
515 return ret;
516}
517
518static void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info)
519{
520 if (!IS_ERR(info->proc_reg))
521 regulator_put(info->proc_reg);
522 if (!IS_ERR(info->cpu_clk))
523 clk_put(info->cpu_clk);
524 dev_pm_opp_of_cpumask_remove_table(&info->cpus);
525}
526
527static int mtk_cpufreq_init(struct cpufreq_policy *policy)
528{
529 struct mtk_cpu_dvfs_info *info;
530 struct cpufreq_frequency_table *freq_table;
531 struct em_data_callback em_cb = EM_DATA_CB(of_dev_pm_opp_get_cpu_power);
532 int ret;
533
534 info = mtk_cpu_dvfs_info_lookup(policy->cpu);
535 if (!info)
536 return -EINVAL;
537
538 ret = dev_pm_opp_get_opp_count(info->cpu_dev);
539 if (ret <= 0)
540 return -EINVAL;
541
542 ret = dev_pm_opp_init_cpufreq_table(info->cpu_dev, &freq_table);
543 if (ret)
544 return -EINVAL;
545
546 cpumask_copy(policy->cpus, &info->cpus);
547 em_register_perf_domain(policy->cpus, ret, &em_cb);
548 policy->freq_table = freq_table;
549 policy->driver_data = info;
550 policy->clk = info->cpu_clk;
551 return 0;
552}
553
554static int mtk_cpufreq_exit(struct cpufreq_policy *policy)
555{
556 struct mtk_cpu_dvfs_info *info = policy->driver_data;
557
558 dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table);
559
560 return 0;
561}
562
563static struct cpufreq_driver mtk_cpufreq_driver = {
564 .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK |
565 CPUFREQ_HAVE_GOVERNOR_PER_POLICY | CPUFREQ_IS_COOLING_DEV,
566 .verify = cpufreq_generic_frequency_table_verify,
567 .target_index = mtk_cpufreq_set_target,
568 .get = cpufreq_generic_get,
569 .init = mtk_cpufreq_init,
570 .exit = mtk_cpufreq_exit,
571 .name = "mtk-cpufreq",
572 .attr = cpufreq_generic_attr,
573};
574
575static int mtk_cpufreq_probe(struct platform_device *pdev)
576{
577 struct mtk_cpu_dvfs_info *info, *tmp;
578 struct mt_cpu_dvfs *p;
579 int cpu, ret, j;
580 struct opp_tbl_m_info *opp_tbl_info;
581 struct device_node *node;
582
583 node = of_find_compatible_node(NULL, NULL, APMIXED_NODE);
584 if (!node)
585 return -ENODEV;
586
587 apmixed_base = (unsigned long)of_iomap(node, 0);
588 if (!apmixed_base)
589 return -ENODEV;
590
591 node = of_find_compatible_node(NULL, NULL, MCUCFG_NODE);
592 if (!node)
593 return -ENODEV;
594
595 mcucfg_base = (unsigned long)of_iomap(node, 0);
596 if (!mcucfg_base)
597 return -ENODEV;
598
599 for_each_cpu_dvfs(j, p) {
600 if (cpu_dvfs_is(p, MT_CPU_DVFS_LL)) {
601 p->armpll_addr = (unsigned int *)ARMPLL_LL_CON2;
602 p->ckdiv_addr = (unsigned int *)CKDIV1_LL_CFG;
603 } else { /* CCI */
604 p->armpll_addr = (unsigned int *)CCIPLL_CON2;
605 p->ckdiv_addr = (unsigned int *)CKDIV1_CCI_CFG;
606 }
607 opp_tbl_info = &opp_tbls_m[j][0];
608 p->freq_tbl = opp_tbl_info->opp_tbl_m;
609 }
610
611 for_each_possible_cpu(cpu) {
612 info = mtk_cpu_dvfs_info_lookup(cpu);
613 if (info)
614 continue;
615
616 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
617 if (!info) {
618 ret = -ENOMEM;
619 goto release_dvfs_info_list;
620 }
621
622 ret = mtk_cpu_dvfs_info_init(info, cpu);
623 if (ret)
624 goto release_dvfs_info_list;
625
626 list_add(&info->list_head, &dvfs_info_list);
627 }
628
629 ret = cpufreq_register_driver(&mtk_cpufreq_driver);
630 if (ret)
631 goto release_dvfs_info_list;
632
633 return 0;
634
635release_dvfs_info_list:
636 list_for_each_entry_safe(info, tmp, &dvfs_info_list, list_head) {
637 mtk_cpu_dvfs_info_release(info);
638 list_del(&info->list_head);
639 }
640
641 return ret;
642}
643
644static struct platform_driver mtk_cpufreq_platdrv = {
645 .driver = {
646 .name = "mtk-cpufreq",
647 },
648 .probe = mtk_cpufreq_probe,
649};
650
651static const struct of_device_id mtk_cpufreq_machines[] __initconst = {
652 { .compatible = "mediatek,mt6880", },
653 { }
654};
655
656static int __init mtk_cpufreq_driver_init(void)
657{
658 struct device_node *np;
659 const struct of_device_id *match;
660 struct platform_device *pdev;
661 int err;
662
663 np = of_find_node_by_path("/");
664 if (!np)
665 return -ENODEV;
666
667 match = of_match_node(mtk_cpufreq_machines, np);
668 of_node_put(np);
669 if (!match)
670 return -ENODEV;
671
672 err = platform_driver_register(&mtk_cpufreq_platdrv);
673 if (err)
674 return err;
675
676 pdev = platform_device_register_simple("mtk-cpufreq", -1, NULL, 0);
677 if (IS_ERR(pdev))
678 return PTR_ERR(pdev);
679
680 return 0;
681}
682device_initcall(mtk_cpufreq_driver_init);
683
684MODULE_DESCRIPTION("MediaTek CPUFreq driver");
685MODULE_AUTHOR("Wei-Chia Su <wei-chia.su@mediatek.com>");
686MODULE_LICENSE("GPL v2");