summaryrefslogtreecommitdiff
path: root/arch/arm/mach-mx27/mxc_pm.c
blob: 9fd0830b9d751e151f0f8c6e1b75d27d4af72291 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/*
 * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @defgroup DPM_MX27 Power Management
 * @ingroup MSL_MX27
 */
/*!
 * @file mach-mx27/mxc_pm.c
 *
 * @brief This file contains the implementation of the Low-level power
 * management driver. It modifies the registers of the PLL and clock module
 * of the i.MX27.
 *
 * @ingroup DPM_MX27
 */

/*
 * Include Files
 */
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <asm/arch/mxc_pm.h>
#include <asm/arch/mxc.h>
#include <asm/arch/system.h>
#include <asm/irq.h>
#include "crm_regs.h"

/* Local defines */
#define MAX_ARM_FREQ        400000000
#define MAX_AHB_FREQ        133000000
#define MAX_IPG_FREQ        66500000
#define FREQ_COMP_TOLERANCE      100	/* tolerance percentage times 100 */
#define MX27_LLPM_DEBUG	    0

/*
 * Global variables
 */
#if 0
/*!
 * These variables hold the various clock values when the module is loaded.
 * This is needed because these clocks are derived from MPLL and when MPLL
 * output changes, these clocks need to be adjusted.
 */
static u32 perclk1, perclk2, perclk3, perclk4, nfcclk, cpuclk;

/*!
 * Compare two frequences using allowable tolerance
 *
 * The MX27 PLL can generate many frequencies. This function
 * compares the generated frequency to the requested frequency
 * and determines it they are within and acceptable tolerance.
 *
 * @param   freq1  desired frequency
 * @param   freq2  generated frequency
 *
 * @return       Returns 0 is frequencies are within talerance
 *               and non-zero is they are not.
 */
static s32 freq_equal(u32 freq1, u32 freq2)
{
	if (freq1 > freq2) {
		return (freq1 - freq2) <= (freq1 / FREQ_COMP_TOLERANCE);
	}
	return (freq2 - freq1) <= (freq1 / FREQ_COMP_TOLERANCE);
}

/*!
 * Select the PLL frequency based on the desired ARM frequency.
 *
 * The MPLL will be configured to output three frequencies, 400/333/266 MHz.
 *
 * @param       armfreq         Desired ARM frequency
 *
 * @return      Returns one of the selected PLL frequency (400/333/266 MHz).
 *              Returns -1 on error.
 *
 */
static s32 select_freq_pll(u32 armfreq)
{
	u32 div;

	div = 266000000 / armfreq;
	if ((div == 0) || (!freq_equal(armfreq, 266000000 / div))) {
		div = 400000000 / armfreq;
		if ((div == 0) || (!freq_equal(armfreq, 400000000 / div))) {
			return -1;
		}

		return 400000000;
	}

	return 266000000;
}

/*!
 * Check whether the desired ARM and AHB frequencies are valid.
 *
 * @param       armfreq         Desired ARM frequency
 * @param       ahbfreq         Desired AHB frequency
 *
 * @return      Returns 0 on success
 *              Return -1 on error
 */
static s32 mx27_pm_check_parameters(u32 armfreq, u32 ahbfreq)
{
	u32 ahbdiv;

	/* No idea about minimum frequencies.. just a guess! */
	if ((armfreq < 1000000) || (ahbfreq < 1000000)) {
		printk("arm or ahb frequencies are less\n");
		return -1;
	}

	if ((armfreq > MAX_ARM_FREQ) || (ahbfreq > MAX_AHB_FREQ)) {
		printk("arm or ahb freq. are too much\n");
		return -1;
	}

	/* AHB divider value is restricted to less than 8 */
	ahbdiv = armfreq / ahbfreq;
	if ((ahbdiv == 0) || (ahbdiv > 8)) {
		printk("Invalid ahb frequency\n");
		return -1;
	}

	return 0;
}

/*!
 * Integer clock scaling
 *
 * Change the main ARM clock frequencies without changing the MPLL.
 * The integer dividers (PRESC and BCLKDIV) are changed to obtain the
 * desired frequency. Since NFC clock is derived from ARM frequency,
 * NFCDIV is also adjusted.
 *
 * @param       arm_freq        Desired ARM frequency
 * @param       ahb_freq        Desired AHB frequency
 * @param       pll_freq        Current PLL frequency
 *
 * @return      Returns 0
 */
