summaryrefslogtreecommitdiff
path: root/drivers/clk/aspeed/clk_ast2500.c
blob: 2182320f607fecb10b7326dc99b7162fe97f853c (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
459
460
461
462
463
464
// SPDX-License-Identifier: GPL-2.0
/*
 * (C) Copyright 2016 Google, Inc
 */

#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
#include <asm/io.h>
#include <asm/arch/scu_ast2500.h>
#include <dm/lists.h>
#include <dt-bindings/clock/ast2500-scu.h>

/*
 * MAC Clock Delay settings, taken from Aspeed SDK
 */
#define RGMII_TXCLK_ODLY		8
#define RMII_RXCLK_IDLY		2

/*
 * TGMII Clock Duty constants, taken from Aspeed SDK
 */
#define RGMII2_TXCK_DUTY	0x66
#define RGMII1_TXCK_DUTY	0x64

#define D2PLL_DEFAULT_RATE	(250 * 1000 * 1000)

DECLARE_GLOBAL_DATA_PTR;

/*
 * Clock divider/multiplier configuration struct.
 * For H-PLL and M-PLL the formula is
 * (Output Frequency) = CLKIN * ((M + 1) / (N + 1)) / (P + 1)
 * M - Numerator
 * N - Denumerator
 * P - Post Divider
 * They have the same layout in their control register.
 *
 * D-PLL and D2-PLL have extra divider (OD + 1), which is not
 * yet needed and ignored by clock configurations.
 */
struct ast2500_div_config {
	unsigned int num;
	unsigned int denum;
	unsigned int post_div;
};

/*
 * Get the rate of the M-PLL clock from input clock frequency and
 * the value of the M-PLL Parameter Register.
 */
static ulong ast2500_get_mpll_rate(ulong clkin, u32 mpll_reg)
{
	const ulong num = (mpll_reg & SCU_MPLL_NUM_MASK) >> SCU_MPLL_NUM_SHIFT;
	const ulong denum = (mpll_reg & SCU_MPLL_DENUM_MASK)
			>> SCU_MPLL_DENUM_SHIFT;
	const ulong post_div = (mpll_reg & SCU_MPLL_POST_MASK)
			>> SCU_MPLL_POST_SHIFT;

	return (clkin * ((num + 1) / (denum + 1))) / (post_div + 1);
}

/*
 * Get the rate of the H-PLL clock from input clock frequency and
 * the value of the H-PLL Parameter Register.
 */
static ulong ast2500_get_hpll_rate(ulong clkin, u32 hpll_reg)
{
	const ulong num = (hpll_reg & SCU_HPLL_NUM_MASK) >> SCU_HPLL_NUM_SHIFT;
	const ulong denum = (hpll_reg & SCU_HPLL_DENUM_MASK)
			>> SCU_HPLL_DENUM_SHIFT;
	const ulong post_div = (hpll_reg & SCU_HPLL_POST_MASK)
			>> SCU_HPLL_POST_SHIFT;

	return (clkin * ((num + 1) / (denum + 1))) / (post_div + 1);
}

static ulong ast2500_get_clkin(struct ast2500_scu *scu)
{
	return readl(&scu->hwstrap) & SCU_HWSTRAP_CLKIN_25MHZ
			? 25 * 1000 * 1000 : 24 * 1000 * 1000;
}

/**
 * Get current rate or uart clock
 *
 * @scu SCU registers
 * @uart_index UART index, 1-5
 *
 * @return current setting for uart clock rate
 */
static ulong ast2500_get_uart_clk_rate(struct ast2500_scu *scu, int uart_index)
{
	/*
	 * ast2500 datasheet is very confusing when it comes to UART clocks,
	 * especially when CLKIN = 25 MHz. The settings are in
	 * different registers and it is unclear how they interact.
	 *
	 * This has only been tested with default settings and CLKIN = 24 MHz.
	 */
	ulong uart_clkin;

	if (readl(&scu->misc_ctrl2) &
	    (1 << (uart_index - 1 + SCU_MISC2_UARTCLK_SHIFT)))
		uart_clkin = 192 * 1000 * 1000;
	else
		uart_clkin = 24 * 1000 * 1000;

	if (readl(&scu->misc_ctrl1) & SCU_MISC_UARTCLK_DIV13)
		uart_clkin /= 13;

	return uart_clkin;
}

static ulong ast2500_clk_get_rate(struct clk *clk)
{
	struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);
	ulong clkin = ast2500_get_clkin(priv->scu);
	ulong rate;

	switch (clk->id) {
	case PLL_HPLL:
	case ARMCLK:
		/*
		 * This ignores dynamic/static slowdown of ARMCLK and may
		 * be inaccurate.
		 */
		rate = ast2500_get_hpll_rate(clkin,
					     readl(&priv->scu->h_pll_param));
		break;
	case MCLK_DDR:
		rate = ast2500_get_mpll_rate(clkin,
					     readl(&priv->scu->m_pll_param));
		break;
	case BCLK_PCLK:
		{
			ulong apb_div = 4 + 4 * ((readl(&priv->scu->clk_sel1)
						  & SCU_PCLK_DIV_MASK)
						 >> SCU_PCLK_DIV_SHIFT);
			rate = ast2500_get_hpll_rate(clkin,
						     readl(&priv->
							   scu->h_pll_param));
			rate = rate / apb_div;
		}
		break;
	case PCLK_UART1:
		rate = ast2500_get_uart_clk_rate(priv->scu, 1);
		break;
	case PCLK_UART2:
		rate = ast2500_get_uart_clk_rate(priv->scu, 2);
		break;
	case PCLK_UART3:
		rate = ast2500_get_uart_clk_rate(priv->scu, 3);
		break;
	case PCLK_UART4:
		rate = ast2500_get_uart_clk_rate(priv->scu, 4);
		break;
	case PCLK_UART5:
		rate = ast2500_get_uart_clk_rate(priv->scu, 5);
		break;
	default:
		return -ENOENT;
	}

	return rate;
}

