summaryrefslogtreecommitdiffstats
path: root/drivers/clk/meson/clk-pll.c
blob: 664edf0708ea712ad677415904e3361ae5026e6e (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
/*
 * Copyright (c) 2015 Endless Mobile, Inc.
 * Author: Carlo Caione <carlo@endlessm.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * In the most basic form, a Meson PLL is composed as follows:
 *
 *                     PLL
 *      +------------------------------+
 *      |                              |
 * in -----[ /N ]---[ *M ]---[ >>OD ]----->> out
 *      |         ^        ^           |
 *      +------------------------------+
 *                |        |
 *               FREF     VCO
 *
 * out = (in * M / N) >> OD
 */

#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/string.h>

#include "clkc.h"

#define MESON_PLL_RESET				BIT(29)
#define MESON_PLL_LOCK				BIT(31)

struct meson_clk_pll {
	struct clk_hw	hw;
	void __iomem	*base;
	struct pll_conf	*conf;
	unsigned int	rate_count;
	spinlock_t	*lock;
};
#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)

static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
						unsigned long parent_rate)
{
	struct meson_clk_pll *pll = to_meson_clk_pll(hw);
	struct parm *p;
	unsigned long parent_rate_mhz = parent_rate / 1000000;
	unsigned long rate_mhz;
	u16 n, m, od;
	u32 reg;

	p = &pll->conf->n;
	reg = readl(pll->base + p->reg_off);
	n = PARM_GET(p->width, p->shift, reg);

	p = &pll->conf->m;
	reg = readl(pll->base + p->reg_off);
	m = PARM_GET(p->width, p->shift, reg);

	p = &pll->conf->od;
	reg = readl(pll->base + p->reg_off);
	od = PARM_GET(p->width, p->shift, reg);

	rate_mhz = (parent_rate_mhz * m / n) >> od;

	return rate_mhz * 1000000;
}

static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
				     unsigned long *parent_rate)
{
	struct meson_clk_pll *pll = to_meson_clk_pll(hw);
	const struct pll_rate_table *rate_table = pll->conf->rate_table;
	int i;

	for (i = 0; i < pll->rate_count; i++) {
		if (rate <= rate_table[i].rate)
			return rate_table[i].rate;
	}

	/* else return the smallest value */
	return rate_table[0].rate;
}

static const struct pll_rate_table *meson_clk_get_pll_settings(struct meson_clk_pll *pll,
							       unsigned long rate)
{
	const struct pll_rate_table *rate_table = pll->conf->rate_table;
	int i;

	for (i = 0; i < pll->rate_count; i++) {
		if (rate == rate_table[i].rate)
			return &rate_table[i];
	}
	return NULL;
}

static int meson_clk_pll_wait_lock(struct meson_clk_pll *pll,
				   struct parm *p_n)
{
	int delay = 24000000;
	u32 reg;

	while (delay > 0) {
		reg = readl(pll->base + p_n->reg_off);

		if (reg & MESON_PLL_LOCK)
			return 0;
		delay--;
	}
	return -ETIMEDOUT;
}

static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
				  unsigned long parent_rate)
{
	struct meson_clk_pll *pll = to_meson_clk_pll(hw);
	struct parm *p;
	const struct pll_rate_table *rate_set;
	unsigned long old_rate;
	int ret = 0;
	u32 reg;

	if (parent_rate == 0 || rate == 0)
		return -EINVAL;

	old_rate = rate;

	rate_set = meson_clk_get_pll_settings(pll, rate);
	if (!rate_set)
		return -EINVAL;

	/* PLL reset */
	p = &pll->conf->n;
	reg = readl(pll->base + p->reg_off);
	writel(reg | MESON_PLL_RESET, pll->base + p->reg_off);

	reg = PARM_SET(p->width, p->shift, reg, rate_set->n);
	writel(reg, pll->base + p->reg_off);

	p = &pll->conf->m;
	reg = readl(pll->base + p->reg_off);
	reg = PARM_SET(p->width, p->shift, reg, rate_set->m);
	writel(reg, pll->base + p->reg_off);

	p = &pll->conf->od;
	reg = readl(pll->base + p->reg_off);
	reg = PARM_SET(p->width, p->shift, reg, rate_set->od);
	writel(reg, pll->base + p->reg_off);

	p = &pll->conf->n;
	ret = meson_clk_pll_wait_lock(pll, p);
	if (ret) {
		pr_warn("%s: pll did not lock, trying to restore old rate %lu\n",
			__func__, old_rate);
		meson_clk_pll_set_rate(hw, old_rate, parent_rate);
	}

	return ret;
}

