1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (c) 2006-2009 Simtec Electronics
4*4882a593Smuzhiyun * http://armlinux.simtec.co.uk/
5*4882a593Smuzhiyun * Ben Dooks <ben@simtec.co.uk>
6*4882a593Smuzhiyun * Vincent Sanders <vince@simtec.co.uk>
7*4882a593Smuzhiyun *
8*4882a593Smuzhiyun * S3C2440/S3C2442 CPU Frequency scaling
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12*4882a593Smuzhiyun
13*4882a593Smuzhiyun #include <linux/init.h>
14*4882a593Smuzhiyun #include <linux/module.h>
15*4882a593Smuzhiyun #include <linux/interrupt.h>
16*4882a593Smuzhiyun #include <linux/ioport.h>
17*4882a593Smuzhiyun #include <linux/cpufreq.h>
18*4882a593Smuzhiyun #include <linux/device.h>
19*4882a593Smuzhiyun #include <linux/delay.h>
20*4882a593Smuzhiyun #include <linux/clk.h>
21*4882a593Smuzhiyun #include <linux/err.h>
22*4882a593Smuzhiyun #include <linux/io.h>
23*4882a593Smuzhiyun #include <linux/soc/samsung/s3c-cpufreq-core.h>
24*4882a593Smuzhiyun #include <linux/soc/samsung/s3c-pm.h>
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun #include <asm/mach/arch.h>
27*4882a593Smuzhiyun #include <asm/mach/map.h>
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun #define S3C2440_CLKDIVN_PDIVN (1<<0)
30*4882a593Smuzhiyun #define S3C2440_CLKDIVN_HDIVN_MASK (3<<1)
31*4882a593Smuzhiyun #define S3C2440_CLKDIVN_HDIVN_1 (0<<1)
32*4882a593Smuzhiyun #define S3C2440_CLKDIVN_HDIVN_2 (1<<1)
33*4882a593Smuzhiyun #define S3C2440_CLKDIVN_HDIVN_4_8 (2<<1)
34*4882a593Smuzhiyun #define S3C2440_CLKDIVN_HDIVN_3_6 (3<<1)
35*4882a593Smuzhiyun #define S3C2440_CLKDIVN_UCLK (1<<3)
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun #define S3C2440_CAMDIVN_CAMCLK_MASK (0xf<<0)
38*4882a593Smuzhiyun #define S3C2440_CAMDIVN_CAMCLK_SEL (1<<4)
39*4882a593Smuzhiyun #define S3C2440_CAMDIVN_HCLK3_HALF (1<<8)
40*4882a593Smuzhiyun #define S3C2440_CAMDIVN_HCLK4_HALF (1<<9)
41*4882a593Smuzhiyun #define S3C2440_CAMDIVN_DVSEN (1<<12)
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun #define S3C2442_CAMDIVN_CAMCLK_DIV3 (1<<5)
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun static struct clk *xtal;
46*4882a593Smuzhiyun static struct clk *fclk;
47*4882a593Smuzhiyun static struct clk *hclk;
48*4882a593Smuzhiyun static struct clk *armclk;
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun /* HDIV: 1, 2, 3, 4, 6, 8 */
51*4882a593Smuzhiyun
within_khz(unsigned long a,unsigned long b)52*4882a593Smuzhiyun static inline int within_khz(unsigned long a, unsigned long b)
53*4882a593Smuzhiyun {
54*4882a593Smuzhiyun long diff = a - b;
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun return (diff >= -1000 && diff <= 1000);
57*4882a593Smuzhiyun }
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun /**
60*4882a593Smuzhiyun * s3c2440_cpufreq_calcdivs - calculate divider settings
61*4882a593Smuzhiyun * @cfg: The cpu frequency settings.
62*4882a593Smuzhiyun *
63*4882a593Smuzhiyun * Calcualte the divider values for the given frequency settings
64*4882a593Smuzhiyun * specified in @cfg. The values are stored in @cfg for later use
65*4882a593Smuzhiyun * by the relevant set routine if the request settings can be reached.
66*4882a593Smuzhiyun */
s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config * cfg)67*4882a593Smuzhiyun static int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
68*4882a593Smuzhiyun {
69*4882a593Smuzhiyun unsigned int hdiv, pdiv;
70*4882a593Smuzhiyun unsigned long hclk, fclk, armclk;
71*4882a593Smuzhiyun unsigned long hclk_max;
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun fclk = cfg->freq.fclk;
74*4882a593Smuzhiyun armclk = cfg->freq.armclk;
75*4882a593Smuzhiyun hclk_max = cfg->max.hclk;
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n",
78*4882a593Smuzhiyun __func__, fclk, armclk, hclk_max);
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun if (armclk > fclk) {
81*4882a593Smuzhiyun pr_warn("%s: armclk > fclk\n", __func__);
82*4882a593Smuzhiyun armclk = fclk;
83*4882a593Smuzhiyun }
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun /* if we are in DVS, we need HCLK to be <= ARMCLK */
86*4882a593Smuzhiyun if (armclk < fclk && armclk < hclk_max)
87*4882a593Smuzhiyun hclk_max = armclk;
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun for (hdiv = 1; hdiv < 9; hdiv++) {
90*4882a593Smuzhiyun if (hdiv == 5 || hdiv == 7)
91*4882a593Smuzhiyun hdiv++;
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun hclk = (fclk / hdiv);
94*4882a593Smuzhiyun if (hclk <= hclk_max || within_khz(hclk, hclk_max))
95*4882a593Smuzhiyun break;
96*4882a593Smuzhiyun }
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv);
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun if (hdiv > 8)
101*4882a593Smuzhiyun goto invalid;
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun if ((hclk / pdiv) > cfg->max.pclk)
106*4882a593Smuzhiyun pdiv++;
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun if (pdiv > 2)
111*4882a593Smuzhiyun goto invalid;
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun pdiv *= hdiv;
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun /* calculate a valid armclk */
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun if (armclk < hclk)
118*4882a593Smuzhiyun armclk = hclk;
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun /* if we're running armclk lower than fclk, this really means
121*4882a593Smuzhiyun * that the system should go into dvs mode, which means that
122*4882a593Smuzhiyun * armclk is connected to hclk. */
123*4882a593Smuzhiyun if (armclk < fclk) {
124*4882a593Smuzhiyun cfg->divs.dvs = 1;
125*4882a593Smuzhiyun armclk = hclk;
126*4882a593Smuzhiyun } else
127*4882a593Smuzhiyun cfg->divs.dvs = 0;
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun cfg->freq.armclk = armclk;
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun /* store the result, and then return */
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun cfg->divs.h_divisor = hdiv;
134*4882a593Smuzhiyun cfg->divs.p_divisor = pdiv;
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun return 0;
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun invalid:
139*4882a593Smuzhiyun return -EINVAL;
140*4882a593Smuzhiyun }
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun #define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \
143*4882a593Smuzhiyun S3C2440_CAMDIVN_HCLK4_HALF)
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun /**
146*4882a593Smuzhiyun * s3c2440_cpufreq_setdivs - set the cpu frequency divider settings
147*4882a593Smuzhiyun * @cfg: The cpu frequency settings.
148*4882a593Smuzhiyun *
149*4882a593Smuzhiyun * Set the divisors from the settings in @cfg, which where generated
150*4882a593Smuzhiyun * during the calculation phase by s3c2440_cpufreq_calcdivs().
151*4882a593Smuzhiyun */
s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config * cfg)152*4882a593Smuzhiyun static void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
153*4882a593Smuzhiyun {
154*4882a593Smuzhiyun unsigned long clkdiv, camdiv;
155*4882a593Smuzhiyun
156*4882a593Smuzhiyun s3c_freq_dbg("%s: divisors: h=%d, p=%d\n", __func__,
157*4882a593Smuzhiyun cfg->divs.h_divisor, cfg->divs.p_divisor);
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun clkdiv = s3c24xx_read_clkdivn();
160*4882a593Smuzhiyun camdiv = s3c2440_read_camdivn();
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN);
163*4882a593Smuzhiyun camdiv &= ~CAMDIVN_HCLK_HALF;
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun switch (cfg->divs.h_divisor) {
166*4882a593Smuzhiyun case 1:
167*4882a593Smuzhiyun clkdiv |= S3C2440_CLKDIVN_HDIVN_1;
168*4882a593Smuzhiyun break;
169*4882a593Smuzhiyun
170*4882a593Smuzhiyun case 2:
171*4882a593Smuzhiyun clkdiv |= S3C2440_CLKDIVN_HDIVN_2;
172*4882a593Smuzhiyun break;
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun case 6:
175*4882a593Smuzhiyun camdiv |= S3C2440_CAMDIVN_HCLK3_HALF;
176*4882a593Smuzhiyun case 3:
177*4882a593Smuzhiyun clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
178*4882a593Smuzhiyun break;
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun case 8:
181*4882a593Smuzhiyun camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
182*4882a593Smuzhiyun case 4:
183*4882a593Smuzhiyun clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8;
184*4882a593Smuzhiyun break;
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun default:
187*4882a593Smuzhiyun BUG(); /* we don't expect to get here. */
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun if (cfg->divs.p_divisor != cfg->divs.h_divisor)
191*4882a593Smuzhiyun clkdiv |= S3C2440_CLKDIVN_PDIVN;
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun /* todo - set pclk. */
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun /* Write the divisors first with hclk intentionally halved so that
196*4882a593Smuzhiyun * when we write clkdiv we will under-frequency instead of over. We
197*4882a593Smuzhiyun * then make a short delay and remove the hclk halving if necessary.
198*4882a593Smuzhiyun */
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun s3c2440_write_camdivn(camdiv | CAMDIVN_HCLK_HALF);
201*4882a593Smuzhiyun s3c24xx_write_clkdivn(clkdiv);
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun ndelay(20);
204*4882a593Smuzhiyun s3c2440_write_camdivn(camdiv);
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
207*4882a593Smuzhiyun }
208*4882a593Smuzhiyun
run_freq_for(unsigned long max_hclk,unsigned long fclk,int * divs,struct cpufreq_frequency_table * table,size_t table_size)209*4882a593Smuzhiyun static int run_freq_for(unsigned long max_hclk, unsigned long fclk,
210*4882a593Smuzhiyun int *divs,
211*4882a593Smuzhiyun struct cpufreq_frequency_table *table,
212*4882a593Smuzhiyun size_t table_size)
213*4882a593Smuzhiyun {
214*4882a593Smuzhiyun unsigned long freq;
215*4882a593Smuzhiyun int index = 0;
216*4882a593Smuzhiyun int div;
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun for (div = *divs; div > 0; div = *divs++) {
219*4882a593Smuzhiyun freq = fclk / div;
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun if (freq > max_hclk && div != 1)
222*4882a593Smuzhiyun continue;
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun freq /= 1000; /* table is in kHz */
225*4882a593Smuzhiyun index = s3c_cpufreq_addfreq(table, index, table_size, freq);
226*4882a593Smuzhiyun if (index < 0)
227*4882a593Smuzhiyun break;
228*4882a593Smuzhiyun }
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun return index;
231*4882a593Smuzhiyun }
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun static int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 };
234*4882a593Smuzhiyun
s3c2440_cpufreq_calctable(struct s3c_cpufreq_config * cfg,struct cpufreq_frequency_table * table,size_t table_size)235*4882a593Smuzhiyun static int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg,
236*4882a593Smuzhiyun struct cpufreq_frequency_table *table,
237*4882a593Smuzhiyun size_t table_size)
238*4882a593Smuzhiyun {
239*4882a593Smuzhiyun int ret;
240*4882a593Smuzhiyun
241*4882a593Smuzhiyun WARN_ON(cfg->info == NULL);
242*4882a593Smuzhiyun WARN_ON(cfg->board == NULL);
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun ret = run_freq_for(cfg->info->max.hclk,
245*4882a593Smuzhiyun cfg->info->max.fclk,
246*4882a593Smuzhiyun hclk_divs,
247*4882a593Smuzhiyun table, table_size);
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun s3c_freq_dbg("%s: returning %d\n", __func__, ret);
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun return ret;
252*4882a593Smuzhiyun }
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun static struct s3c_cpufreq_info s3c2440_cpufreq_info = {
255*4882a593Smuzhiyun .max = {
256*4882a593Smuzhiyun .fclk = 400000000,
257*4882a593Smuzhiyun .hclk = 133333333,
258*4882a593Smuzhiyun .pclk = 66666666,
259*4882a593Smuzhiyun },
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun .locktime_m = 300,
262*4882a593Smuzhiyun .locktime_u = 300,
263*4882a593Smuzhiyun .locktime_bits = 16,
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun .name = "s3c244x",
266*4882a593Smuzhiyun .calc_iotiming = s3c2410_iotiming_calc,
267*4882a593Smuzhiyun .set_iotiming = s3c2410_iotiming_set,
268*4882a593Smuzhiyun .get_iotiming = s3c2410_iotiming_get,
269*4882a593Smuzhiyun .set_fvco = s3c2410_set_fvco,
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun .set_refresh = s3c2410_cpufreq_setrefresh,
272*4882a593Smuzhiyun .set_divs = s3c2440_cpufreq_setdivs,
273*4882a593Smuzhiyun .calc_divs = s3c2440_cpufreq_calcdivs,
274*4882a593Smuzhiyun .calc_freqtable = s3c2440_cpufreq_calctable,
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun .debug_io_show = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
277*4882a593Smuzhiyun };
278*4882a593Smuzhiyun
s3c2440_cpufreq_add(struct device * dev,struct subsys_interface * sif)279*4882a593Smuzhiyun static int s3c2440_cpufreq_add(struct device *dev,
280*4882a593Smuzhiyun struct subsys_interface *sif)
281*4882a593Smuzhiyun {
282*4882a593Smuzhiyun xtal = s3c_cpufreq_clk_get(NULL, "xtal");
283*4882a593Smuzhiyun hclk = s3c_cpufreq_clk_get(NULL, "hclk");
284*4882a593Smuzhiyun fclk = s3c_cpufreq_clk_get(NULL, "fclk");
285*4882a593Smuzhiyun armclk = s3c_cpufreq_clk_get(NULL, "armclk");
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) {
288*4882a593Smuzhiyun pr_err("%s: failed to get clocks\n", __func__);
289*4882a593Smuzhiyun return -ENOENT;
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun return s3c_cpufreq_register(&s3c2440_cpufreq_info);
293*4882a593Smuzhiyun }
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun static struct subsys_interface s3c2440_cpufreq_interface = {
296*4882a593Smuzhiyun .name = "s3c2440_cpufreq",
297*4882a593Smuzhiyun .subsys = &s3c2440_subsys,
298*4882a593Smuzhiyun .add_dev = s3c2440_cpufreq_add,
299*4882a593Smuzhiyun };
300*4882a593Smuzhiyun
s3c2440_cpufreq_init(void)301*4882a593Smuzhiyun static int s3c2440_cpufreq_init(void)
302*4882a593Smuzhiyun {
303*4882a593Smuzhiyun return subsys_interface_register(&s3c2440_cpufreq_interface);
304*4882a593Smuzhiyun }
305*4882a593Smuzhiyun
306*4882a593Smuzhiyun /* arch_initcall adds the clocks we need, so use subsys_initcall. */
307*4882a593Smuzhiyun subsys_initcall(s3c2440_cpufreq_init);
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun static struct subsys_interface s3c2442_cpufreq_interface = {
310*4882a593Smuzhiyun .name = "s3c2442_cpufreq",
311*4882a593Smuzhiyun .subsys = &s3c2442_subsys,
312*4882a593Smuzhiyun .add_dev = s3c2440_cpufreq_add,
313*4882a593Smuzhiyun };
314*4882a593Smuzhiyun
s3c2442_cpufreq_init(void)315*4882a593Smuzhiyun static int s3c2442_cpufreq_init(void)
316*4882a593Smuzhiyun {
317*4882a593Smuzhiyun return subsys_interface_register(&s3c2442_cpufreq_interface);
318*4882a593Smuzhiyun }
319*4882a593Smuzhiyun subsys_initcall(s3c2442_cpufreq_init);
320