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>
17 #include <linux/module.h>
18 #include <linux/platform_device.h>
19 #include <linux/slab.h>
22 #include <loongson1.h>
26 struct clk
*clk
; /* CPU clk */
27 struct clk
*mux_clk
; /* MUX of CPU clk */
28 struct clk
*pll_clk
; /* PLL clk */
29 struct clk
*osc_clk
; /* OSC clk */
30 unsigned int max_freq
;
31 unsigned int min_freq
;
34 static struct ls1x_cpufreq
*cpufreq
;
36 static int ls1x_cpufreq_notifier(struct notifier_block
*nb
,
37 unsigned long val
, void *data
)
39 if (val
== CPUFREQ_POSTCHANGE
)
40 current_cpu_data
.udelay_val
= loops_per_jiffy
;
45 static struct notifier_block ls1x_cpufreq_notifier_block
= {
46 .notifier_call
= ls1x_cpufreq_notifier
49 static int ls1x_cpufreq_target(struct cpufreq_policy
*policy
,
52 struct device
*cpu_dev
= get_cpu_device(policy
->cpu
);
53 unsigned int old_freq
, new_freq
;
55 old_freq
= policy
->cur
;
56 new_freq
= policy
->freq_table
[index
].frequency
;
59 * The procedure of reconfiguring CPU clk is as below.
61 * - Reparent CPU clk to OSC clk
62 * - Reset CPU clock (very important)
63 * - Reconfigure CPU DIV
64 * - Reparent CPU clk back to CPU DIV clk
67 clk_set_parent(policy
->clk
, cpufreq
->osc_clk
);
68 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV
) | RST_CPU_EN
| RST_CPU
,
70 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV
) & ~(RST_CPU_EN
| RST_CPU
),
72 clk_set_rate(cpufreq
->mux_clk
, new_freq
* 1000);
73 clk_set_parent(policy
->clk
, cpufreq
->mux_clk
);
74 dev_dbg(cpu_dev
, "%u KHz --> %u KHz\n", old_freq
, new_freq
);
79 static int ls1x_cpufreq_init(struct cpufreq_policy
*policy
)
81 struct device
*cpu_dev
= get_cpu_device(policy
->cpu
);
82 struct cpufreq_frequency_table
*freq_tbl
;
83 unsigned int pll_freq
, freq
;
86 pll_freq
= clk_get_rate(cpufreq
->pll_clk
) / 1000;
88 steps
= 1 << DIV_CPU_WIDTH
;
89 freq_tbl
= kcalloc(steps
, sizeof(*freq_tbl
), GFP_KERNEL
);
93 for (i
= 0; i
< (steps
- 1); i
++) {
94 freq
= pll_freq
/ (i
+ 1);
95 if ((freq
< cpufreq
->min_freq
) || (freq
> cpufreq
->max_freq
))
96 freq_tbl
[i
].frequency
= CPUFREQ_ENTRY_INVALID
;
98 freq_tbl
[i
].frequency
= freq
;
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
= cpufreq
->clk
;
106 cpufreq_generic_init(policy
, freq_tbl
, 0);
111 static int ls1x_cpufreq_exit(struct cpufreq_policy
*policy
)
113 kfree(policy
->freq_table
);
117 static struct cpufreq_driver ls1x_cpufreq_driver
= {
118 .name
= "cpufreq-ls1x",
119 .flags
= CPUFREQ_STICKY
| CPUFREQ_NEED_INITIAL_FREQ_CHECK
,
120 .verify
= cpufreq_generic_frequency_table_verify
,
121 .target_index
= ls1x_cpufreq_target
,
122 .get
= cpufreq_generic_get
,
123 .init
= ls1x_cpufreq_init
,
124 .exit
= ls1x_cpufreq_exit
,
125 .attr
= cpufreq_generic_attr
,
128 static int ls1x_cpufreq_remove(struct platform_device
*pdev
)
130 cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block
,
131 CPUFREQ_TRANSITION_NOTIFIER
);
132 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
137 static int ls1x_cpufreq_probe(struct platform_device
*pdev
)
139 struct plat_ls1x_cpufreq
*pdata
= dev_get_platdata(&pdev
->dev
);
143 if (!pdata
|| !pdata
->clk_name
|| !pdata
->osc_clk_name
) {
144 dev_err(&pdev
->dev
, "platform data missing\n");
149 devm_kzalloc(&pdev
->dev
, sizeof(struct ls1x_cpufreq
), GFP_KERNEL
);
153 cpufreq
->dev
= &pdev
->dev
;
155 clk
= devm_clk_get(&pdev
->dev
, pdata
->clk_name
);
157 dev_err(&pdev
->dev
, "unable to get %s clock\n",
163 clk
= clk_get_parent(clk
);
165 dev_err(&pdev
->dev
, "unable to get parent of %s clock\n",
166 __clk_get_name(cpufreq
->clk
));
169 cpufreq
->mux_clk
= clk
;
171 clk
= clk_get_parent(clk
);
173 dev_err(&pdev
->dev
, "unable to get parent of %s clock\n",
174 __clk_get_name(cpufreq
->mux_clk
));
177 cpufreq
->pll_clk
= clk
;
179 clk
= devm_clk_get(&pdev
->dev
, pdata
->osc_clk_name
);
181 dev_err(&pdev
->dev
, "unable to get %s clock\n",
182 pdata
->osc_clk_name
);
185 cpufreq
->osc_clk
= clk
;
187 cpufreq
->max_freq
= pdata
->max_freq
;
188 cpufreq
->min_freq
= pdata
->min_freq
;
190 ret
= cpufreq_register_driver(&ls1x_cpufreq_driver
);
193 "failed to register CPUFreq driver: %d\n", ret
);
197 ret
= cpufreq_register_notifier(&ls1x_cpufreq_notifier_block
,
198 CPUFREQ_TRANSITION_NOTIFIER
);
202 "failed to register CPUFreq notifier: %d\n",ret
);
203 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
209 static struct platform_driver ls1x_cpufreq_platdrv
= {
210 .probe
= ls1x_cpufreq_probe
,
211 .remove
= ls1x_cpufreq_remove
,
213 .name
= "ls1x-cpufreq",
217 module_platform_driver(ls1x_cpufreq_platdrv
);
219 MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>");
220 MODULE_DESCRIPTION("Loongson1 CPUFreq driver");
221 MODULE_LICENSE("GPL");