1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2012, 2013, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/io.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/of.h> |
9 | #include <linux/of_address.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/export.h> |
12 | #include <linux/clk/tegra.h> |
13 | |
14 | #include "clk.h" |
15 | #include "clk-id.h" |
16 | |
17 | #define AUDIO_SYNC_CLK_I2S0 0x4a0 |
18 | #define AUDIO_SYNC_CLK_I2S1 0x4a4 |
19 | #define AUDIO_SYNC_CLK_I2S2 0x4a8 |
20 | #define AUDIO_SYNC_CLK_I2S3 0x4ac |
21 | #define AUDIO_SYNC_CLK_I2S4 0x4b0 |
22 | #define AUDIO_SYNC_CLK_SPDIF 0x4b4 |
23 | #define AUDIO_SYNC_CLK_DMIC1 0x560 |
24 | #define AUDIO_SYNC_CLK_DMIC2 0x564 |
25 | #define AUDIO_SYNC_CLK_DMIC3 0x6b8 |
26 | |
27 | #define AUDIO_SYNC_DOUBLER 0x49c |
28 | |
29 | #define PLLA_OUT 0xb4 |
30 | |
31 | struct tegra_sync_source_initdata { |
32 | char *name; |
33 | unsigned long rate; |
34 | unsigned long max_rate; |
35 | int clk_id; |
36 | }; |
37 | |
38 | #define SYNC(_name) \ |
39 | {\ |
40 | .name = #_name,\ |
41 | .clk_id = tegra_clk_ ## _name,\ |
42 | } |
43 | |
44 | struct tegra_audio_clk_initdata { |
45 | char *gate_name; |
46 | char *mux_name; |
47 | u32 offset; |
48 | int gate_clk_id; |
49 | int mux_clk_id; |
50 | }; |
51 | |
52 | #define AUDIO(_name, _offset) \ |
53 | {\ |
54 | .gate_name = #_name,\ |
55 | .mux_name = #_name"_mux",\ |
56 | .offset = _offset,\ |
57 | .gate_clk_id = tegra_clk_ ## _name,\ |
58 | .mux_clk_id = tegra_clk_ ## _name ## _mux,\ |
59 | } |
60 | |
61 | struct tegra_audio2x_clk_initdata { |
62 | char *parent; |
63 | char *gate_name; |
64 | char *name_2x; |
65 | char *div_name; |
66 | int clk_id; |
67 | int clk_num; |
68 | u8 div_offset; |
69 | }; |
70 | |
71 | #define AUDIO2X(_name, _num, _offset) \ |
72 | {\ |
73 | .parent = #_name,\ |
74 | .gate_name = #_name"_2x",\ |
75 | .name_2x = #_name"_doubler",\ |
76 | .div_name = #_name"_div",\ |
77 | .clk_id = tegra_clk_ ## _name ## _2x,\ |
78 | .clk_num = _num,\ |
79 | .div_offset = _offset,\ |
80 | } |
81 | |
82 | static DEFINE_SPINLOCK(clk_doubler_lock); |
83 | |
84 | static const char * const mux_audio_sync_clk[] = { "spdif_in_sync" , |
85 | "i2s0_sync" , "i2s1_sync" , "i2s2_sync" , "i2s3_sync" , "i2s4_sync" , |
86 | "pll_a_out0" , "vimclk_sync" , |
87 | }; |
88 | |
89 | static const char * const mux_dmic_sync_clk[] = { "unused" , "i2s0_sync" , |
90 | "i2s1_sync" , "i2s2_sync" , "i2s3_sync" , "i2s4_sync" , "pll_a_out0" , |
91 | "vimclk_sync" , |
92 | }; |
93 | |
94 | static struct tegra_sync_source_initdata sync_source_clks[] __initdata = { |
95 | SYNC(spdif_in_sync), |
96 | SYNC(i2s0_sync), |
97 | SYNC(i2s1_sync), |
98 | SYNC(i2s2_sync), |
99 | SYNC(i2s3_sync), |
100 | SYNC(i2s4_sync), |
101 | SYNC(vimclk_sync), |
102 | }; |
103 | |
104 | static struct tegra_audio_clk_initdata audio_clks[] = { |
105 | AUDIO(audio0, AUDIO_SYNC_CLK_I2S0), |
106 | AUDIO(audio1, AUDIO_SYNC_CLK_I2S1), |
107 | AUDIO(audio2, AUDIO_SYNC_CLK_I2S2), |
108 | AUDIO(audio3, AUDIO_SYNC_CLK_I2S3), |
109 | AUDIO(audio4, AUDIO_SYNC_CLK_I2S4), |
110 | AUDIO(spdif, AUDIO_SYNC_CLK_SPDIF), |
111 | }; |
112 | |
113 | static struct tegra_audio_clk_initdata dmic_clks[] = { |
114 | AUDIO(dmic1_sync_clk, AUDIO_SYNC_CLK_DMIC1), |
115 | AUDIO(dmic2_sync_clk, AUDIO_SYNC_CLK_DMIC2), |
116 | AUDIO(dmic3_sync_clk, AUDIO_SYNC_CLK_DMIC3), |
117 | }; |
118 | |
119 | static struct tegra_audio2x_clk_initdata audio2x_clks[] = { |
120 | AUDIO2X(audio0, 113, 24), |
121 | AUDIO2X(audio1, 114, 25), |
122 | AUDIO2X(audio2, 115, 26), |
123 | AUDIO2X(audio3, 116, 27), |
124 | AUDIO2X(audio4, 117, 28), |
125 | AUDIO2X(spdif, 118, 29), |
126 | }; |
127 | |
128 | static void __init tegra_audio_sync_clk_init(void __iomem *clk_base, |
129 | struct tegra_clk *tegra_clks, |
130 | struct tegra_audio_clk_initdata *sync, |
131 | int num_sync_clks, |
132 | const char * const *mux_names, |
133 | int num_mux_inputs) |
134 | { |
135 | struct clk *clk; |
136 | struct clk **dt_clk; |
137 | struct tegra_audio_clk_initdata *data; |
138 | int i; |
139 | |
140 | for (i = 0, data = sync; i < num_sync_clks; i++, data++) { |
141 | dt_clk = tegra_lookup_dt_id(clk_id: data->mux_clk_id, tegra_clk: tegra_clks); |
142 | if (!dt_clk) |
143 | continue; |
144 | |
145 | clk = clk_register_mux(NULL, data->mux_name, mux_names, |
146 | num_mux_inputs, |
147 | CLK_SET_RATE_NO_REPARENT, |
148 | clk_base + data->offset, 0, 3, 0, |
149 | NULL); |
150 | *dt_clk = clk; |
151 | |
152 | dt_clk = tegra_lookup_dt_id(clk_id: data->gate_clk_id, tegra_clk: tegra_clks); |
153 | if (!dt_clk) |
154 | continue; |
155 | |
156 | clk = clk_register_gate(NULL, name: data->gate_name, parent_name: data->mux_name, |
157 | flags: 0, reg: clk_base + data->offset, bit_idx: 4, |
158 | CLK_GATE_SET_TO_DISABLE, NULL); |
159 | *dt_clk = clk; |
160 | } |
161 | } |
162 | |
163 | void __init tegra_audio_clk_init(void __iomem *clk_base, |
164 | void __iomem *pmc_base, struct tegra_clk *tegra_clks, |
165 | struct tegra_audio_clk_info *audio_info, |
166 | unsigned int num_plls, unsigned long sync_max_rate) |
167 | { |
168 | struct clk *clk; |
169 | struct clk **dt_clk; |
170 | int i; |
171 | |
172 | if (!audio_info || num_plls < 1) { |
173 | pr_err("No audio data passed to tegra_audio_clk_init\n" ); |
174 | WARN_ON(1); |
175 | return; |
176 | } |
177 | |
178 | for (i = 0; i < num_plls; i++) { |
179 | struct tegra_audio_clk_info *info = &audio_info[i]; |
180 | |
181 | dt_clk = tegra_lookup_dt_id(clk_id: info->clk_id, tegra_clk: tegra_clks); |
182 | if (dt_clk) { |
183 | clk = tegra_clk_register_pll(name: info->name, parent_name: info->parent, |
184 | clk_base, pmc: pmc_base, flags: 0, pll_params: info->pll_params, |
185 | NULL); |
186 | *dt_clk = clk; |
187 | } |
188 | } |
189 | |
190 | /* PLLA_OUT0 */ |
191 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_pll_a_out0, tegra_clk: tegra_clks); |
192 | if (dt_clk) { |
193 | clk = tegra_clk_register_divider(name: "pll_a_out0_div" , parent_name: "pll_a" , |
194 | reg: clk_base + PLLA_OUT, flags: 0, TEGRA_DIVIDER_ROUND_UP, |
195 | shift: 8, width: 8, frac_width: 1, NULL); |
196 | clk = tegra_clk_register_pll_out(name: "pll_a_out0" , parent_name: "pll_a_out0_div" , |
197 | reg: clk_base + PLLA_OUT, enb_bit_idx: 1, rst_bit_idx: 0, CLK_IGNORE_UNUSED | |
198 | CLK_SET_RATE_PARENT, pll_div_flags: 0, NULL); |
199 | *dt_clk = clk; |
200 | } |
201 | |
202 | for (i = 0; i < ARRAY_SIZE(sync_source_clks); i++) { |
203 | struct tegra_sync_source_initdata *data; |
204 | |
205 | data = &sync_source_clks[i]; |
206 | |
207 | dt_clk = tegra_lookup_dt_id(clk_id: data->clk_id, tegra_clk: tegra_clks); |
208 | if (!dt_clk) |
209 | continue; |
210 | |
211 | clk = tegra_clk_register_sync_source(name: data->name, max_rate: sync_max_rate); |
212 | *dt_clk = clk; |
213 | } |
214 | |
215 | tegra_audio_sync_clk_init(clk_base, tegra_clks, sync: audio_clks, |
216 | ARRAY_SIZE(audio_clks), mux_names: mux_audio_sync_clk, |
217 | ARRAY_SIZE(mux_audio_sync_clk)); |
218 | |
219 | /* make sure the DMIC sync clocks have a valid parent */ |
220 | for (i = 0; i < ARRAY_SIZE(dmic_clks); i++) |
221 | writel_relaxed(1, clk_base + dmic_clks[i].offset); |
222 | |
223 | tegra_audio_sync_clk_init(clk_base, tegra_clks, sync: dmic_clks, |
224 | ARRAY_SIZE(dmic_clks), mux_names: mux_dmic_sync_clk, |
225 | ARRAY_SIZE(mux_dmic_sync_clk)); |
226 | |
227 | for (i = 0; i < ARRAY_SIZE(audio2x_clks); i++) { |
228 | struct tegra_audio2x_clk_initdata *data; |
229 | |
230 | data = &audio2x_clks[i]; |
231 | dt_clk = tegra_lookup_dt_id(clk_id: data->clk_id, tegra_clk: tegra_clks); |
232 | if (!dt_clk) |
233 | continue; |
234 | |
235 | clk = clk_register_fixed_factor(NULL, name: data->name_2x, |
236 | parent_name: data->parent, CLK_SET_RATE_PARENT, mult: 2, div: 1); |
237 | clk = tegra_clk_register_divider(name: data->div_name, |
238 | parent_name: data->name_2x, reg: clk_base + AUDIO_SYNC_DOUBLER, |
239 | flags: 0, clk_divider_flags: 0, shift: data->div_offset, width: 1, frac_width: 0, |
240 | lock: &clk_doubler_lock); |
241 | clk = tegra_clk_register_periph_gate(name: data->gate_name, |
242 | parent_name: data->div_name, TEGRA_PERIPH_NO_RESET, |
243 | clk_base, CLK_SET_RATE_PARENT, clk_num: data->clk_num, |
244 | enable_refcnt: periph_clk_enb_refcnt); |
245 | *dt_clk = clk; |
246 | } |
247 | } |
248 | |
249 | |