/*
 * @input_rate - the rate of input clock in Hz
 * @requested_rate - desired output rate in Hz
 * @div - this is an IN/OUT parameter, at input all fields of the config
 * need to be set to their maximum allowed values.
 * The result (the best config we could find), would also be returned
 * in this structure.
 *
 * @return The clock rate, when the resulting div_config is used.
 */
static ulong ast2500_calc_clock_config(ulong input_rate, ulong requested_rate,
				       struct ast2500_div_config *cfg)
{
	/*
	 * The assumption is that kHz precision is good enough and
	 * also enough to avoid overflow when multiplying.
	 */
	const ulong input_rate_khz = input_rate / 1000;
	const ulong rate_khz = requested_rate / 1000;
	const struct ast2500_div_config max_vals = *cfg;
	struct ast2500_div_config it = { 0, 0, 0 };
	ulong delta = rate_khz;
	ulong new_rate_khz = 0;

	for (; it.denum <= max_vals.denum; ++it.denum) {
		for (it.post_div = 0; it.post_div <= max_vals.post_div;
		     ++it.post_div) {
			it.num = (rate_khz * (it.post_div + 1) / input_rate_khz)
			    * (it.denum + 1);
			if (it.num > max_vals.num)
				continue;

			new_rate_khz = (input_rate_khz
					* ((it.num + 1) / (it.denum + 1)))
			    / (it.post_div + 1);

			/* Keep the rate below requested one. */
			if (new_rate_khz > rate_khz)
				continue;

			if (new_rate_khz - rate_khz < delta) {
				delta = new_rate_khz - rate_khz;
				*cfg = it;
				if (delta == 0)
					return new_rate_khz * 1000;
			}
		}
	}

	return new_rate_khz * 1000;
}

static ulong ast2500_configure_ddr(struct ast2500_scu *scu, ulong rate)
{
	ulong clkin = ast2500_get_clkin(scu);
	u32 mpll_reg;
	struct ast2500_div_config div_cfg = {
		.num = (SCU_MPLL_NUM_MASK >> SCU_MPLL_NUM_SHIFT),
		.denum = (SCU_MPLL_DENUM_MASK >> SCU_MPLL_DENUM_SHIFT),
		.post_div = (SCU_MPLL_POST_MASK >> SCU_MPLL_POST_SHIFT),
	};

	ast2500_calc_clock_config(clkin, rate, &div_cfg);

	mpll_reg = readl(&scu->m_pll_param);
	mpll_reg &= ~(SCU_MPLL_POST_MASK | SCU_MPLL_NUM_MASK
		      | SCU_MPLL_DENUM_MASK);
	mpll_reg |= (div_cfg.post_div << SCU_MPLL_POST_SHIFT)
	    | (div_cfg.num << SCU_MPLL_NUM_SHIFT)
	    | (div_cfg.denum << SCU_MPLL_DENUM_SHIFT);

	ast_scu_unlock(scu);
	writel(mpll_reg, &scu->m_pll_param);
	ast_scu_lock(scu);

	return ast2500_get_mpll_rate(clkin, mpll_reg);
}

