1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/export.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/err.h> |
11 | |
12 | #include "clk.h" |
13 | |
14 | static u8 clk_periph_get_parent(struct clk_hw *hw) |
15 | { |
16 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
17 | const struct clk_ops *mux_ops = periph->mux_ops; |
18 | struct clk_hw *mux_hw = &periph->mux.hw; |
19 | |
20 | __clk_hw_set_clk(dst: mux_hw, src: hw); |
21 | |
22 | return mux_ops->get_parent(mux_hw); |
23 | } |
24 | |
25 | static int clk_periph_set_parent(struct clk_hw *hw, u8 index) |
26 | { |
27 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
28 | const struct clk_ops *mux_ops = periph->mux_ops; |
29 | struct clk_hw *mux_hw = &periph->mux.hw; |
30 | |
31 | __clk_hw_set_clk(dst: mux_hw, src: hw); |
32 | |
33 | return mux_ops->set_parent(mux_hw, index); |
34 | } |
35 | |
36 | static unsigned long clk_periph_recalc_rate(struct clk_hw *hw, |
37 | unsigned long parent_rate) |
38 | { |
39 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
40 | const struct clk_ops *div_ops = periph->div_ops; |
41 | struct clk_hw *div_hw = &periph->divider.hw; |
42 | |
43 | __clk_hw_set_clk(dst: div_hw, src: hw); |
44 | |
45 | return div_ops->recalc_rate(div_hw, parent_rate); |
46 | } |
47 | |
48 | static int clk_periph_determine_rate(struct clk_hw *hw, |
49 | struct clk_rate_request *req) |
50 | { |
51 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
52 | const struct clk_ops *div_ops = periph->div_ops; |
53 | struct clk_hw *div_hw = &periph->divider.hw; |
54 | unsigned long rate; |
55 | |
56 | __clk_hw_set_clk(dst: div_hw, src: hw); |
57 | |
58 | rate = div_ops->round_rate(div_hw, req->rate, &req->best_parent_rate); |
59 | if (rate < 0) |
60 | return rate; |
61 | |
62 | req->rate = rate; |
63 | return 0; |
64 | } |
65 | |
66 | static int clk_periph_set_rate(struct clk_hw *hw, unsigned long rate, |
67 | unsigned long parent_rate) |
68 | { |
69 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
70 | const struct clk_ops *div_ops = periph->div_ops; |
71 | struct clk_hw *div_hw = &periph->divider.hw; |
72 | |
73 | __clk_hw_set_clk(dst: div_hw, src: hw); |
74 | |
75 | return div_ops->set_rate(div_hw, rate, parent_rate); |
76 | } |
77 | |
78 | static int clk_periph_is_enabled(struct clk_hw *hw) |
79 | { |
80 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
81 | const struct clk_ops *gate_ops = periph->gate_ops; |
82 | struct clk_hw *gate_hw = &periph->gate.hw; |
83 | |
84 | __clk_hw_set_clk(dst: gate_hw, src: hw); |
85 | |
86 | return gate_ops->is_enabled(gate_hw); |
87 | } |
88 | |
89 | static int clk_periph_enable(struct clk_hw *hw) |
90 | { |
91 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
92 | const struct clk_ops *gate_ops = periph->gate_ops; |
93 | struct clk_hw *gate_hw = &periph->gate.hw; |
94 | |
95 | __clk_hw_set_clk(dst: gate_hw, src: hw); |
96 | |
97 | return gate_ops->enable(gate_hw); |
98 | } |
99 | |
100 | static void clk_periph_disable(struct clk_hw *hw) |
101 | { |
102 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
103 | const struct clk_ops *gate_ops = periph->gate_ops; |
104 | struct clk_hw *gate_hw = &periph->gate.hw; |
105 | |
106 | gate_ops->disable(gate_hw); |
107 | } |
108 | |
109 | static void clk_periph_disable_unused(struct clk_hw *hw) |
110 | { |
111 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
112 | const struct clk_ops *gate_ops = periph->gate_ops; |
113 | struct clk_hw *gate_hw = &periph->gate.hw; |
114 | |
115 | gate_ops->disable_unused(gate_hw); |
116 | } |
117 | |
118 | static void clk_periph_restore_context(struct clk_hw *hw) |
119 | { |
120 | struct tegra_clk_periph *periph = to_clk_periph(hw); |
121 | const struct clk_ops *div_ops = periph->div_ops; |
122 | struct clk_hw *div_hw = &periph->divider.hw; |
123 | int parent_id; |
124 | |
125 | parent_id = clk_hw_get_parent_index(hw); |
126 | if (WARN_ON(parent_id < 0)) |
127 | return; |
128 | |
129 | if (!(periph->gate.flags & TEGRA_PERIPH_NO_DIV)) |
130 | div_ops->restore_context(div_hw); |
131 | |
132 | clk_periph_set_parent(hw, index: parent_id); |
133 | } |
134 | |
135 | const struct clk_ops tegra_clk_periph_ops = { |
136 | .get_parent = clk_periph_get_parent, |
137 | .set_parent = clk_periph_set_parent, |
138 | .recalc_rate = clk_periph_recalc_rate, |
139 | .determine_rate = clk_periph_determine_rate, |
140 | .set_rate = clk_periph_set_rate, |
141 | .is_enabled = clk_periph_is_enabled, |
142 | .enable = clk_periph_enable, |
143 | .disable = clk_periph_disable, |
144 | .disable_unused = clk_periph_disable_unused, |
145 | .restore_context = clk_periph_restore_context, |
146 | }; |
147 | |
148 | static const struct clk_ops tegra_clk_periph_nodiv_ops = { |
149 | .determine_rate = clk_hw_determine_rate_no_reparent, |
150 | .get_parent = clk_periph_get_parent, |
151 | .set_parent = clk_periph_set_parent, |
152 | .is_enabled = clk_periph_is_enabled, |
153 | .enable = clk_periph_enable, |
154 | .disable = clk_periph_disable, |
155 | .disable_unused = clk_periph_disable_unused, |
156 | .restore_context = clk_periph_restore_context, |
157 | }; |
158 | |
159 | static const struct clk_ops tegra_clk_periph_no_gate_ops = { |
160 | .get_parent = clk_periph_get_parent, |
161 | .set_parent = clk_periph_set_parent, |
162 | .recalc_rate = clk_periph_recalc_rate, |
163 | .determine_rate = clk_periph_determine_rate, |
164 | .set_rate = clk_periph_set_rate, |
165 | .restore_context = clk_periph_restore_context, |
166 | }; |
167 | |
168 | static struct clk *_tegra_clk_register_periph(const char *name, |
169 | const char * const *parent_names, int num_parents, |
170 | struct tegra_clk_periph *periph, |
171 | void __iomem *clk_base, u32 offset, |
172 | unsigned long flags) |
173 | { |
174 | struct clk *clk; |
175 | struct clk_init_data init; |
176 | const struct tegra_clk_periph_regs *bank; |
177 | bool div = !(periph->gate.flags & TEGRA_PERIPH_NO_DIV); |
178 | |
179 | if (periph->gate.flags & TEGRA_PERIPH_NO_DIV) { |
180 | flags |= CLK_SET_RATE_PARENT; |
181 | init.ops = &tegra_clk_periph_nodiv_ops; |
182 | } else if (periph->gate.flags & TEGRA_PERIPH_NO_GATE) |
183 | init.ops = &tegra_clk_periph_no_gate_ops; |
184 | else |
185 | init.ops = &tegra_clk_periph_ops; |
186 | |
187 | init.name = name; |
188 | init.flags = flags; |
189 | init.parent_names = parent_names; |
190 | init.num_parents = num_parents; |
191 | |
192 | bank = get_reg_bank(clkid: periph->gate.clk_num); |
193 | if (!bank) |
194 | return ERR_PTR(error: -EINVAL); |
195 | |
196 | /* Data in .init is copied by clk_register(), so stack variable OK */ |
197 | periph->hw.init = &init; |
198 | periph->magic = TEGRA_CLK_PERIPH_MAGIC; |
199 | periph->mux.reg = clk_base + offset; |
200 | periph->divider.reg = div ? (clk_base + offset) : NULL; |
201 | periph->gate.clk_base = clk_base; |
202 | periph->gate.regs = bank; |
203 | periph->gate.enable_refcnt = periph_clk_enb_refcnt; |
204 | |
205 | clk = clk_register(NULL, hw: &periph->hw); |
206 | if (IS_ERR(ptr: clk)) |
207 | return clk; |
208 | |
209 | periph->mux.hw.clk = clk; |
210 | periph->divider.hw.clk = div ? clk : NULL; |
211 | periph->gate.hw.clk = clk; |
212 | |
213 | return clk; |
214 | } |
215 | |
216 | struct clk *tegra_clk_register_periph(const char *name, |
217 | const char * const *parent_names, int num_parents, |
218 | struct tegra_clk_periph *periph, void __iomem *clk_base, |
219 | u32 offset, unsigned long flags) |
220 | { |
221 | return _tegra_clk_register_periph(name, parent_names, num_parents, |
222 | periph, clk_base, offset, flags); |
223 | } |
224 | |
225 | struct clk *tegra_clk_register_periph_nodiv(const char *name, |
226 | const char * const *parent_names, int num_parents, |
227 | struct tegra_clk_periph *periph, void __iomem *clk_base, |
228 | u32 offset) |
229 | { |
230 | periph->gate.flags |= TEGRA_PERIPH_NO_DIV; |
231 | return _tegra_clk_register_periph(name, parent_names, num_parents, |
232 | periph, clk_base, offset, CLK_SET_RATE_PARENT); |
233 | } |
234 | |
235 | struct clk *tegra_clk_register_periph_data(void __iomem *clk_base, |
236 | struct tegra_periph_init_data *init) |
237 | { |
238 | return _tegra_clk_register_periph(name: init->name, parent_names: init->p.parent_names, |
239 | num_parents: init->num_parents, periph: &init->periph, |
240 | clk_base, offset: init->offset, flags: init->flags); |
241 | } |
242 | |