static s32 mx27_pm_intscale(u32 arm_freq, u32 ahb_freq, s32 pll_freq)
{
	u32 pre_div, bclk_div, nfc_div;

	/* Calculate ARM divider */
	pre_div = pll_freq / arm_freq;
	if (pre_div == 0)
		pre_div = 1;

	/* Calculate AHB divider */
	bclk_div = arm_freq / ahb_freq;
	if (bclk_div == 0)
		bclk_div = 1;

	if ((arm_freq / bclk_div) > ahb_freq)
		bclk_div++;

	/* NFC clock is dependent on ARM clock */
	nfc_div = arm_freq / nfcclk;
	if ((arm_freq / nfc_div) > nfcclk)
		nfc_div++;

	/* Adjust NFC divider */
	mxc_set_clocks_div(NFC_CLK, nfc_div);

#if MX27_LLPM_DEBUG
	printk("DIVIDERS: PreDiv = %d BCLKDIV = %d \n", pre_div, bclk_div);
	printk("Integer scaling\n");
	printk("PLL = %d : ARM = %d: AHB = %d\n", pll_freq, arm_freq, ahb_freq);
#endif

	/*
	 * This part is tricky. What to adjust first (PRESC or BCLKDIV)?
	 * After trial and error, if current ARM frequency is greater than
	 * desired ARM frequency, then adjust PRESC first, else if current
	 * ARM frequency is less than desired ARM frequency, then adjust
	 * BCLKDIV first.
	 */
	if (cpuclk > arm_freq) {
		mxc_set_clocks_div(CPU_CLK, pre_div);
		mxc_set_clocks_div(AHB_CLK, bclk_div);
	} else {
		mxc_set_clocks_div(AHB_CLK, bclk_div);
		mxc_set_clocks_div(CPU_CLK, pre_div);
	}

	cpuclk = arm_freq;
	mdelay(50);
	return 0;
}

/*!
 * Set dividers for various peripheral clocks.
 *
 * PERCLK1, PERCLK2, PERCLK3 and PERCLK4 are adjusted based on the MPLL
 * output frequency.
 *
 * @param       pll_freq        Desired MPLL output frequency
 */
static void mx27_set_dividers(u32 pll_freq)
{
	s32 perdiv1, perdiv2, perdiv3, perdiv4;

	perdiv1 = pll_freq / perclk1;
	if ((pll_freq / perdiv1) > perclk1)
		perdiv1++;

	perdiv2 = pll_freq / perclk2;
	if ((pll_freq / perdiv2) > perclk2)
		perdiv2++;

	perdiv3 = pll_freq / perclk3;
	if ((pll_freq / perdiv3) > perclk3)
		perdiv3++;

	perdiv4 = pll_freq / perclk4;
	if ((pll_freq / perdiv4) > perclk4)
		perdiv4++;

	mxc_set_clocks_div(PERCLK1, perdiv1);
	mxc_set_clocks_div(PERCLK2, perdiv2);
	mxc_set_clocks_div(PERCLK3, perdiv3);
	mxc_set_clocks_div(PERCLK4, perdiv4);
}

/*!
 * Change MPLL output frequency and adjust derived clocks to produce the
 * desired frequencies.
 *
 * @param       arm_freq        Desired ARM frequency
 * @param       ahb_freq        Desired AHB frequency
 * @param       org_pll         Current PLL frequency
 *
 * @return      Returns 0 on success
 *              Returns -1 on error
 */
static s32 mx27_pm_pllscale(u32 arm_freq, u32 ahb_freq, s32 org_pll)
{
	u32 mfi, mfn, mfd, pd = 1, cscr;
	s32 pll_freq;

	/* Obtain the PLL frequency for the desired ARM frequency */
	pll_freq = select_freq_pll(arm_freq);
	if (pll_freq == -1) {
		return -1;
	}

	/* The MPCTL0 register values are programmed based on the oscillator */
	cscr = __raw_readl(IO_ADDRESS(CCM_BASE_ADDR) + CCM_CSCR);
	if ((cscr & CCM_CSCR_OSC26M) == 0) {
		/* MPCTL0 register values are programmed for 400/266 MHz */
		switch (pll_freq) {
		case 400000000:
			mfi = 7;
			mfn = 9;
			mfd = 12;
			pd = 0;
			break;

		case 266000000:
			mfi = 10;
			mfn = 6;
			mfd = 25;
			break;

		default:
			return -1;
		}
	} else {
		/* MPCTL0 register values are programmed for 400/266 MHz */
		switch (pll_freq) {
		case 400000000:
			mfi = 12;
			mfn = 2;
			mfd = 3;
			break;

		case 266000000:
			mfi = 8;
			mfn = 10;
			mfd = 31;
			break;

		default:
			return -1;
		}
	}

#if MX27_LLPM_DEBUG
	printk("PLL scaling\n");
	printk("PLL = %d : ARM = %d: AHB = %d\n", pll_freq, arm_freq, ahb_freq);
#endif

	/* Adjust the peripheral clock dividers for new PLL frequency */
	mx27_set_dividers(pll_freq);

	if (pll_freq > org_pll) {
		/* Set the dividers first */
		mx27_pm_intscale(arm_freq, ahb_freq, pll_freq);

		/* Set the PLL */
		mxc_pll_set(MCUPLL, mfi, pd, mfd, mfn);
		mdelay(50);
	} else {
		/* Set the PLL first */
		mxc_pll_set(MCUPLL, mfi, pd, mfd, mfn);
		mdelay(50);

		/* Set the dividers later */
		mx27_pm_intscale(arm_freq, ahb_freq, pll_freq);
	}

	return 0;
}
#endif
/*!
 * Implement steps required to transition to low-power modes.
 *
 * @param       mode    The desired low-power mode. Possible values are,
 *                      DOZE_MODE
 *                      WAIT_MODE
 *                      STOP_MODE
 *                      DSM_MODE
 */
