Commit | Line | Data |
---|---|---|
7a29a869 CC |
1 | /* |
2 | * Copyright (c) 2015 Endless Mobile, Inc. | |
3 | * Author: Carlo Caione <carlo@endlessm.com> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms and conditions of the GNU General Public License, | |
7 | * version 2, as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along with | |
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | */ | |
17 | ||
18 | /* | |
19 | * In the most basic form, a Meson PLL is composed as follows: | |
20 | * | |
21 | * PLL | |
22 | * +------------------------------+ | |
23 | * | | | |
24 | * in -----[ /N ]---[ *M ]---[ >>OD ]----->> out | |
25 | * | ^ ^ | | |
26 | * +------------------------------+ | |
27 | * | | | |
28 | * FREF VCO | |
29 | * | |
30 | * out = (in * M / N) >> OD | |
31 | */ | |
32 | ||
33 | #include <linux/clk-provider.h> | |
34 | #include <linux/delay.h> | |
35 | #include <linux/err.h> | |
36 | #include <linux/io.h> | |
37 | #include <linux/module.h> | |
38 | #include <linux/of_address.h> | |
39 | #include <linux/slab.h> | |
40 | #include <linux/string.h> | |
41 | ||
42 | #include "clkc.h" | |
43 | ||
44 | #define MESON_PLL_RESET BIT(29) | |
45 | #define MESON_PLL_LOCK BIT(31) | |
46 | ||
47 | struct meson_clk_pll { | |
48 | struct clk_hw hw; | |
49 | void __iomem *base; | |
50 | struct pll_conf *conf; | |
51 | unsigned int rate_count; | |
52 | spinlock_t *lock; | |
53 | }; | |
54 | #define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw) | |
55 | ||
56 | static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw, | |
57 | unsigned long parent_rate) | |
58 | { | |
59 | struct meson_clk_pll *pll = to_meson_clk_pll(hw); | |
60 | struct parm *p; | |
61 | unsigned long parent_rate_mhz = parent_rate / 1000000; | |
62 | unsigned long rate_mhz; | |
63 | u16 n, m, od; | |
64 | u32 reg; | |
65 | ||
66 | p = &pll->conf->n; | |
67 | reg = readl(pll->base + p->reg_off); | |
68 | n = PARM_GET(p->width, p->shift, reg); | |
69 | ||
70 | p = &pll->conf->m; | |
71 | reg = readl(pll->base + p->reg_off); | |
72 | m = PARM_GET(p->width, p->shift, reg); | |
73 | ||
74 | p = &pll->conf->od; | |
75 | reg = readl(pll->base + p->reg_off); | |
76 | od = PARM_GET(p->width, p->shift, reg); | |
77 | ||
78 | rate_mhz = (parent_rate_mhz * m / n) >> od; | |
79 | ||
80 | return rate_mhz * 1000000; | |
81 | } | |
82 | ||
83 | static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, | |
84 | unsigned long *parent_rate) | |
85 | { | |
86 | struct meson_clk_pll *pll = to_meson_clk_pll(hw); | |
87 | const struct pll_rate_table *rate_table = pll->conf->rate_table; | |
88 | int i; | |
89 | ||
90 | for (i = 0; i < pll->rate_count; i++) { | |
91 | if (rate <= rate_table[i].rate) | |
92 | return rate_table[i].rate; | |
93 | } | |
94 | ||
95 | /* else return the smallest value */ | |
96 | return rate_table[0].rate; | |
97 | } | |
98 | ||
99 | static const struct pll_rate_table *meson_clk_get_pll_settings(struct meson_clk_pll *pll, | |
100 | unsigned long rate) | |
101 | { | |
102 | const struct pll_rate_table *rate_table = pll->conf->rate_table; | |
103 | int i; | |
104 | ||
105 | for (i = 0; i < pll->rate_count; i++) { | |
106 | if (rate == rate_table[i].rate) | |
107 | return &rate_table[i]; | |
108 | } | |
109 | return NULL; | |
110 | } | |
111 | ||
112 | static int meson_clk_pll_wait_lock(struct meson_clk_pll *pll, | |
113 | struct parm *p_n) | |
114 | { | |
115 | int delay = 24000000; | |
116 | u32 reg; | |
117 | ||
118 | while (delay > 0) { | |
119 | reg = readl(pll->base + p_n->reg_off); | |
120 | ||
121 | if (reg & MESON_PLL_LOCK) | |
122 | return 0; | |
123 | delay--; | |
124 | } | |
125 | return -ETIMEDOUT; | |
126 | } | |
127 | ||
128 | static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, | |
129 | unsigned long parent_rate) | |
130 | { | |
131 | struct meson_clk_pll *pll = to_meson_clk_pll(hw); | |
132 | struct parm *p; | |
133 | const struct pll_rate_table *rate_set; | |
134 | unsigned long old_rate; | |
135 | int ret = 0; | |
136 | u32 reg; | |
137 | ||
138 | if (parent_rate == 0 || rate == 0) | |
139 | return -EINVAL; | |
140 | ||
141 | old_rate = rate; | |
142 | ||
143 | rate_set = meson_clk_get_pll_settings(pll, rate); | |
144 | if (!rate_set) | |
145 | return -EINVAL; | |
146 | ||
147 | /* PLL reset */ | |
148 | p = &pll->conf->n; | |
149 | reg = readl(pll->base + p->reg_off); | |
150 | writel(reg | MESON_PLL_RESET, pll->base + p->reg_off); | |
151 | ||
152 | reg = PARM_SET(p->width, p->shift, reg, rate_set->n); | |
153 | writel(reg, pll->base + p->reg_off); | |
154 | ||
155 | p = &pll->conf->m; | |
156 | reg = readl(pll->base + p->reg_off); | |
157 | reg = PARM_SET(p->width, p->shift, reg, rate_set->m); | |
158 | writel(reg, pll->base + p->reg_off); | |
159 | ||
160 | p = &pll->conf->od; | |
161 | reg = readl(pll->base + p->reg_off); | |
162 | reg = PARM_SET(p->width, p->shift, reg, rate_set->od); | |
163 | writel(reg, pll->base + p->reg_off); | |
164 | ||
165 | p = &pll->conf->n; | |
166 | ret = meson_clk_pll_wait_lock(pll, p); | |
167 | if (ret) { | |
168 | pr_warn("%s: pll did not lock, trying to restore old rate %lu\n", | |
169 | __func__, old_rate); | |
170 | meson_clk_pll_set_rate(hw, old_rate, parent_rate); | |
171 | } | |
172 | ||
173 | return ret; | |
174 | } | |
175 | ||
176 | static const struct clk_ops meson_clk_pll_ops = { | |
177 | .recalc_rate = meson_clk_pll_recalc_rate, | |
178 | .round_rate = meson_clk_pll_round_rate, | |
179 | .set_rate = meson_clk_pll_set_rate, | |
180 | }; | |
181 | ||
182 | static const struct clk_ops meson_clk_pll_ro_ops = { | |
183 | .recalc_rate = meson_clk_pll_recalc_rate, | |
184 | }; | |
185 | ||
186 | struct clk *meson_clk_register_pll(const struct clk_conf *clk_conf, | |
187 | void __iomem *reg_base, | |
188 | spinlock_t *lock) | |
189 | { | |
190 | struct clk *clk; | |
191 | struct meson_clk_pll *clk_pll; | |
192 | struct clk_init_data init; | |
193 | ||
194 | clk_pll = kzalloc(sizeof(*clk_pll), GFP_KERNEL); | |
195 | if (!clk_pll) | |
196 | return ERR_PTR(-ENOMEM); | |
197 | ||
198 | clk_pll->base = reg_base + clk_conf->reg_off; | |
199 | clk_pll->lock = lock; | |
200 | clk_pll->conf = clk_conf->conf.pll; | |
201 | ||
202 | init.name = clk_conf->clk_name; | |
203 | init.flags = clk_conf->flags | CLK_GET_RATE_NOCACHE; | |
204 | ||
205 | init.parent_names = &clk_conf->clks_parent[0]; | |
206 | init.num_parents = 1; | |
207 | init.ops = &meson_clk_pll_ro_ops; | |
208 | ||
209 | /* If no rate_table is specified we assume the PLL is read-only */ | |
210 | if (clk_pll->conf->rate_table) { | |
211 | int len; | |
212 | ||
213 | for (len = 0; clk_pll->conf->rate_table[len].rate != 0; ) | |
214 | len++; | |
215 | ||
216 | clk_pll->rate_count = len; | |
217 | init.ops = &meson_clk_pll_ops; | |
218 | } | |
219 | ||
220 | clk_pll->hw.init = &init; | |
221 | ||
222 | clk = clk_register(NULL, &clk_pll->hw); | |
223 | if (IS_ERR(clk)) | |
224 | kfree(clk_pll); | |
225 | ||
226 | return clk; | |
227 | } |