]>
Commit | Line | Data |
---|---|---|
633e7965 PDS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2018 NVIDIA CORPORATION. All rights reserved. | |
4 | * | |
5 | * based on clk-mux.c | |
6 | * | |
7 | * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> | |
8 | * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org> | |
9 | * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org> | |
10 | * | |
11 | */ | |
12 | ||
13 | #include <linux/clk-provider.h> | |
14 | #include <linux/err.h> | |
62e59c4e | 15 | #include <linux/io.h> |
633e7965 PDS |
16 | #include <linux/types.h> |
17 | ||
18 | #include "clk.h" | |
19 | ||
20 | #define DIV_MASK GENMASK(7, 0) | |
21 | #define MUX_SHIFT 29 | |
22 | #define MUX_MASK GENMASK(MUX_SHIFT + 2, MUX_SHIFT) | |
23 | #define SDMMC_MUL 2 | |
24 | ||
25 | #define get_max_div(d) DIV_MASK | |
26 | #define get_div_field(val) ((val) & DIV_MASK) | |
27 | #define get_mux_field(val) (((val) & MUX_MASK) >> MUX_SHIFT) | |
28 | ||
29 | static const char * const mux_sdmmc_parents[] = { | |
30 | "pll_p", "pll_c4_out2", "pll_c4_out0", "pll_c4_out1", "clk_m" | |
31 | }; | |
32 | ||
33 | static const u8 mux_lj_idx[] = { | |
34 | [0] = 0, [1] = 1, [2] = 2, [3] = 5, [4] = 6 | |
35 | }; | |
36 | ||
37 | static const u8 mux_non_lj_idx[] = { | |
38 | [0] = 0, [1] = 3, [2] = 7, [3] = 4, [4] = 6 | |
39 | }; | |
40 | ||
41 | static u8 clk_sdmmc_mux_get_parent(struct clk_hw *hw) | |
42 | { | |
43 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
44 | int num_parents, i; | |
45 | u32 src, val; | |
46 | const u8 *mux_idx; | |
47 | ||
48 | num_parents = clk_hw_get_num_parents(hw); | |
49 | ||
50 | val = readl_relaxed(sdmmc_mux->reg); | |
51 | src = get_mux_field(val); | |
52 | if (get_div_field(val)) | |
53 | mux_idx = mux_non_lj_idx; | |
54 | else | |
55 | mux_idx = mux_lj_idx; | |
56 | ||
57 | for (i = 0; i < num_parents; i++) { | |
58 | if (mux_idx[i] == src) | |
59 | return i; | |
60 | } | |
61 | ||
62 | WARN(1, "Unknown parent selector %d\n", src); | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | static int clk_sdmmc_mux_set_parent(struct clk_hw *hw, u8 index) | |
68 | { | |
69 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
70 | u32 val; | |
71 | ||
72 | ||
73 | val = readl_relaxed(sdmmc_mux->reg); | |
74 | if (get_div_field(val)) | |
75 | index = mux_non_lj_idx[index]; | |
76 | else | |
77 | index = mux_lj_idx[index]; | |
78 | ||
79 | val &= ~MUX_MASK; | |
80 | val |= index << MUX_SHIFT; | |
81 | ||
82 | writel(val, sdmmc_mux->reg); | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | static unsigned long clk_sdmmc_mux_recalc_rate(struct clk_hw *hw, | |
88 | unsigned long parent_rate) | |
89 | { | |
90 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
91 | u32 val; | |
92 | int div; | |
93 | u64 rate = parent_rate; | |
94 | ||
95 | val = readl_relaxed(sdmmc_mux->reg); | |
96 | div = get_div_field(val); | |
97 | ||
98 | div += SDMMC_MUL; | |
99 | ||
100 | rate *= SDMMC_MUL; | |
101 | rate += div - 1; | |
102 | do_div(rate, div); | |
103 | ||
104 | return rate; | |
105 | } | |
106 | ||
107 | static int clk_sdmmc_mux_determine_rate(struct clk_hw *hw, | |
108 | struct clk_rate_request *req) | |
109 | { | |
110 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
111 | int div; | |
112 | unsigned long output_rate = req->best_parent_rate; | |
113 | ||
114 | req->rate = max(req->rate, req->min_rate); | |
115 | req->rate = min(req->rate, req->max_rate); | |
116 | ||
117 | if (!req->rate) | |
118 | return output_rate; | |
119 | ||
120 | div = div_frac_get(req->rate, output_rate, 8, 1, sdmmc_mux->div_flags); | |
121 | if (div < 0) | |
122 | div = 0; | |
123 | ||
124 | if (sdmmc_mux->div_flags & TEGRA_DIVIDER_ROUND_UP) | |
125 | req->rate = DIV_ROUND_UP(output_rate * SDMMC_MUL, | |
126 | div + SDMMC_MUL); | |
127 | else | |
128 | req->rate = output_rate * SDMMC_MUL / (div + SDMMC_MUL); | |
129 | ||
130 | return 0; | |
131 | } | |
132 | ||
133 | static int clk_sdmmc_mux_set_rate(struct clk_hw *hw, unsigned long rate, | |
134 | unsigned long parent_rate) | |
135 | { | |
136 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
137 | int div; | |
138 | unsigned long flags = 0; | |
139 | u32 val; | |
140 | u8 src; | |
141 | ||
142 | div = div_frac_get(rate, parent_rate, 8, 1, sdmmc_mux->div_flags); | |
143 | if (div < 0) | |
144 | return div; | |
145 | ||
146 | if (sdmmc_mux->lock) | |
147 | spin_lock_irqsave(sdmmc_mux->lock, flags); | |
148 | ||
149 | src = clk_sdmmc_mux_get_parent(hw); | |
150 | if (div) | |
151 | src = mux_non_lj_idx[src]; | |
152 | else | |
153 | src = mux_lj_idx[src]; | |
154 | ||
155 | val = src << MUX_SHIFT; | |
156 | val |= div; | |
157 | writel(val, sdmmc_mux->reg); | |
158 | fence_udelay(2, sdmmc_mux->reg); | |
159 | ||
160 | if (sdmmc_mux->lock) | |
161 | spin_unlock_irqrestore(sdmmc_mux->lock, flags); | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | static int clk_sdmmc_mux_is_enabled(struct clk_hw *hw) | |
167 | { | |
168 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
169 | const struct clk_ops *gate_ops = sdmmc_mux->gate_ops; | |
170 | struct clk_hw *gate_hw = &sdmmc_mux->gate.hw; | |
171 | ||
172 | __clk_hw_set_clk(gate_hw, hw); | |
173 | ||
174 | return gate_ops->is_enabled(gate_hw); | |
175 | } | |
176 | ||
177 | static int clk_sdmmc_mux_enable(struct clk_hw *hw) | |
178 | { | |
179 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
180 | const struct clk_ops *gate_ops = sdmmc_mux->gate_ops; | |
181 | struct clk_hw *gate_hw = &sdmmc_mux->gate.hw; | |
182 | ||
183 | __clk_hw_set_clk(gate_hw, hw); | |
184 | ||
185 | return gate_ops->enable(gate_hw); | |
186 | } | |
187 | ||
188 | static void clk_sdmmc_mux_disable(struct clk_hw *hw) | |
189 | { | |
190 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
191 | const struct clk_ops *gate_ops = sdmmc_mux->gate_ops; | |
192 | struct clk_hw *gate_hw = &sdmmc_mux->gate.hw; | |
193 | ||
194 | gate_ops->disable(gate_hw); | |
195 | } | |
196 | ||
2bcc025a DO |
197 | static void clk_sdmmc_mux_disable_unused(struct clk_hw *hw) |
198 | { | |
199 | struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw); | |
200 | const struct clk_ops *gate_ops = sdmmc_mux->gate_ops; | |
201 | struct clk_hw *gate_hw = &sdmmc_mux->gate.hw; | |
202 | ||
203 | gate_ops->disable_unused(gate_hw); | |
204 | } | |
205 | ||
2b8cfd6b SK |
206 | static void clk_sdmmc_mux_restore_context(struct clk_hw *hw) |
207 | { | |
208 | struct clk_hw *parent = clk_hw_get_parent(hw); | |
209 | unsigned long parent_rate = clk_hw_get_rate(parent); | |
210 | unsigned long rate = clk_hw_get_rate(hw); | |
211 | int parent_id; | |
212 | ||
213 | parent_id = clk_hw_get_parent_index(hw); | |
214 | if (WARN_ON(parent_id < 0)) | |
215 | return; | |
216 | ||
217 | clk_sdmmc_mux_set_parent(hw, parent_id); | |
218 | clk_sdmmc_mux_set_rate(hw, rate, parent_rate); | |
219 | } | |
220 | ||
633e7965 PDS |
221 | static const struct clk_ops tegra_clk_sdmmc_mux_ops = { |
222 | .get_parent = clk_sdmmc_mux_get_parent, | |
223 | .set_parent = clk_sdmmc_mux_set_parent, | |
224 | .determine_rate = clk_sdmmc_mux_determine_rate, | |
225 | .recalc_rate = clk_sdmmc_mux_recalc_rate, | |
226 | .set_rate = clk_sdmmc_mux_set_rate, | |
227 | .is_enabled = clk_sdmmc_mux_is_enabled, | |
228 | .enable = clk_sdmmc_mux_enable, | |
229 | .disable = clk_sdmmc_mux_disable, | |
2bcc025a | 230 | .disable_unused = clk_sdmmc_mux_disable_unused, |
2b8cfd6b | 231 | .restore_context = clk_sdmmc_mux_restore_context, |
633e7965 PDS |
232 | }; |
233 | ||
234 | struct clk *tegra_clk_register_sdmmc_mux_div(const char *name, | |
235 | void __iomem *clk_base, u32 offset, u32 clk_num, u8 div_flags, | |
236 | unsigned long flags, void *lock) | |
237 | { | |
238 | struct clk *clk; | |
239 | struct clk_init_data init; | |
240 | const struct tegra_clk_periph_regs *bank; | |
241 | struct tegra_sdmmc_mux *sdmmc_mux; | |
242 | ||
243 | init.ops = &tegra_clk_sdmmc_mux_ops; | |
244 | init.name = name; | |
245 | init.flags = flags; | |
246 | init.parent_names = mux_sdmmc_parents; | |
247 | init.num_parents = ARRAY_SIZE(mux_sdmmc_parents); | |
248 | ||
249 | bank = get_reg_bank(clk_num); | |
250 | if (!bank) | |
251 | return ERR_PTR(-EINVAL); | |
252 | ||
253 | sdmmc_mux = kzalloc(sizeof(*sdmmc_mux), GFP_KERNEL); | |
254 | if (!sdmmc_mux) | |
255 | return ERR_PTR(-ENOMEM); | |
256 | ||
257 | /* Data in .init is copied by clk_register(), so stack variable OK */ | |
258 | sdmmc_mux->hw.init = &init; | |
259 | sdmmc_mux->reg = clk_base + offset; | |
260 | sdmmc_mux->lock = lock; | |
261 | sdmmc_mux->gate.clk_base = clk_base; | |
262 | sdmmc_mux->gate.regs = bank; | |
263 | sdmmc_mux->gate.enable_refcnt = periph_clk_enb_refcnt; | |
264 | sdmmc_mux->gate.clk_num = clk_num; | |
265 | sdmmc_mux->gate.flags = TEGRA_PERIPH_ON_APB; | |
266 | sdmmc_mux->div_flags = div_flags; | |
267 | sdmmc_mux->gate_ops = &tegra_clk_periph_gate_ops; | |
268 | ||
269 | clk = clk_register(NULL, &sdmmc_mux->hw); | |
270 | if (IS_ERR(clk)) { | |
271 | kfree(sdmmc_mux); | |
272 | return clk; | |
273 | } | |
274 | ||
275 | sdmmc_mux->gate.hw.clk = clk; | |
276 | ||
277 | return clk; | |
278 | } |