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 struct ls1x_cpufreq
*cpufreq
;
35 static int ls1x_cpufreq_notifier(struct notifier_block
*nb
,
36 unsigned long val
, void *data
)
38 if (val
== CPUFREQ_POSTCHANGE
)
39 current_cpu_data
.udelay_val
= loops_per_jiffy
;
44 static struct notifier_block ls1x_cpufreq_notifier_block
= {
45 .notifier_call
= ls1x_cpufreq_notifier
48 static int ls1x_cpufreq_target(struct cpufreq_policy
*policy
,
51 struct device
*cpu_dev
= get_cpu_device(policy
->cpu
);
52 unsigned int old_freq
, new_freq
;
54 old_freq
= policy
->cur
;
55 new_freq
= policy
->freq_table
[index
].frequency
;
58 * The procedure of reconfiguring CPU clk is as below.
60 * - Reparent CPU clk to OSC clk
61 * - Reset CPU clock (very important)
62 * - Reconfigure CPU DIV
63 * - Reparent CPU clk back to CPU DIV clk
66 clk_set_parent(policy
->clk
, cpufreq
->osc_clk
);
67 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV
) | RST_CPU_EN
| RST_CPU
,
69 __raw_writel(__raw_readl(LS1X_CLK_PLL_DIV
) & ~(RST_CPU_EN
| RST_CPU
),
71 clk_set_rate(cpufreq
->mux_clk
, new_freq
* 1000);
72 clk_set_parent(policy
->clk
, cpufreq
->mux_clk
);
73 dev_dbg(cpu_dev
, "%u KHz --> %u KHz\n", old_freq
, new_freq
);
78 static int ls1x_cpufreq_init(struct cpufreq_policy
*policy
)
80 struct device
*cpu_dev
= get_cpu_device(policy
->cpu
);
81 struct cpufreq_frequency_table
*freq_tbl
;
82 unsigned int pll_freq
, freq
;
85 pll_freq
= clk_get_rate(cpufreq
->pll_clk
) / 1000;
87 steps
= 1 << DIV_CPU_WIDTH
;
88 freq_tbl
= kcalloc(steps
, sizeof(*freq_tbl
), GFP_KERNEL
);
92 for (i
= 0; i
< (steps
- 1); i
++) {
93 freq
= pll_freq
/ (i
+ 1);
94 if ((freq
< cpufreq
->min_freq
) || (freq
> cpufreq
->max_freq
))
95 freq_tbl
[i
].frequency
= CPUFREQ_ENTRY_INVALID
;
97 freq_tbl
[i
].frequency
= freq
;
99 "cpufreq table: index %d: frequency %d\n", i
,
100 freq_tbl
[i
].frequency
);
102 freq_tbl
[i
].frequency
= CPUFREQ_TABLE_END
;
104 policy
->clk
= cpufreq
->clk
;
105 ret
= cpufreq_generic_init(policy
, freq_tbl
, 0);
112 static int ls1x_cpufreq_exit(struct cpufreq_policy
*policy
)
114 kfree(policy
->freq_table
);
118 static struct cpufreq_driver ls1x_cpufreq_driver
= {
119 .name
= "cpufreq-ls1x",
120 .flags
= CPUFREQ_STICKY
| CPUFREQ_NEED_INITIAL_FREQ_CHECK
,
121 .verify
= cpufreq_generic_frequency_table_verify
,
122 .target_index
= ls1x_cpufreq_target
,
123 .get
= cpufreq_generic_get
,
124 .init
= ls1x_cpufreq_init
,
125 .exit
= ls1x_cpufreq_exit
,
126 .attr
= cpufreq_generic_attr
,
129 static int ls1x_cpufreq_remove(struct platform_device
*pdev
)
131 cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block
,
132 CPUFREQ_TRANSITION_NOTIFIER
);
133 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
138 static int ls1x_cpufreq_probe(struct platform_device
*pdev
)
140 struct plat_ls1x_cpufreq
*pdata
= dev_get_platdata(&pdev
->dev
);
144 if (!pdata
|| !pdata
->clk_name
|| !pdata
->osc_clk_name
) {
145 dev_err(&pdev
->dev
, "platform data missing\n");
150 devm_kzalloc(&pdev
->dev
, sizeof(struct ls1x_cpufreq
), GFP_KERNEL
);
154 cpufreq
->dev
= &pdev
->dev
;
156 clk
= devm_clk_get(&pdev
->dev
, pdata
->clk_name
);
158 dev_err(&pdev
->dev
, "unable to get %s clock\n",
164 clk
= clk_get_parent(clk
);
166 dev_err(&pdev
->dev
, "unable to get parent of %s clock\n",
167 __clk_get_name(cpufreq
->clk
));
170 cpufreq
->mux_clk
= clk
;
172 clk
= clk_get_parent(clk
);
174 dev_err(&pdev
->dev
, "unable to get parent of %s clock\n",
175 __clk_get_name(cpufreq
->mux_clk
));
178 cpufreq
->pll_clk
= clk
;
180 clk
= devm_clk_get(&pdev
->dev
, pdata
->osc_clk_name
);
182 dev_err(&pdev
->dev
, "unable to get %s clock\n",
183 pdata
->osc_clk_name
);
186 cpufreq
->osc_clk
= clk
;
188 cpufreq
->max_freq
= pdata
->max_freq
;
189 cpufreq
->min_freq
= pdata
->min_freq
;
191 ret
= cpufreq_register_driver(&ls1x_cpufreq_driver
);
194 "failed to register CPUFreq driver: %d\n", ret
);
198 ret
= cpufreq_register_notifier(&ls1x_cpufreq_notifier_block
,
199 CPUFREQ_TRANSITION_NOTIFIER
);
203 "failed to register CPUFreq notifier: %d\n",ret
);
204 cpufreq_unregister_driver(&ls1x_cpufreq_driver
);
210 static struct platform_driver ls1x_cpufreq_platdrv
= {
211 .probe
= ls1x_cpufreq_probe
,
212 .remove
= ls1x_cpufreq_remove
,
214 .name
= "ls1x-cpufreq",
218 module_platform_driver(ls1x_cpufreq_platdrv
);
220 MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>");
221 MODULE_DESCRIPTION("Loongson1 CPUFreq driver");
222 MODULE_LICENSE("GPL");