summaryrefslogtreecommitdiff
path: root/drivers/clk/sophgo/clk-pll.c
blob: c99aa0b4e440c9876b42fbcafbcc04af1acd7871 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2024, Kongyang Liu <seashell11234455@gmail.com>
 */

#include <clk-uclass.h>
#include <dm.h>
#include <div64.h>
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/kernel.h>

#include "clk-common.h"
#include "clk-pll.h"

#define PLL_PRE_DIV_MIN      1
#define PLL_PRE_DIV_MAX      127
#define PLL_POST_DIV_MIN     1
#define PLL_POST_DIV_MAX     127
#define PLL_DIV_MIN          6
#define PLL_DIV_MAX          127
#define PLL_ICTRL_MIN        0
#define PLL_ICTRL_MAX        7
#define PLL_MODE_MIN         0
#define PLL_MODE_MAX         3
#define FOR_RANGE(x, RANGE) for (x = RANGE##_MIN; x <= RANGE##_MAX; x++)

#define PLL_ICTRL GENMASK(26, 24)
#define PLL_DIV_SEL GENMASK(23, 17)
#define PLL_SEL_MODE GENMASK(16, 15)
#define PLL_POST_DIV_SEL GENMASK(14, 8)
#define PLL_PRE_DIV_SEL GENMASK(6, 0)
#define PLL_MASK_ALL (PLL_ICTRL | PLL_DIV_SEL | PLL_SEL_MODE | PLL_POST_DIV_SEL | PLL_PRE_DIV_SEL)

/* IPLL */
#define to_clk_ipll(dev) container_of(dev, struct cv1800b_clk_ipll, clk)

static int cv1800b_ipll_enable(struct clk *clk)
{
	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);

	cv1800b_clk_clrbit(pll->base, &pll->pll_pwd);
	return 0;
}

static int cv1800b_ipll_disable(struct clk *clk)
{
	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);

	cv1800b_clk_setbit(pll->base, &pll->pll_pwd);
	return 0;
}

static ulong cv1800b_ipll_get_rate(struct clk *clk)
{
	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);

	ulong parent_rate = clk_get_parent_rate(clk);
	u32 reg = readl(pll->base + pll->pll_reg);
	u32 pre_div = FIELD_GET(PLL_PRE_DIV_SEL, reg);
	u32 post_div = FIELD_GET(PLL_POST_DIV_SEL, reg);
	u32 div = FIELD_GET(PLL_DIV_SEL, reg);

	return DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div);
}

static ulong cv1800b_ipll_set_rate(struct clk *clk, ulong rate)
{
	struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
	ulong parent_rate = clk_get_parent_rate(clk);
	u32 pre_div, post_div, div;
	u32 pre_div_sel, post_div_sel, div_sel;
	ulong new_rate, best_rate = 0;
	u32 mode, ictrl;
	u32 test, val;

	FOR_RANGE(pre_div, PLL_PRE_DIV)
	{
		FOR_RANGE(post_div, PLL_POST_DIV)
		{
			FOR_RANGE(div, PLL_DIV)
			{
				new_rate =
					DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div);
				if (rate - new_rate < rate - best_rate) {
					best_rate = new_rate;
					pre_div_sel = pre_div;
					post_div_sel = post_div;
					div_sel = div;
				}
			}
		}
	}

	FOR_RANGE(mode, PLL_MODE)
	{
		FOR_RANGE(ictrl, PLL_ICTRL)
		{
			test = 184 * (1 + mode) * (1 + ictrl) / 2;
			if (test > 20 * div_sel && test < 35 * div_sel) {
				val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) |
				      FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) |
				      FIELD_PREP(PLL_DIV_SEL, div_sel) |
				      FIELD_PREP(PLL_ICTRL, ictrl) |
				      FIELD_PREP(PLL_SEL_MODE, mode);
				clrsetbits_le32(pll->base + pll->pll_reg, PLL_MASK_ALL, val);
				return best_rate;
			}
		}
	}

	return -EINVAL;
}

const struct clk_ops cv1800b_ipll_ops = {
	.enable = cv1800b_ipll_enable,
	.disable = cv1800b_ipll_disable,
	.get_rate = cv1800b_ipll_get_rate,
	.set_rate = cv1800b_ipll_set_rate,
};

U_BOOT_DRIVER(cv1800b_clk_ipll) = {
	.name = "cv1800b_clk_ipll",
	.id = UCLASS_CLK,
	.ops = &cv1800b_ipll_ops,
	.flags = DM_FLAG_PRE_RELOC,
};

/* FPLL */
#define to_clk_fpll(dev) container_of(dev, struct cv1800b_clk_fpll, ipll.clk)

