summaryrefslogtreecommitdiff
path: root/drivers/clk/aspeed/clk_ast2500.c
blob: 504731271c386b7cf3f5bff8c24dcb0c92d769c3 (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
/*
 * (C) Copyright 2016 Google, Inc
 *
 * SPDX-License-Identifier:	GPL-2.0
 */

#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>

DECLARE_GLOBAL_DATA_PTR;

/*
 * 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.
 */

/*
 * 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_SHIFT) & SCU_MPLL_NUM_MASK;
	const ulong denum = (mpll_reg >> SCU_MPLL_DENUM_SHIFT)
			& SCU_MPLL_DENUM_MASK;
	const ulong post_div = (mpll_reg >> SCU_MPLL_POST_SHIFT)
			& SCU_MPLL_POST_MASK;

	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_SHIFT) & SCU_HPLL_NUM_MASK;
	const ulong denum = (hpll_reg >> SCU_HPLL_DENUM_SHIFT)
			& SCU_HPLL_DENUM_MASK;
	const ulong post_div = (hpll_reg >> SCU_HPLL_POST_SHIFT)
			& SCU_HPLL_POST_MASK;

	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 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;
}

static ulong ast2500_configure_ddr(struct ast2500_scu *scu, ulong rate)
{
	ulong clkin = ast2500_get_clkin(scu);
	u32 mpll_reg;

	/*
	 * There are not that many combinations of numerator, denumerator
	 * and post divider, so just brute force the best combination.
	 * However, to avoid overflow when multiplying, use kHz.
	 */
	const ulong clkin_khz = clkin / 1000;
	const ulong rate_khz = rate / 1000;
	ulong best_num = 0;
	ulong best_denum = 0;
	ulong best_post = 0;
	ulong delta = rate;
	ulong num, denum, post;

	for (denum = 0; denum <= SCU_MPLL_DENUM_MASK; ++denum) {
		for (post = 0; post <= SCU_MPLL_POST_MASK; ++post) {
			num = (rate_khz * (post + 1) / clkin_khz) * (denum + 1);
			ulong new_rate_khz = (clkin_khz
					      * ((num + 1) / (denum + 1)))
					     / (post + 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;

				best_num = num;
				best_denum = denum;
				best_post = post;

				if (delta == 0)
					goto rate_calc_done;
			}
		}
	}

 rate_calc_done:
	mpll_reg = readl(&scu->m_pll_param);
	mpll_reg &= ~((SCU_MPLL_POST_MASK << SCU_MPLL_POST_SHIFT)
		      | (SCU_MPLL_NUM_MASK << SCU_MPLL_NUM_SHIFT)
		      | (SCU_MPLL_DENUM_MASK << SCU_MPLL_DENUM_SHIFT));
	mpll_reg |= (best_post << SCU_MPLL_POST_SHIFT)
	    | (best_num << SCU_MPLL_NUM_SHIFT)
	    | (best_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_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;
	default:
		return -ENOENT;
	}

	return new_rate;
}

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

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

	priv->scu = dev_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,
};