summaryrefslogtreecommitdiffstats
path: root/drivers/clk/zynqmp/clk-pll-zynqmp.c
blob: e4b759b73c9cf53b32ce240f6f67786387ce8650 (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
// SPDX-License-Identifier: GPL-2.0
/*
 * Zynq UltraScale+ MPSoC PLL Clock
 *
 * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de>
 *
 * Based on the Linux driver in drivers/clk/zynqmp/
 *
 * Copyright (C) 2016-2018 Xilinx
 */

#include <common.h>
#include <linux/clk.h>
#include <mach/firmware-zynqmp.h>

#include "clk-zynqmp.h"

struct zynqmp_pll {
	struct clk clk;
	unsigned int clk_id;
	const char *parent;
	const struct zynqmp_eemi_ops *ops;
};

#define to_zynqmp_pll(clk) \
	container_of(clk, struct zynqmp_pll, clk)

#define PLL_FBDIV_MIN		25
#define PLL_FBDIV_MAX		125

#define PS_PLL_VCO_MIN		1500000000
#define PS_PLL_VCO_MAX		3000000000UL

enum pll_mode {
	PLL_MODE_INT,
	PLL_MODE_FRAC,
};

#define FRAC_DIV 		(1 << 16)

static inline enum pll_mode zynqmp_pll_get_mode(struct zynqmp_pll *pll)
{
	u32 ret_payload[PAYLOAD_ARG_CNT];

	pll->ops->ioctl(0, IOCTL_GET_PLL_FRAC_MODE, pll->clk_id, 0,
			      ret_payload);

	return ret_payload[1];
}

static inline void zynqmp_pll_set_mode(struct zynqmp_pll *pll, enum pll_mode mode)
{
	pll->ops->ioctl(0, IOCTL_SET_PLL_FRAC_MODE, pll->clk_id, mode, NULL);
}

static long zynqmp_pll_round_rate(struct clk *clk, unsigned long rate,
				  unsigned long *prate)
{
	struct zynqmp_pll *pll = to_zynqmp_pll(clk);
	u32 fbdiv;
	long rate_div;

	rate_div = (rate * FRAC_DIV) / *prate;
	if (rate_div % FRAC_DIV)
		zynqmp_pll_set_mode(pll, PLL_MODE_FRAC);
	else
		zynqmp_pll_set_mode(pll, PLL_MODE_INT);

	if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) {
		if (rate > PS_PLL_VCO_MAX) {
			fbdiv = rate / PS_PLL_VCO_MAX;
			rate = rate / (fbdiv + 1);
		}
		if (rate < PS_PLL_VCO_MIN) {
			fbdiv = DIV_ROUND_UP(PS_PLL_VCO_MIN, rate);
			rate = rate * fbdiv;
		}
	} else {
		fbdiv = DIV_ROUND_CLOSEST(rate, *prate);
		fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
		rate = *prate * fbdiv;
	}

	return rate;
}

static unsigned long zynqmp_pll_recalc_rate(struct clk *clk,
					    unsigned long parent_rate)
{
	struct zynqmp_pll *pll = to_zynqmp_pll(clk);
	u32 clk_id = pll->clk_id;
	u32 fbdiv, data;
	unsigned long rate, frac;
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	ret = pll->ops->clock_getdivider(clk_id, &fbdiv);

	rate = parent_rate * fbdiv;

	if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) {
		pll->ops->ioctl(0, IOCTL_GET_PLL_FRAC_DATA, clk_id, 0,
				ret_payload);
		data = ret_payload[1];
		frac = (parent_rate * data) / FRAC_DIV;
		rate = rate + frac;
	}

	return rate;
}

static int zynqmp_pll_set_rate(struct clk *clk, unsigned long rate,
			       unsigned long parent_rate)
{
	struct zynqmp_pll *pll = to_zynqmp_pll(clk);
	u32 clk_id = pll->clk_id;
	u32 fbdiv;
	long rate_div, frac, m, f;

	if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) {
		rate_div = (rate * FRAC_DIV) / parent_rate;
		m = rate_div / FRAC_DIV;
		f = rate_div % FRAC_DIV;
		m = clamp_t(u32, m, (PLL_FBDIV_MIN), (PLL_FBDIV_MAX));
		rate = parent_rate * m;
		frac = (parent_rate * f) / FRAC_DIV;

		pll->ops->clock_setdivider(clk_id, m);
		pll->ops->ioctl(0, IOCTL_SET_PLL_FRAC_DATA, clk_id, f, NULL);

		return rate + frac;
	} else {
		fbdiv = DIV_ROUND_CLOSEST(rate, parent_rate);
		fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
		pll->ops->clock_setdivider(clk_id, fbdiv);

		return parent_rate * fbdiv;
	}
}

static int zynqmp_pll_is_enabled(struct clk *clk)
{
	struct zynqmp_pll *pll = to_zynqmp_pll(clk);
	u32 is_enabled;
	int ret;

	ret = pll->ops->clock_getstate(pll->clk_id, &is_enabled);
	if (ret)
		return -EIO;

	return !!(is_enabled);
}

static int zynqmp_pll_enable(struct clk *clk)
{
	struct zynqmp_pll *pll = to_zynqmp_pll(clk);

	if (zynqmp_pll_is_enabled(clk))
		return 0;

	return pll->ops->clock_enable(pll->clk_id);
}

static void zynqmp_pll_disable(struct clk *clk)
{
	struct zynqmp_pll *pll = to_zynqmp_pll(clk);

	if (!zynqmp_pll_is_enabled(clk))
		return;

	pll->ops->clock_disable(pll->clk_id);
}

static const struct clk_ops zynqmp_pll_ops = {
	.enable = zynqmp_pll_enable,
	.disable = zynqmp_pll_disable,
	.is_enabled = zynqmp_pll_is_enabled,
	.round_rate = zynqmp_pll_round_rate,
	.recalc_rate = zynqmp_pll_recalc_rate,
	.set_rate = zynqmp_pll_set_rate,
};

struct clk *zynqmp_clk_register_pll(const char *name,
				    unsigned int clk_id,
				    const char **parents,
				    unsigned int num_parents,
				    const struct clock_topology *nodes)
{
	struct zynqmp_pll *pll;
	int ret;

	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
	if (!pll)
		return ERR_PTR(-ENOMEM);

	pll->clk_id = clk_id;
	pll->ops = zynqmp_pm_get_eemi_ops();
	pll->parent = strdup(parents[0]);

	pll->clk.name = strdup(name);
	pll->clk.ops = &zynqmp_pll_ops;
	pll->clk.flags = nodes->flag | CLK_SET_RATE_PARENT;
	pll->clk.parent_names = &pll->parent;
	pll->clk.num_parents = 1;

	ret = clk_register(&pll->clk);
	if (ret) {
		kfree(pll);
		return ERR_PTR(ret);
	}

	return &pll->clk;
}