xref: /OK3568_Linux_fs/kernel/drivers/cpufreq/s3c2440-cpufreq.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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