static const struct clk_ops meson_clk_pll_ops = {
	.recalc_rate	= meson_clk_pll_recalc_rate,
	.round_rate	= meson_clk_pll_round_rate,
	.set_rate	= meson_clk_pll_set_rate,
};

static const struct clk_ops meson_clk_pll_ro_ops = {
	.recalc_rate	= meson_clk_pll_recalc_rate,
};

struct clk *meson_clk_register_pll(const struct clk_conf *clk_conf,
				   void __iomem *reg_base,
				   spinlock_t *lock)
{
	struct clk *clk;
	struct meson_clk_pll *clk_pll;
	struct clk_init_data init;

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

	clk_pll->base = reg_base + clk_conf->reg_off;
	clk_pll->lock = lock;
	clk_pll->conf = clk_conf->conf.pll;

	init.name = clk_conf->clk_name;
	init.flags = clk_conf->flags | CLK_GET_RATE_NOCACHE;

	init.parent_names = &clk_conf->clks_parent[0];
	init.num_parents = 1;
	init.ops = &meson_clk_pll_ro_ops;

	/* If no rate_table is specified we assume the PLL is read-only */
	if (clk_pll->conf->rate_table) {
		int len;

		for (len = 0; clk_pll->conf->rate_table[len].rate != 0; )
			len++;

		 clk_pll->rate_count = len;
		 init.ops = &meson_clk_pll_ops;
	}

	clk_pll->hw.init = &init;

	clk = clk_register(NULL, &clk_pll->hw);
	if (IS_ERR(clk))
		kfree(clk_pll);

	return clk;
}
9;, etc. You can use a decimal point, too: "-0.5s" is minus half a 'shiftwidth'. The examples below assume a 'shiftwidth' of 4. *cino->* >N Amount added for "normal" indent. Used after a line that should increase the indent (lines starting with "if", an opening brace, etc.). (default 'shiftwidth'). cino= cino=>2 cino=>2s > if (cond) if (cond) if (cond) { { { foo; foo; foo; } } } < *cino-e* eN Add N to the prevailing indent inside a set of braces if the opening brace at the End of the line (more precise: is not the first character in a line). This is useful if you want a different indent when the '{' is at the start of the line from when '{' is at the end of the line. (default 0). cino= cino=e2 cino=e-2 > if (cond) { if (cond) { if (cond) { foo; foo; foo; } } } else else else { { { bar; bar; bar; } } } < *cino-n* nN Add N to the prevailing indent for a statement after an "if", "while", etc., if it is NOT inside a set of braces. This is useful if you want a different indent when there is no '{' before the statement from when there is a '{' before it. (default 0). cino= cino=n2 cino=n-2 > if (cond) if (cond) if (cond) foo; foo; foo; else else else { { { bar; bar; bar; } } } < *cino-f* fN Place the first opening brace of a function or other block in column N. This applies only for an opening brace that is not inside other braces and is at the start of the line. What comes after the brace is put relative to this brace. (default 0). cino= cino=f.5s cino=f1s > func() func() func() { { { int foo; int foo; int foo; < *cino-{* {N Place opening braces N characters from the prevailing indent. This applies only for opening braces that are inside other braces. (default 0). cino= cino={.5s cino={1s > if (cond) if (cond) if (cond) { { { foo; foo; foo; < *cino-}* }N Place closing braces N characters from the matching opening brace. (default 0). cino= cino={2,}-0.5s cino=}2 > if (cond) if (cond) if (cond) { { { foo; foo; foo; } } } < *cino-^* ^N Add N to the prevailing indent inside a set of braces if the opening brace is in column 0. This can specify a different indent for whole of a function (some may like to set it to a negative number). (default 0). cino= cino=^-2 cino=^-s > func() func() func() { { { if (cond) if (cond) if (cond) { { { a = b; a = b; a = b; } } } } } } < *cino-L* LN Controls placement of jump labels. If N is negative, the label will be placed at column 1. If N is non-negative, the indent of the label will be the prevailing indent minus N. (default -1). cino= cino=L2 cino=Ls > func() func() func() { { { { { { stmt; stmt; stmt; LABEL: LABEL: LABEL: } } } } } } < *cino-:* :N Place case labels N characters from the indent of the switch(). (default 'shiftwidth'). cino= cino=:0 > switch (x) switch(x) { { case 1: case 1: a = b; a = b; default: default: } } < *cino-=* =N Place statements occurring after a case label N characters from the indent of the label. (default 'shiftwidth'). cino= cino==10 > case 11: case 11: a = a + 1; a = a + 1; b = b + 1; < *cino-l* lN If N != 0 Vim will align with a case label instead of the statement after it in the same line. cino= cino=l1 > switch (a) { switch (a) { case 1: { case 1: { break; break; } } < *cino-b* bN If N != 0 Vim will align a final "break" with the case label, so that case..break looks like a sort of block. (default: 0). When using 1, consider adding "0=break" to 'cinkeys'. cino= cino=b1 > switch (x) switch(x) { { case 1: case 1: a = b; a = b; break; break; default: default: a = 0; a = 0; break; break; } } < *cino-g* gN Place C++ scope declarations N characters from the indent of the block they are in. (default 'shiftwidth'). A scope declaration can be "public:", "protected:" or "private:". cino= cino=g0 > { { public: public: a = b; a = b; private: private: } } < *cino-h* hN Place statements occurring after a C++ scope declaration N characters from the indent of the label. (default 'shiftwidth'). cino= cino=h10 > public: public: a = a + 1; a = a + 1; b = b + 1; < *cino-N* NN Indent inside C++ namespace N characters extra compared to a normal block. (default 0). cino= cino=N-s > namespace { namespace { void function(); void function(); } } namespace my namespace my { { void function(); void function(); } } < *cino-E* EN Indent inside C++ linkage specifications (extern "C" or extern "C++") N characters extra compared to a normal block. (default 0). cino= cino=E-s > extern "C" { extern "C" { void function(); void function(); } } extern "C" extern "C" { { void function(); void function(); } } < *cino-p* pN Parameter declarations for K&R-style function declarations will be indented N characters from the margin. (default 'shiftwidth'). cino= cino=p0 cino=p2s > func(a, b) func(a, b) func(a, b) int a; int a; int a; char b; char b; char b; < *cino-t* tN Indent a function return type declaration N characters from the margin. (default 'shiftwidth'). cino= cino=t0 cino=t7 > int int int func() func() func() < *cino-i* iN Indent C++ base class declarations and constructor initializations, if they start in a new line (otherwise they are aligned at the right side of the ':'). (default 'shiftwidth'). cino= cino=i0 > class MyClass : class MyClass : public BaseClass public BaseClass {} {} MyClass::MyClass() : MyClass::MyClass() : BaseClass(3) BaseClass(3) {} {} < *cino-+* +N Indent a continuation line (a line that spills onto the next) inside a function N additional characters. (default 'shiftwidth'). Outside of a function, when the previous line ended in a backslash, the 2 * N is used. cino= cino=+10 > a = b + 9 * a = b + 9 * c; c; < *cino-c* cN Indent comment lines after the comment opener, when there is no other text with which to align, N characters from the comment opener. (default 3). See also |format-comments|. cino= cino=c5 > /* /* text. text. */ */ < *cino-C* CN When N is non-zero, indent comment lines by the amount specified with the c flag above even if there is other text behind the comment opener. (default 0). cino=c0 cino=c0,C1 > /******** /*******