static ulong cv1800b_fpll_get_rate(struct clk *clk)
{
	struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
	u32 val, syn_set;
	u32 pre_div, post_div, div;
	u8 mult = 1;
	ulong divisor, remainder, rate;

	if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en))
		return cv1800b_ipll_get_rate(clk);

	syn_set = readl(pll->ipll.base + pll->syn.set);
	if (syn_set == 0)
		return 0;

	val = readl(pll->ipll.base + pll->ipll.pll_reg);
	pre_div = FIELD_GET(PLL_PRE_DIV_SEL, val);
	post_div = FIELD_GET(PLL_POST_DIV_SEL, val);
	div = FIELD_GET(PLL_DIV_SEL, val);

	if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half))
		mult = 2;

	divisor = (ulong)pre_div * post_div * syn_set;
	rate = (clk_get_parent_rate(clk) * div) << 25;
	remainder = rate % divisor;
	rate /= divisor;
	return rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor);
}

static ulong cv1800b_find_syn(ulong rate, ulong parent_rate, ulong pre_div, ulong post_div,
			      ulong div, u32 *syn)
{
	u32 syn_min = (4 << 26) + 1;
	u32 syn_max = U32_MAX;
	u32 mid;
	ulong new_rate;
	u32 mult = 1;
	ulong divisor, remainder;

	while (syn_min < syn_max) {
		mid = ((ulong)syn_min + syn_max) / 2;
		divisor = pre_div * post_div * mid;
		new_rate = (parent_rate * div) << 25;
		remainder = do_div(new_rate, divisor);
		new_rate = new_rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor);
		if (new_rate > rate) {
			syn_max = mid + 1;
		} else if (new_rate < rate) {
			syn_min = mid - 1;
		} else {
			syn_min = mid;
			break;
		}
	}
	*syn = syn_min;
	return new_rate;
}

static ulong cv1800b_fpll_set_rate(struct clk *clk, ulong rate)
{
	struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
	ulong parent_rate = clk_get_parent_rate(clk);
	u32 pre_div, post_div, div;
	u32 pre_div_sel, post_div_sel, div_sel;
	u32 syn, syn_sel;
	ulong new_rate, best_rate = 0;
	u32 mult = 1;
	u32 mode, ictrl;

	if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en))
		return cv1800b_ipll_set_rate(clk, rate);

	if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half))
		mult = 2;

	FOR_RANGE(pre_div, PLL_PRE_DIV)
	{
		FOR_RANGE(post_div, PLL_POST_DIV)
		{
			FOR_RANGE(div, PLL_DIV)
			{
				new_rate = cv1800b_find_syn(rate, parent_rate, pre_div, post_div,
							    div, &syn);
				if (rate - new_rate < rate - best_rate) {
					best_rate = new_rate;
					pre_div_sel = pre_div;
					post_div_sel = post_div;
					div_sel = div;
					syn_sel = syn;
				}
			}
		}
	}

	FOR_RANGE(mode, PLL_MODE)
	{
		FOR_RANGE(ictrl, PLL_ICTRL)
		{
			u32 test = 184 * (1 + mode) * (1 + ictrl) / 2;

			if (test > 10 * div_sel && test <= 24 * div_sel) {
				u32 val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) |
					  FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) |
					  FIELD_PREP(PLL_DIV_SEL, div_sel) |
					  FIELD_PREP(PLL_ICTRL, ictrl) |
					  FIELD_PREP(PLL_SEL_MODE, mode);
				clrsetbits_le32(pll->ipll.base + pll->ipll.pll_reg, PLL_MASK_ALL,
						val);
				writel(syn_sel, pll->ipll.base + pll->syn.set);
				return best_rate;
			}
		}
	}

	return -EINVAL;
}

static int cv1800b_fpll_set_parent(struct clk *clk, struct clk *parent)
{
	struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);

	if (parent->id == CV1800B_CLK_BYPASS)
		cv1800b_clk_setbit(pll->ipll.base, &pll->syn.en);
	else
		cv1800b_clk_clrbit(pll->ipll.base, &pll->syn.en);

	return 0;
}

const struct clk_ops cv1800b_fpll_ops = {
	.enable = cv1800b_ipll_enable,
	.disable = cv1800b_ipll_disable,
	.get_rate = cv1800b_fpll_get_rate,
	.set_rate = cv1800b_fpll_set_rate,
	.set_parent = cv1800b_fpll_set_parent,
};

U_BOOT_DRIVER(cv1800b_clk_fpll) = {
	.name = "cv1800b_clk_fpll",
	.id = UCLASS_CLK,
	.ops = &cv1800b_fpll_ops,
	.flags = DM_FLAG_PRE_RELOC,
};