2 * CPU Frequency Scaling for Loongson 1 SoC
4 * Copyright (C) 2014 Zhang, Keguang <keguang.zhang@gmail.com>
6 * This file is licensed under the terms of the GNU General Public
7 * License version 2. This program is licensed "as is" without any
8 * warranty of any kind, whether express or implied.
11 #include <linux/clk.h>
12 #include <linux/clk-provider.h>
13 #include <linux/cpu.h>
14 #include <linux/cpufreq.h>
15 #include <linux/delay.h>
16 #include <linux/module.h>
17 #include <linux/platform_device.h>
18 #include <linux/slab.h>
20 #include <asm/mach-loongson1/cpufreq.h>
21 #include <asm/mach-loongson1/loongson1.h>
25 struct clk
*clk
; /* CPU clk */
26 struct clk
*mux_clk
; /* MUX of CPU clk */
27 struct clk
*pll_clk
; /* PLL clk */
28 struct clk
*osc_clk
; /* OSC clk */
29 unsigned int max_freq
;
30 unsigned int min_freq
;
33 static int ls1x_cpufreq_notifier(struct notifier_block
*nb
,
34 unsigned long val
, void *data
)
36 if (val
== CPUFREQ_POSTCHANGE
)
37 current_cpu_data
.udelay_val
= loops_per_jiffy
;
42 static struct notifier_block ls1x_cpufreq_notifier_block
= {
43 .notifier_call
= ls1x_cpufreq_notifier
46 static int ls1x_cpufreq_target(struct cpufreq_policy
*policy
,
49 unsigned int old_freq
, new_freq
;
51 old_freq
= policy
->cur
;
52 new_freq
= policy
->freq_table
[index
].frequency
;
55 * The procedure of reconfiguring CPU clk is as below.
57 * - Reparent CPU clk to OSC clk
58 * - Reset CPU clock (very important)
59 * - Reconfigure CPU DIV
60 * - Reparent CPU clk back to CPU DIV clk
63 dev_dbg(ls1x_cpufreq
.dev
, "%u KHz --> %u KHz\n", old_freq
, new_freq
);
64 clk_set_parent(policy
->clk
, ls1x_cpufreq
.osc_clk
);
65 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV
) | RST_CPU_EN
| RST_CPU
,
67 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV
) & ~(RST_CPU_EN
| RST_CPU
),
69 clk_set_rate(ls1x_cpufreq
.mux_clk
, new_freq
* 1000);
70 clk_set_parent(policy
->clk
, ls1x_cpufreq
.mux_clk
);
75 static int ls1x_cpufreq_init(struct cpufreq_policy
*policy
)
77 struct cpufreq_frequency_table
*freq_tbl
;
78 unsigned int pll_freq
, freq
;
81 pll_freq
= clk_get_rate(ls1x_cpufreq
.pll_clk
) / 1000;
83 steps
= 1 << DIV_CPU_WIDTH
;
84 freq_tbl
= kzalloc(sizeof(*freq_tbl
) * steps
, GFP_KERNEL
);
86 dev_err(ls1x_cpufreq
.dev
,
87 "failed to alloc cpufreq_frequency_table\n");
92 for (i
= 0; i
< (steps
- 1); i
++) {
93 freq
= pll_freq
/ (i
+ 1);
94 if ((freq
< ls1x_cpufreq
.min_freq
) ||
95 (freq
> ls1x_cpufreq
.max_freq
))
96 freq_tbl
[i
].frequency
= CPUFREQ_ENTRY_INVALID
;
98 freq_tbl
[i
].frequency
= freq
;
99 dev_dbg(ls1x_cpufreq
.dev
,
100 "cpufreq table: index %d: frequency %d\n", i
,
101 freq_tbl
[i
].frequency
);
103 freq_tbl
[i
].frequency
= CPUFREQ_TABLE_END
;
105 policy
->clk
= ls1x_cpufreq
.clk
;
106 ret
= cpufreq_generic_init(policy
, freq_tbl
, 0);
113 static int ls1x_cpufreq_exit(struct cpufreq_policy
*policy
)
115 kfree(policy
->freq_table
);
119 static struct cpufreq_driver ls1x_cpufreq_driver
= {
120 .name
= "cpufreq-ls1x",
121 .flags
= CPUFREQ_STICKY
| CPUFREQ_NEED_INITIAL_FREQ_CHECK
,
122 .verify
= cpufreq_generic_frequency_table_verify
,
123 .target_index
= ls1x_cpufreq_target
,
124 .get
= cpufreq_generic_get
,
125 .init
= ls1x_cpufreq_init
,
126 .exit
= ls1x_cpufreq_exit
,
127 .attr
= cpufreq_generic_attr
,
130 static int ls1x_cpufreq_remove(struct platform_device
*pdev
)
132 cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block
,
133 CPUFREQ_TRANSITION_NOTIFIER
);
134 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
139 static int ls1x_cpufreq_probe(struct platform_device
*pdev
)
141 struct plat_ls1x_cpufreq
*pdata
= pdev
->dev
.platform_data
;
145 if (!pdata
|| !pdata
->clk_name
|| !pdata
->osc_clk_name
)
148 ls1x_cpufreq
.dev
= &pdev
->dev
;
150 clk
= devm_clk_get(&pdev
->dev
, pdata
->clk_name
);
152 dev_err(ls1x_cpufreq
.dev
, "unable to get %s clock\n",
157 ls1x_cpufreq
.clk
= clk
;
159 clk
= clk_get_parent(clk
);
161 dev_err(ls1x_cpufreq
.dev
, "unable to get parent of %s clock\n",
162 __clk_get_name(ls1x_cpufreq
.clk
));
166 ls1x_cpufreq
.mux_clk
= clk
;
168 clk
= clk_get_parent(clk
);
170 dev_err(ls1x_cpufreq
.dev
, "unable to get parent of %s clock\n",
171 __clk_get_name(ls1x_cpufreq
.mux_clk
));
175 ls1x_cpufreq
.pll_clk
= clk
;
177 clk
= devm_clk_get(&pdev
->dev
, pdata
->osc_clk_name
);
179 dev_err(ls1x_cpufreq
.dev
, "unable to get %s clock\n",
180 pdata
->osc_clk_name
);
184 ls1x_cpufreq
.osc_clk
= clk
;
186 ls1x_cpufreq
.max_freq
= pdata
->max_freq
;
187 ls1x_cpufreq
.min_freq
= pdata
->min_freq
;
189 ret
= cpufreq_register_driver(&ls1x_cpufreq_driver
);
191 dev_err(ls1x_cpufreq
.dev
,
192 "failed to register cpufreq driver: %d\n", ret
);
196 ret
= cpufreq_register_notifier(&ls1x_cpufreq_notifier_block
,
197 CPUFREQ_TRANSITION_NOTIFIER
);
202 dev_err(ls1x_cpufreq
.dev
, "failed to register cpufreq notifier: %d\n",
205 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
210 static struct platform_driver ls1x_cpufreq_platdrv
= {
212 .name
= "ls1x-cpufreq",
214 .probe
= ls1x_cpufreq_probe
,
215 .remove
= ls1x_cpufreq_remove
,
218 module_platform_driver(ls1x_cpufreq_platdrv
);
220 MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>");
221 MODULE_DESCRIPTION("Loongson 1 CPUFreq driver");
222 MODULE_LICENSE("GPL");