summaryrefslogtreecommitdiff
path: root/arch/arm/cpu/arm926ejs/mxs/clock.c
blob: 0439f9c0eae1a4209b2dd914bb357941f057b758 (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
/*
 * Freescale i.MX28 clock setup code
 *
 * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
 * on behalf of DENX Software Engineering GmbH
 *
 * Based on code from LTIB:
 * Copyright (C) 2010 Freescale Semiconductor, Inc.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/imx-regs.h>

/* The PLL frequency is always 480MHz, see section 10.2 in iMX28 datasheet. */
#define	PLL_FREQ_KHZ	480000
#define	PLL_FREQ_COEF	18
/* The XTAL frequency is always 24MHz, see section 10.2 in iMX28 datasheet. */
#define	XTAL_FREQ_KHZ	24000

#define	PLL_FREQ_MHZ	(PLL_FREQ_KHZ / 1000)
#define	XTAL_FREQ_MHZ	(XTAL_FREQ_KHZ / 1000)

static uint32_t mx28_get_pclk(void)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;

	uint32_t clkctrl, clkseq, div;
	uint8_t clkfrac, frac;

	clkctrl = readl(&clkctrl_regs->hw_clkctrl_cpu);

	/* No support of fractional divider calculation */
	if (clkctrl &
		(CLKCTRL_CPU_DIV_XTAL_FRAC_EN | CLKCTRL_CPU_DIV_CPU_FRAC_EN)) {
		return 0;
	}

	clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);

	/* XTAL Path */
	if (clkseq & CLKCTRL_CLKSEQ_BYPASS_CPU) {
		div = (clkctrl & CLKCTRL_CPU_DIV_XTAL_MASK) >>
			CLKCTRL_CPU_DIV_XTAL_OFFSET;
		return XTAL_FREQ_MHZ / div;
	}

	/* REF Path */
	clkfrac = readb(&clkctrl_regs->hw_clkctrl_frac0[CLKCTRL_FRAC0_CPU]);
	frac = clkfrac & CLKCTRL_FRAC_FRAC_MASK;
	div = clkctrl & CLKCTRL_CPU_DIV_CPU_MASK;
	return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
}

static uint32_t mx28_get_hclk(void)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;

	uint32_t div;
	uint32_t clkctrl;

	clkctrl = readl(&clkctrl_regs->hw_clkctrl_hbus);

	/* No support of fractional divider calculation */
	if (clkctrl & CLKCTRL_HBUS_DIV_FRAC_EN)
		return 0;

	div = clkctrl & CLKCTRL_HBUS_DIV_MASK;
	return mx28_get_pclk() / div;
}

static uint32_t mx28_get_emiclk(void)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;

	uint32_t clkctrl, clkseq, div;
	uint8_t clkfrac, frac;

	clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
	clkctrl = readl(&clkctrl_regs->hw_clkctrl_emi);

	/* XTAL Path */
	if (clkseq & CLKCTRL_CLKSEQ_BYPASS_EMI) {
		div = (clkctrl & CLKCTRL_EMI_DIV_XTAL_MASK) >>
			CLKCTRL_EMI_DIV_XTAL_OFFSET;
		return XTAL_FREQ_MHZ / div;
	}

	/* REF Path */
	clkfrac = readb(&clkctrl_regs->hw_clkctrl_frac0[CLKCTRL_FRAC0_EMI]);
	frac = clkfrac & CLKCTRL_FRAC_FRAC_MASK;
	div = clkctrl & CLKCTRL_EMI_DIV_EMI_MASK;
	return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
}

static uint32_t mx28_get_gpmiclk(void)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;

	uint32_t clkctrl, clkseq, div;
	uint8_t clkfrac, frac;

	clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
	clkctrl = readl(&clkctrl_regs->hw_clkctrl_gpmi);

	/* XTAL Path */
	if (clkseq & CLKCTRL_CLKSEQ_BYPASS_GPMI) {
		div = clkctrl & CLKCTRL_GPMI_DIV_MASK;
		return XTAL_FREQ_MHZ / div;
	}

	/* REF Path */
	clkfrac = readb(&clkctrl_regs->hw_clkctrl_frac1[CLKCTRL_FRAC1_GPMI]);
	frac = clkfrac & CLKCTRL_FRAC_FRAC_MASK;
	div = clkctrl & CLKCTRL_GPMI_DIV_MASK;
	return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
}

/*
 * Set IO clock frequency, in kHz
 */
void mx28_set_ioclk(enum mxs_ioclock io, uint32_t freq)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
	uint32_t div;
	int io_reg;

	if (freq == 0)
		return;

	if ((io < MXC_IOCLK0) || (io > MXC_IOCLK1))
		return;

	div = (PLL_FREQ_KHZ * PLL_FREQ_COEF) / freq;

	if (div < 18)
		div = 18;

	if (div > 35)
		div = 35;

	io_reg = CLKCTRL_FRAC0_IO0 - io;	/* Register order is reversed */
	writeb(CLKCTRL_FRAC_CLKGATE,
		&clkctrl_regs->hw_clkctrl_frac0_set[io_reg]);
	writeb(CLKCTRL_FRAC_CLKGATE | (div & CLKCTRL_FRAC_FRAC_MASK),
		&clkctrl_regs->hw_clkctrl_frac0[io_reg]);
	writeb(CLKCTRL_FRAC_CLKGATE,
		&clkctrl_regs->hw_clkctrl_frac0_clr[io_reg]);
}