void mxc_pm_lowpower(s32 mode)
{
	u32 cscr;

	local_irq_disable();

	/* WAIT and DOZE execute WFI only */
	switch (mode) {
	case STOP_MODE:
	case DSM_MODE:
		/* Clear MPEN and SPEN to disable MPLL/SPLL */
		cscr = __raw_readl(CCM_CSCR);
		cscr &= 0xFFFFFFFC;
		__raw_writel(cscr, CCM_CSCR);
		break;
	}

	/* Executes WFI */
	arch_idle();

	local_irq_enable();
}

#if 0
/*!
 * Called to change the core frequency. This function internally decides
 * whether to do integer scaling or pll scaling.
 *
 * @param       arm_freq        Desired ARM frequency
 * @param       ahb_freq        Desired AHB frequency
 * @param       ipg_freq        Desired IP frequency, constant AHB / 2 always.
 *
 * @return      Returns 0 on success
 *              Returns -1 on error
 */
int mxc_pm_dvfs(unsigned long arm_freq, long ahb_freq, long ipg_freq)
{
	u32 divider;
	s32 pll_freq, ret;
	unsigned long flags;

	if (mx27_pm_check_parameters(arm_freq, ahb_freq) != 0) {
		return -1;
	}

	local_irq_save(flags);

	/* Get the current PLL frequency */
	pll_freq = mxc_pll_clock(MCUPLL);

#if MX27_LLPM_DEBUG
	printk("MCU PLL frequency is %d\n", pll_freq);
#endif

	/* Decide whether to do integer scaling or pll scaling */
	if (arm_freq > pll_freq) {
		/* Do PLL scaling */
		ret = mx27_pm_pllscale(arm_freq, ahb_freq, pll_freq);
	} else {
		/* We need integer divider values */
		divider = pll_freq / arm_freq;
		if (!freq_equal(arm_freq, pll_freq / divider)) {
			/* Do PLL scaling */
			ret = mx27_pm_pllscale(arm_freq, ahb_freq, pll_freq);
		} else {
			/* Do integer scaling */
			ret = mx27_pm_intscale(arm_freq, ahb_freq, pll_freq);
		}
	}

	local_irq_restore(flags);
	return ret;
}
#endif
/*
 * This API is not supported on i.MX27
 */
int mxc_pm_intscale(long armfreq, long ahbfreq, long ipfreq)
{
	return -MXC_PM_API_NOT_SUPPORTED;
}

/*
 * This API is not supported on i.MX27
 */
int mxc_pm_pllscale(long armfreq, long ahbfreq, long ipfreq)
{
	return -MXC_PM_API_NOT_SUPPORTED;
}

/*!
 * This function is used to load the module.
 *
 * @return   Returns an Integer on success
 */
static int __init mxc_pm_init_module(void)
{
	printk(KERN_INFO "MX27: Power management module initialized\n");
	return 0;
}

/*!
 * This function is used to unload the module
 */
static void __exit mxc_pm_cleanup_module(void)
{
	printk(KERN_INFO "MX27: Power management module exit\n");
}

module_init(mxc_pm_init_module);
module_exit(mxc_pm_cleanup_module);

EXPORT_SYMBOL(mxc_pm_lowpower);
//EXPORT_SYMBOL(mxc_pm_dvfs);
EXPORT_SYMBOL(mxc_pm_pllscale);
EXPORT_SYMBOL(mxc_pm_intscale);

MODULE_AUTHOR("Freescale Semiconductor");
MODULE_DESCRIPTION("i.MX27 low level PM driver");
MODULE_LICENSE("GPL");