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