/*
 * Get IO clock, returns IO clock in kHz
 */
static uint32_t mx28_get_ioclk(enum mxs_ioclock io)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
	uint8_t ret;
	int io_reg;

	if ((io < MXC_IOCLK0) || (io > MXC_IOCLK1))
		return 0;

	io_reg = CLKCTRL_FRAC0_IO0 - io;	/* Register order is reversed */

	ret = readb(&clkctrl_regs->hw_clkctrl_frac0[io_reg]) &
		CLKCTRL_FRAC_FRAC_MASK;

	return (PLL_FREQ_KHZ * PLL_FREQ_COEF) / ret;
}

/*
 * Configure SSP clock frequency, in kHz
 */
void mx28_set_sspclk(enum mxs_sspclock ssp, uint32_t freq, int xtal)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
	uint32_t clk, clkreg;

	if (ssp > MXC_SSPCLK3)
		return;

	clkreg = (uint32_t)(&clkctrl_regs->hw_clkctrl_ssp0) +
			(ssp * sizeof(struct mx28_register_32));

	clrbits_le32(clkreg, CLKCTRL_SSP_CLKGATE);
	while (readl(clkreg) & CLKCTRL_SSP_CLKGATE)
		;

	if (xtal)
		clk = XTAL_FREQ_KHZ;
	else
		clk = mx28_get_ioclk(ssp >> 1);

	if (freq > clk)
		return;

	/* Calculate the divider and cap it if necessary */
	clk /= freq;
	if (clk > CLKCTRL_SSP_DIV_MASK)
		clk = CLKCTRL_SSP_DIV_MASK;

	clrsetbits_le32(clkreg, CLKCTRL_SSP_DIV_MASK, clk);
	while (readl(clkreg) & CLKCTRL_SSP_BUSY)
		;

	if (xtal)
		writel(CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp,
			&clkctrl_regs->hw_clkctrl_clkseq_set);
	else
		writel(CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp,
			&clkctrl_regs->hw_clkctrl_clkseq_clr);
}

/*
 * Return SSP frequency, in kHz
 */
static uint32_t mx28_get_sspclk(enum mxs_sspclock ssp)
{
	struct mx28_clkctrl_regs *clkctrl_regs =
		(struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
	uint32_t clkreg;
	uint32_t clk, tmp;

	if (ssp > MXC_SSPCLK3)
		return 0;

	tmp = readl(&clkctrl_regs->hw_clkctrl_clkseq);
	if (tmp & (CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp))
		return XTAL_FREQ_KHZ;

	clkreg = (uint32_t)(&clkctrl_regs->hw_clkctrl_ssp0) +
			(ssp * sizeof(struct mx28_register_32));

	tmp = readl(clkreg) & CLKCTRL_SSP_DIV_MASK;

	if (tmp == 0)
		return 0;

	clk = mx28_get_ioclk(ssp >> 1);

	return clk / tmp;
}

/*
 * Set SSP/MMC bus frequency, in kHz)
 */
void mx28_set_ssp_busclock(unsigned int bus, uint32_t freq)
{
	struct mx28_ssp_regs *ssp_regs;
	const uint32_t sspclk = mx28_get_sspclk(bus);
	uint32_t reg;
	uint32_t divide, rate, tgtclk;

	ssp_regs = (struct mx28_ssp_regs *)(MXS_SSP0_BASE + (bus * 0x2000));

	/*
	 * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)),
	 * CLOCK_DIVIDE has to be an even value from 2 to 254, and
	 * CLOCK_RATE could be any integer from 0 to 255.
	 */
	for (divide = 2; divide < 254; divide += 2) {
		rate = sspclk / freq / divide;
		if (rate <= 256)
			break;
	}

	tgtclk = sspclk / divide / rate;
	while (tgtclk > freq) {
		rate++;
		tgtclk = sspclk / divide / rate;
	}
	if (rate > 256)
		rate = 256;

	/* Always set timeout the maximum */
	reg = SSP_TIMING_TIMEOUT_MASK |
		(divide << SSP_TIMING_CLOCK_DIVIDE_OFFSET) |
		((rate - 1) << SSP_TIMING_CLOCK_RATE_OFFSET);
	writel(reg, &ssp_regs->hw_ssp_timing);

	debug("SPI%d: Set freq rate to %d KHz (requested %d KHz)\n",
		bus, tgtclk, freq);
}

uint32_t mxc_get_clock(enum mxc_clock clk)
{
	switch (clk) {
	case MXC_ARM_CLK:
		return mx28_get_pclk() * 1000000;
	case MXC_GPMI_CLK:
		return mx28_get_gpmiclk() * 1000000;
	case MXC_AHB_CLK:
	case MXC_IPG_CLK:
		return mx28_get_hclk() * 1000000;
	case MXC_EMI_CLK:
		return mx28_get_emiclk();
	case MXC_IO0_CLK:
		return mx28_get_ioclk(MXC_IOCLK0);
	case MXC_IO1_CLK:
		return mx28_get_ioclk(MXC_IOCLK1);
	case MXC_SSP0_CLK:
		return mx28_get_sspclk(MXC_SSPCLK0);
	case MXC_SSP1_CLK:
		return mx28_get_sspclk(MXC_SSPCLK1);
	case MXC_SSP2_CLK:
		return mx28_get_sspclk(MXC_SSPCLK2);
	case MXC_SSP3_CLK:
		return mx28_get_sspclk(MXC_SSPCLK3);
	}

	return 0;
}