static ulong ast2500_configure_mac(struct ast2500_scu *scu, int index)
{
	ulong clkin = ast2500_get_clkin(scu);
	ulong hpll_rate = ast2500_get_hpll_rate(clkin,
						readl(&scu->h_pll_param));
	ulong required_rate;
	u32 hwstrap;
	u32 divisor;
	u32 reset_bit;
	u32 clkstop_bit;

	/*
	 * According to data sheet, for 10/100 mode the MAC clock frequency
	 * should be at least 25MHz and for 1000 mode at least 100MHz
	 */
	hwstrap = readl(&scu->hwstrap);
	if (hwstrap & (SCU_HWSTRAP_MAC1_RGMII | SCU_HWSTRAP_MAC2_RGMII))
		required_rate = 100 * 1000 * 1000;
	else
		required_rate = 25 * 1000 * 1000;

	divisor = hpll_rate / required_rate;

	if (divisor < 4) {
		/* Clock can't run fast enough, but let's try anyway */
		debug("MAC clock too slow\n");
		divisor = 4;
	} else if (divisor > 16) {
		/* Can't slow down the clock enough, but let's try anyway */
		debug("MAC clock too fast\n");
		divisor = 16;
	}

	switch (index) {
	case 1:
		reset_bit = SCU_SYSRESET_MAC1;
		clkstop_bit = SCU_CLKSTOP_MAC1;
		break;
	case 2:
		reset_bit = SCU_SYSRESET_MAC2;
		clkstop_bit = SCU_CLKSTOP_MAC2;
		break;
	default:
		return -EINVAL;
	}

	ast_scu_unlock(scu);
	clrsetbits_le32(&scu->clk_sel1, SCU_MACCLK_MASK,
			((divisor - 2) / 2) << SCU_MACCLK_SHIFT);

	/*
	 * Disable MAC, start its clock and re-enable it.
	 * The procedure and the delays (100us & 10ms) are
	 * specified in the datasheet.
	 */
	setbits_le32(&scu->sysreset_ctrl1, reset_bit);
	udelay(100);
	clrbits_le32(&scu->clk_stop_ctrl1, clkstop_bit);
	mdelay(10);
	clrbits_le32(&scu->sysreset_ctrl1, reset_bit);

	writel((RGMII2_TXCK_DUTY << SCU_CLKDUTY_RGMII2TXCK_SHIFT)
	       | (RGMII1_TXCK_DUTY << SCU_CLKDUTY_RGMII1TXCK_SHIFT),
	       &scu->clk_duty_sel);

	ast_scu_lock(scu);

	return required_rate;
}

