2 * CPU Frequency Scaling for Loongson 1 SoC
4 * Copyright (C) 2014-2016 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>
21 #include <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
= kcalloc(steps
, sizeof(*freq_tbl
), GFP_KERNEL
);
88 for (i
= 0; i
< (steps
- 1); i
++) {
89 freq
= pll_freq
/ (i
+ 1);
90 if ((freq
< ls1x_cpufreq
.min_freq
) ||
91 (freq
> ls1x_cpufreq
.max_freq
))
92 freq_tbl
[i
].frequency
= CPUFREQ_ENTRY_INVALID
;
94 freq_tbl
[i
].frequency
= freq
;
95 dev_dbg(ls1x_cpufreq
.dev
,
96 "cpufreq table: index %d: frequency %d\n", i
,
97 freq_tbl
[i
].frequency
);
99 freq_tbl
[i
].frequency
= CPUFREQ_TABLE_END
;
101 policy
->clk
= ls1x_cpufreq
.clk
;
102 ret
= cpufreq_generic_init(policy
, freq_tbl
, 0);
109 static int ls1x_cpufreq_exit(struct cpufreq_policy
*policy
)
111 kfree(policy
->freq_table
);
115 static struct cpufreq_driver ls1x_cpufreq_driver
= {
116 .name
= "cpufreq-ls1x",
117 .flags
= CPUFREQ_STICKY
| CPUFREQ_NEED_INITIAL_FREQ_CHECK
,
118 .verify
= cpufreq_generic_frequency_table_verify
,
119 .target_index
= ls1x_cpufreq_target
,
120 .get
= cpufreq_generic_get
,
121 .init
= ls1x_cpufreq_init
,
122 .exit
= ls1x_cpufreq_exit
,
123 .attr
= cpufreq_generic_attr
,
126 static int ls1x_cpufreq_remove(struct platform_device
*pdev
)
128 cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block
,
129 CPUFREQ_TRANSITION_NOTIFIER
);
130 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
135 static int ls1x_cpufreq_probe(struct platform_device
*pdev
)
137 struct plat_ls1x_cpufreq
*pdata
= dev_get_platdata(&pdev
->dev
);
141 if (!pdata
|| !pdata
->clk_name
|| !pdata
->osc_clk_name
)
144 ls1x_cpufreq
.dev
= &pdev
->dev
;
146 clk
= devm_clk_get(&pdev
->dev
, pdata
->clk_name
);
148 dev_err(ls1x_cpufreq
.dev
, "unable to get %s clock\n",
153 ls1x_cpufreq
.clk
= clk
;
155 clk
= clk_get_parent(clk
);
157 dev_err(ls1x_cpufreq
.dev
, "unable to get parent of %s clock\n",
158 __clk_get_name(ls1x_cpufreq
.clk
));
162 ls1x_cpufreq
.mux_clk
= clk
;
164 clk
= clk_get_parent(clk
);
166 dev_err(ls1x_cpufreq
.dev
, "unable to get parent of %s clock\n",
167 __clk_get_name(ls1x_cpufreq
.mux_clk
));
171 ls1x_cpufreq
.pll_clk
= clk
;
173 clk
= devm_clk_get(&pdev
->dev
, pdata
->osc_clk_name
);
175 dev_err(ls1x_cpufreq
.dev
, "unable to get %s clock\n",
176 pdata
->osc_clk_name
);
180 ls1x_cpufreq
.osc_clk
= clk
;
182 ls1x_cpufreq
.max_freq
= pdata
->max_freq
;
183 ls1x_cpufreq
.min_freq
= pdata
->min_freq
;
185 ret
= cpufreq_register_driver(&ls1x_cpufreq_driver
);
187 dev_err(ls1x_cpufreq
.dev
,
188 "failed to register cpufreq driver: %d\n", ret
);
192 ret
= cpufreq_register_notifier(&ls1x_cpufreq_notifier_block
,
193 CPUFREQ_TRANSITION_NOTIFIER
);
198 dev_err(ls1x_cpufreq
.dev
, "failed to register cpufreq notifier: %d\n",
201 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
206 static struct platform_driver ls1x_cpufreq_platdrv
= {
207 .probe
= ls1x_cpufreq_probe
,
208 .remove
= ls1x_cpufreq_remove
,
210 .name
= "ls1x-cpufreq",
214 module_platform_driver(ls1x_cpufreq_platdrv
);
216 MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>");
217 MODULE_DESCRIPTION("Loongson1 CPUFreq driver");
218 MODULE_LICENSE("GPL");