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> |
15 | #include <linux/io.h> |
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, addr: 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(rate: req->rate, parent_rate: output_rate, width: 8, frac_width: 1, flags: 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, width: 8, frac_width: 1, flags: 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, addr: sdmmc_mux->reg); |
158 | fence_udelay(2, sdmmc_mux->reg); |
159 | |
160 | if (sdmmc_mux->lock) |
161 | spin_unlock_irqrestore(lock: 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(dst: gate_hw, src: 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(dst: gate_hw, src: 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 | |
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 | |
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(hw: 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, index: parent_id); |
218 | clk_sdmmc_mux_set_rate(hw, rate, parent_rate); |
219 | } |
220 | |
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, |
230 | .disable_unused = clk_sdmmc_mux_disable_unused, |
231 | .restore_context = clk_sdmmc_mux_restore_context, |
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(clkid: clk_num); |
250 | if (!bank) |
251 | return ERR_PTR(error: -EINVAL); |
252 | |
253 | sdmmc_mux = kzalloc(size: sizeof(*sdmmc_mux), GFP_KERNEL); |
254 | if (!sdmmc_mux) |
255 | return ERR_PTR(error: -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, hw: &sdmmc_mux->hw); |
270 | if (IS_ERR(ptr: clk)) { |
271 | kfree(objp: sdmmc_mux); |
272 | return clk; |
273 | } |
274 | |
275 | sdmmc_mux->gate.hw.clk = clk; |
276 | |
277 | return clk; |
278 | } |
279 | |