static ulong ast2500_configure_d2pll(struct ast2500_scu *scu, ulong rate)
{
	/*
	 * The values and the meaning of the next three
	 * parameters are undocumented. Taken from Aspeed SDK.
	 */
	const u32 d2_pll_ext_param = 0x2c;
	const u32 d2_pll_sip = 0x11;
	const u32 d2_pll_sic = 0x18;
	u32 clk_delay_settings =
	    (RMII_RXCLK_IDLY << SCU_MICDS_MAC1RMII_RDLY_SHIFT)
	    | (RMII_RXCLK_IDLY << SCU_MICDS_MAC2RMII_RDLY_SHIFT)
	    | (RGMII_TXCLK_ODLY << SCU_MICDS_MAC1RGMII_TXDLY_SHIFT)
	    | (RGMII_TXCLK_ODLY << SCU_MICDS_MAC2RGMII_TXDLY_SHIFT);
	struct ast2500_div_config div_cfg = {
		.num = SCU_D2PLL_NUM_MASK >> SCU_D2PLL_NUM_SHIFT,
		.denum = SCU_D2PLL_DENUM_MASK >> SCU_D2PLL_DENUM_SHIFT,
		.post_div = SCU_D2PLL_POST_MASK >> SCU_D2PLL_POST_SHIFT,
	};
	ulong clkin = ast2500_get_clkin(scu);
	ulong new_rate;

	ast_scu_unlock(scu);
	writel((d2_pll_ext_param << SCU_D2PLL_EXT1_PARAM_SHIFT)
	       | SCU_D2PLL_EXT1_OFF
	       | SCU_D2PLL_EXT1_RESET, &scu->d2_pll_ext_param[0]);

	/*
	 * Select USB2.0 port1 PHY clock as a clock source for GCRT.
	 * This would disconnect it from D2-PLL.
	 */
	clrsetbits_le32(&scu->misc_ctrl1, SCU_MISC_D2PLL_OFF,
			SCU_MISC_GCRT_USB20CLK);

	new_rate = ast2500_calc_clock_config(clkin, rate, &div_cfg);
	writel((d2_pll_sip << SCU_D2PLL_SIP_SHIFT)
	       | (d2_pll_sic << SCU_D2PLL_SIC_SHIFT)
	       | (div_cfg.num << SCU_D2PLL_NUM_SHIFT)
	       | (div_cfg.denum << SCU_D2PLL_DENUM_SHIFT)
	       | (div_cfg.post_div << SCU_D2PLL_POST_SHIFT),
	       &scu->d2_pll_param);

	clrbits_le32(&scu->d2_pll_ext_param[0],
		     SCU_D2PLL_EXT1_OFF | SCU_D2PLL_EXT1_RESET);

	clrsetbits_le32(&scu->misc_ctrl2,
			SCU_MISC2_RGMII_HPLL | SCU_MISC2_RMII_MPLL
			| SCU_MISC2_RGMII_CLKDIV_MASK |
			SCU_MISC2_RMII_CLKDIV_MASK,
			(4 << SCU_MISC2_RMII_CLKDIV_SHIFT));

	writel(clk_delay_settings | SCU_MICDS_RGMIIPLL, &scu->mac_clk_delay);
	writel(clk_delay_settings, &scu->mac_clk_delay_100M);
	writel(clk_delay_settings, &scu->mac_clk_delay_10M);

	ast_scu_lock(scu);

	return new_rate;
}

static ulong ast2500_clk_set_rate(struct clk *clk, ulong rate)
{
	struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);

	ulong new_rate;
	switch (clk->id) {
	case PLL_MPLL:
	case MCLK_DDR:
		new_rate = ast2500_configure_ddr(priv->scu, rate);
		break;
	case PLL_D2PLL:
		new_rate = ast2500_configure_d2pll(priv->scu, rate);
		break;
	default:
		return -ENOENT;
	}

	return new_rate;
}

static int ast2500_clk_enable(struct clk *clk)
{
	struct ast2500_clk_priv *priv = dev_get_priv(clk->dev);

	switch (clk->id) {
	/*
	 * For MAC clocks the clock rate is
	 * configured based on whether RGMII or RMII mode has been selected
	 * through hardware strapping.
	 */
	case PCLK_MAC1:
		ast2500_configure_mac(priv->scu, 1);
		break;
	case PCLK_MAC2:
		ast2500_configure_mac(priv->scu, 2);
		break;
	case PLL_D2PLL:
		ast2500_configure_d2pll(priv->scu, D2PLL_DEFAULT_RATE);
		break;
	default:
		return -ENOENT;
	}

	return 0;
}

struct clk_ops ast2500_clk_ops = {
	.get_rate = ast2500_clk_get_rate,
	.set_rate = ast2500_clk_set_rate,
	.enable = ast2500_clk_enable,
};

static int ast2500_clk_probe(struct udevice *dev)
{
	struct ast2500_clk_priv *priv = dev_get_priv(dev);

	priv->scu = devfdt_get_addr_ptr(dev);
	if (IS_ERR(priv->scu))
		return PTR_ERR(priv->scu);

	return 0;
}

static int ast2500_clk_bind(struct udevice *dev)
{
	int ret;

	/* The reset driver does not have a device node, so bind it here */
	ret = device_bind_driver(gd->dm_root, "ast_sysreset", "reset", &dev);
	if (ret)
		debug("Warning: No reset driver: ret=%d\n", ret);

	return 0;
}

static const struct udevice_id ast2500_clk_ids[] = {
	{ .compatible = "aspeed,ast2500-scu" },
	{ }
};

U_BOOT_DRIVER(aspeed_ast2500_scu) = {
	.name		= "aspeed_ast2500_scu",
	.id		= UCLASS_CLK,
	.of_match	= ast2500_clk_ids,
	.priv_auto_alloc_size = sizeof(struct ast2500_clk_priv),
	.ops		= &ast2500_clk_ops,
	.bind		= ast2500_clk_bind,
	.probe		= ast2500_clk_probe,
};