]>
Commit | Line | Data |
---|---|---|
1e0edd4d KM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // Renesas R-Car SSIU support | |
4 | // | |
5 | // Copyright (c) 2015 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
6 | ||
c7f69ab5 KM |
7 | #include "rsnd.h" |
8 | ||
9 | #define SSIU_NAME "ssiu" | |
10 | ||
11 | struct rsnd_ssiu { | |
12 | struct rsnd_mod mod; | |
2e66d523 JW |
13 | u32 busif_status[8]; /* for BUSIF0 - BUSIF7 */ |
14 | unsigned int usrcnt; | |
4e7788fb KM |
15 | int id; |
16 | int id_sub; | |
c7f69ab5 KM |
17 | }; |
18 | ||
f69f4522 KM |
19 | /* SSI_MODE */ |
20 | #define TDM_EXT (1 << 0) | |
21 | #define TDM_SPLIT (1 << 8) | |
22 | ||
c7f69ab5 | 23 | #define rsnd_ssiu_nr(priv) ((priv)->ssiu_nr) |
2e66d523 | 24 | #define rsnd_mod_to_ssiu(_mod) container_of((_mod), struct rsnd_ssiu, mod) |
c7f69ab5 KM |
25 | #define for_each_rsnd_ssiu(pos, priv, i) \ |
26 | for (i = 0; \ | |
27 | (i < rsnd_ssiu_nr(priv)) && \ | |
28 | ((pos) = ((struct rsnd_ssiu *)(priv)->ssiu + i)); \ | |
29 | i++) | |
30 | ||
4e7788fb KM |
31 | /* |
32 | * SSI Gen2 Gen3 | |
33 | * 0 BUSIF0-3 BUSIF0-7 | |
34 | * 1 BUSIF0-3 BUSIF0-7 | |
35 | * 2 BUSIF0-3 BUSIF0-7 | |
36 | * 3 BUSIF0 BUSIF0-7 | |
37 | * 4 BUSIF0 BUSIF0-7 | |
38 | * 5 BUSIF0 BUSIF0 | |
39 | * 6 BUSIF0 BUSIF0 | |
40 | * 7 BUSIF0 BUSIF0 | |
41 | * 8 BUSIF0 BUSIF0 | |
42 | * 9 BUSIF0-3 BUSIF0-7 | |
43 | * total 22 52 | |
44 | */ | |
45 | static const int gen2_id[] = { 0, 4, 8, 12, 13, 14, 15, 16, 17, 18 }; | |
46 | static const int gen3_id[] = { 0, 8, 16, 24, 32, 40, 41, 42, 43, 44 }; | |
47 | ||
7e7fe06d KM |
48 | static u32 *rsnd_ssiu_get_status(struct rsnd_mod *mod, |
49 | struct rsnd_dai_stream *io, | |
50 | enum rsnd_mod_type type) | |
51 | { | |
52 | struct rsnd_ssiu *ssiu = rsnd_mod_to_ssiu(mod); | |
4e7788fb | 53 | int busif = rsnd_mod_id_sub(mod); |
7e7fe06d KM |
54 | |
55 | return &ssiu->busif_status[busif]; | |
56 | } | |
57 | ||
c7f69ab5 KM |
58 | static int rsnd_ssiu_init(struct rsnd_mod *mod, |
59 | struct rsnd_dai_stream *io, | |
60 | struct rsnd_priv *priv) | |
61 | { | |
62 | struct rsnd_dai *rdai = rsnd_io_to_rdai(io); | |
dfea7b2c | 63 | u32 ssis = rsnd_ssi_multi_slaves_runtime(io); |
c7f69ab5 KM |
64 | int use_busif = rsnd_ssi_use_busif(io); |
65 | int id = rsnd_mod_id(mod); | |
dfea7b2c KM |
66 | int is_clk_master = rsnd_rdai_is_clk_master(rdai); |
67 | u32 val1, val2; | |
b7169dde | 68 | int i; |
c7f69ab5 | 69 | |
814efe3e KM |
70 | /* clear status */ |
71 | switch (id) { | |
72 | case 0: | |
73 | case 1: | |
74 | case 2: | |
75 | case 3: | |
76 | case 4: | |
b7169dde KM |
77 | for (i = 0; i < 4; i++) |
78 | rsnd_mod_write(mod, SSI_SYS_STATUS(i * 2), 0xf << (id * 4)); | |
814efe3e KM |
79 | break; |
80 | case 9: | |
b7169dde | 81 | for (i = 0; i < 4; i++) |
76379dfb | 82 | rsnd_mod_write(mod, SSI_SYS_STATUS((i * 2) + 1), 0xf << 4); |
814efe3e KM |
83 | break; |
84 | } | |
85 | ||
c7f69ab5 KM |
86 | /* |
87 | * SSI_MODE0 | |
88 | */ | |
89 | rsnd_mod_bset(mod, SSI_MODE0, (1 << id), !use_busif << id); | |
90 | ||
91 | /* | |
dfea7b2c KM |
92 | * SSI_MODE1 / SSI_MODE2 |
93 | * | |
94 | * FIXME | |
95 | * sharing/multi with SSI0 are mainly supported | |
c7f69ab5 | 96 | */ |
dfea7b2c KM |
97 | val1 = rsnd_mod_read(mod, SSI_MODE1); |
98 | val2 = rsnd_mod_read(mod, SSI_MODE2); | |
99 | if (rsnd_ssi_is_pin_sharing(io)) { | |
100 | ||
101 | ssis |= (1 << id); | |
102 | ||
103 | } else if (ssis) { | |
4b30eebf | 104 | /* |
dfea7b2c KM |
105 | * Multi SSI |
106 | * | |
107 | * set synchronized bit here | |
4b30eebf | 108 | */ |
dfea7b2c KM |
109 | |
110 | /* SSI4 is synchronized with SSI3 */ | |
111 | if (ssis & (1 << 4)) | |
112 | val1 |= (1 << 20); | |
113 | /* SSI012 are synchronized */ | |
114 | if (ssis == 0x0006) | |
115 | val1 |= (1 << 4); | |
116 | /* SSI0129 are synchronized */ | |
117 | if (ssis == 0x0206) | |
118 | val2 |= (1 << 4); | |
c7f69ab5 KM |
119 | } |
120 | ||
dfea7b2c KM |
121 | /* SSI1 is sharing pin with SSI0 */ |
122 | if (ssis & (1 << 1)) | |
123 | val1 |= is_clk_master ? 0x2 : 0x1; | |
124 | ||
125 | /* SSI2 is sharing pin with SSI0 */ | |
126 | if (ssis & (1 << 2)) | |
127 | val1 |= is_clk_master ? 0x2 << 2 : | |
128 | 0x1 << 2; | |
129 | /* SSI4 is sharing pin with SSI3 */ | |
130 | if (ssis & (1 << 4)) | |
131 | val1 |= is_clk_master ? 0x2 << 16 : | |
132 | 0x1 << 16; | |
133 | /* SSI9 is sharing pin with SSI0 */ | |
134 | if (ssis & (1 << 9)) | |
135 | val2 |= is_clk_master ? 0x2 : 0x1; | |
136 | ||
137 | rsnd_mod_bset(mod, SSI_MODE1, 0x0013001f, val1); | |
138 | rsnd_mod_bset(mod, SSI_MODE2, 0x00000017, val2); | |
b4c83b17 | 139 | |
c7f69ab5 KM |
140 | return 0; |
141 | } | |
142 | ||
143 | static struct rsnd_mod_ops rsnd_ssiu_ops_gen1 = { | |
7e7fe06d KM |
144 | .name = SSIU_NAME, |
145 | .init = rsnd_ssiu_init, | |
146 | .get_status = rsnd_ssiu_get_status, | |
c7f69ab5 KM |
147 | }; |
148 | ||
149 | static int rsnd_ssiu_init_gen2(struct rsnd_mod *mod, | |
150 | struct rsnd_dai_stream *io, | |
151 | struct rsnd_priv *priv) | |
152 | { | |
2e66d523 | 153 | struct rsnd_ssiu *ssiu = rsnd_mod_to_ssiu(mod); |
beed78ae KM |
154 | u32 has_hdmi0 = rsnd_flags_has(io, RSND_STREAM_HDMI0); |
155 | u32 has_hdmi1 = rsnd_flags_has(io, RSND_STREAM_HDMI1); | |
c7f69ab5 | 156 | int ret; |
a91d7fb9 | 157 | u32 mode = 0; |
c7f69ab5 KM |
158 | |
159 | ret = rsnd_ssiu_init(mod, io, priv); | |
160 | if (ret < 0) | |
161 | return ret; | |
162 | ||
2e66d523 JW |
163 | ssiu->usrcnt++; |
164 | ||
f69f4522 KM |
165 | /* |
166 | * TDM Extend/Split Mode | |
167 | * see | |
168 | * rsnd_ssi_config_init() | |
169 | */ | |
170 | if (rsnd_runtime_is_tdm(io)) | |
171 | mode = TDM_EXT; | |
172 | else if (rsnd_runtime_is_tdm_split(io)) | |
173 | mode = TDM_SPLIT; | |
186fadc1 | 174 | |
a91d7fb9 JW |
175 | rsnd_mod_write(mod, SSI_MODE, mode); |
176 | ||
c7f69ab5 | 177 | if (rsnd_ssi_use_busif(io)) { |
8c9d7503 | 178 | int id = rsnd_mod_id(mod); |
4e7788fb | 179 | int busif = rsnd_mod_id_sub(mod); |
8af6c521 | 180 | enum rsnd_reg adinr_reg, mode_reg, dalign_reg; |
8c9d7503 | 181 | |
8c9d7503 | 182 | if ((id == 9) && (busif >= 4)) { |
8af6c521 JW |
183 | adinr_reg = SSI9_BUSIF_ADINR(busif); |
184 | mode_reg = SSI9_BUSIF_MODE(busif); | |
185 | dalign_reg = SSI9_BUSIF_DALIGN(busif); | |
186 | } else { | |
187 | adinr_reg = SSI_BUSIF_ADINR(busif); | |
188 | mode_reg = SSI_BUSIF_MODE(busif); | |
189 | dalign_reg = SSI_BUSIF_DALIGN(busif); | |
8c9d7503 JW |
190 | } |
191 | ||
8af6c521 | 192 | rsnd_mod_write(mod, adinr_reg, |
b7169dde KM |
193 | rsnd_get_adinr_bit(mod, io) | |
194 | (rsnd_io_is_play(io) ? | |
195 | rsnd_runtime_channel_after_ctu(io) : | |
196 | rsnd_runtime_channel_original(io))); | |
8af6c521 | 197 | rsnd_mod_write(mod, mode_reg, |
b7169dde | 198 | rsnd_get_busif_shift(io, mod) | 1); |
8af6c521 | 199 | rsnd_mod_write(mod, dalign_reg, |
b7169dde | 200 | rsnd_get_dalign(mod, io)); |
c7f69ab5 KM |
201 | } |
202 | ||
beed78ae | 203 | if (has_hdmi0 || has_hdmi1) { |
7fa72cca KM |
204 | enum rsnd_mod_type rsnd_ssi_array[] = { |
205 | RSND_MOD_SSIM1, | |
206 | RSND_MOD_SSIM2, | |
207 | RSND_MOD_SSIM3, | |
208 | }; | |
209 | struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io); | |
210 | struct rsnd_mod *pos; | |
211 | u32 val; | |
212 | int i, shift; | |
213 | ||
214 | i = rsnd_mod_id(ssi_mod); | |
215 | ||
216 | /* output all same SSI as default */ | |
217 | val = i << 16 | | |
218 | i << 20 | | |
219 | i << 24 | | |
220 | i << 28 | | |
221 | i; | |
222 | ||
223 | for_each_rsnd_mod_array(i, pos, io, rsnd_ssi_array) { | |
224 | shift = (i * 4) + 16; | |
225 | val = (val & ~(0xF << shift)) | | |
226 | rsnd_mod_id(pos) << shift; | |
227 | } | |
228 | ||
beed78ae | 229 | if (has_hdmi0) |
7fa72cca | 230 | rsnd_mod_write(mod, HDMI0_SEL, val); |
beed78ae | 231 | if (has_hdmi1) |
7fa72cca | 232 | rsnd_mod_write(mod, HDMI1_SEL, val); |
7fa72cca KM |
233 | } |
234 | ||
c7f69ab5 KM |
235 | return 0; |
236 | } | |
237 | ||
238 | static int rsnd_ssiu_start_gen2(struct rsnd_mod *mod, | |
239 | struct rsnd_dai_stream *io, | |
240 | struct rsnd_priv *priv) | |
241 | { | |
4e7788fb | 242 | int busif = rsnd_mod_id_sub(mod); |
8c9d7503 | 243 | |
b4c83b17 KM |
244 | if (!rsnd_ssi_use_busif(io)) |
245 | return 0; | |
246 | ||
8c9d7503 | 247 | rsnd_mod_bset(mod, SSI_CTRL, 1 << (busif * 4), 1 << (busif * 4)); |
b4c83b17 | 248 | |
4f5c634d | 249 | if (rsnd_ssi_multi_slaves_runtime(io)) |
b4c83b17 | 250 | rsnd_mod_write(mod, SSI_CONTROL, 0x1); |
c7f69ab5 KM |
251 | |
252 | return 0; | |
253 | } | |
254 | ||
255 | static int rsnd_ssiu_stop_gen2(struct rsnd_mod *mod, | |
256 | struct rsnd_dai_stream *io, | |
257 | struct rsnd_priv *priv) | |
258 | { | |
2e66d523 | 259 | struct rsnd_ssiu *ssiu = rsnd_mod_to_ssiu(mod); |
4e7788fb | 260 | int busif = rsnd_mod_id_sub(mod); |
8c9d7503 | 261 | |
b4c83b17 KM |
262 | if (!rsnd_ssi_use_busif(io)) |
263 | return 0; | |
264 | ||
8c9d7503 | 265 | rsnd_mod_bset(mod, SSI_CTRL, 1 << (busif * 4), 0); |
b4c83b17 | 266 | |
2e66d523 JW |
267 | if (--ssiu->usrcnt) |
268 | return 0; | |
269 | ||
4f5c634d | 270 | if (rsnd_ssi_multi_slaves_runtime(io)) |
b4c83b17 | 271 | rsnd_mod_write(mod, SSI_CONTROL, 0); |
c7f69ab5 KM |
272 | |
273 | return 0; | |
274 | } | |
275 | ||
4e7788fb KM |
276 | static int rsnd_ssiu_id(struct rsnd_mod *mod) |
277 | { | |
278 | struct rsnd_ssiu *ssiu = rsnd_mod_to_ssiu(mod); | |
279 | ||
280 | /* see rsnd_ssiu_probe() */ | |
281 | return ssiu->id; | |
282 | } | |
283 | ||
284 | static int rsnd_ssiu_id_sub(struct rsnd_mod *mod) | |
285 | { | |
286 | struct rsnd_ssiu *ssiu = rsnd_mod_to_ssiu(mod); | |
287 | ||
288 | /* see rsnd_ssiu_probe() */ | |
289 | return ssiu->id_sub; | |
290 | } | |
291 | ||
292 | static struct dma_chan *rsnd_ssiu_dma_req(struct rsnd_dai_stream *io, | |
293 | struct rsnd_mod *mod) | |
294 | { | |
295 | struct rsnd_priv *priv = rsnd_mod_to_priv(mod); | |
296 | int is_play = rsnd_io_is_play(io); | |
297 | char *name; | |
298 | ||
299 | /* | |
300 | * It should use "rcar_sound,ssiu" on DT. | |
301 | * But, we need to keep compatibility for old version. | |
302 | * | |
303 | * If it has "rcar_sound.ssiu", it will be used. | |
304 | * If not, "rcar_sound.ssi" will be used. | |
305 | * see | |
306 | * rsnd_ssi_dma_req() | |
307 | * rsnd_dma_of_path() | |
308 | */ | |
309 | ||
310 | name = is_play ? "rx" : "tx"; | |
311 | ||
312 | return rsnd_dma_request_channel(rsnd_ssiu_of_node(priv), | |
313 | mod, name); | |
314 | } | |
315 | ||
c7f69ab5 | 316 | static struct rsnd_mod_ops rsnd_ssiu_ops_gen2 = { |
7e7fe06d | 317 | .name = SSIU_NAME, |
4e7788fb | 318 | .dma_req = rsnd_ssiu_dma_req, |
7e7fe06d KM |
319 | .init = rsnd_ssiu_init_gen2, |
320 | .start = rsnd_ssiu_start_gen2, | |
321 | .stop = rsnd_ssiu_stop_gen2, | |
322 | .get_status = rsnd_ssiu_get_status, | |
c7f69ab5 KM |
323 | }; |
324 | ||
325 | static struct rsnd_mod *rsnd_ssiu_mod_get(struct rsnd_priv *priv, int id) | |
326 | { | |
327 | if (WARN_ON(id < 0 || id >= rsnd_ssiu_nr(priv))) | |
328 | id = 0; | |
329 | ||
330 | return rsnd_mod_get((struct rsnd_ssiu *)(priv->ssiu) + id); | |
331 | } | |
332 | ||
4e7788fb KM |
333 | static void rsnd_parse_connect_ssiu_compatible(struct rsnd_priv *priv, |
334 | struct rsnd_dai_stream *io) | |
335 | { | |
336 | struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io); | |
337 | struct rsnd_mod *mod; | |
338 | struct rsnd_ssiu *ssiu; | |
339 | int i; | |
340 | ||
341 | if (!ssi_mod) | |
342 | return; | |
343 | ||
344 | /* select BUSIF0 */ | |
345 | for_each_rsnd_ssiu(ssiu, priv, i) { | |
346 | mod = rsnd_mod_get(ssiu); | |
347 | ||
331e8754 KM |
348 | if ((rsnd_mod_id(ssi_mod) == rsnd_mod_id(mod)) && |
349 | (rsnd_mod_id_sub(mod) == 0)) { | |
4e7788fb | 350 | rsnd_dai_connect(mod, io, mod->type); |
331e8754 KM |
351 | return; |
352 | } | |
4e7788fb KM |
353 | } |
354 | } | |
355 | ||
356 | void rsnd_parse_connect_ssiu(struct rsnd_dai *rdai, | |
357 | struct device_node *playback, | |
358 | struct device_node *capture) | |
c7f69ab5 | 359 | { |
4e7788fb KM |
360 | struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); |
361 | struct device_node *node = rsnd_ssiu_of_node(priv); | |
362 | struct device_node *np; | |
363 | struct rsnd_mod *mod; | |
364 | struct rsnd_dai_stream *io_p = &rdai->playback; | |
365 | struct rsnd_dai_stream *io_c = &rdai->capture; | |
366 | int i; | |
c7f69ab5 | 367 | |
4e7788fb KM |
368 | /* use rcar_sound,ssiu if exist */ |
369 | if (node) { | |
370 | i = 0; | |
371 | for_each_child_of_node(node, np) { | |
372 | mod = rsnd_ssiu_mod_get(priv, i); | |
373 | if (np == playback) | |
374 | rsnd_dai_connect(mod, io_p, mod->type); | |
375 | if (np == capture) | |
376 | rsnd_dai_connect(mod, io_c, mod->type); | |
377 | i++; | |
378 | } | |
c7f69ab5 | 379 | |
4e7788fb KM |
380 | of_node_put(node); |
381 | } | |
382 | ||
383 | /* Keep DT compatibility */ | |
384 | if (!rsnd_io_to_mod_ssiu(io_p)) | |
385 | rsnd_parse_connect_ssiu_compatible(priv, io_p); | |
386 | if (!rsnd_io_to_mod_ssiu(io_c)) | |
387 | rsnd_parse_connect_ssiu_compatible(priv, io_c); | |
c7f69ab5 KM |
388 | } |
389 | ||
2ea6b074 | 390 | int rsnd_ssiu_probe(struct rsnd_priv *priv) |
c7f69ab5 KM |
391 | { |
392 | struct device *dev = rsnd_priv_to_dev(priv); | |
4e7788fb | 393 | struct device_node *node; |
c7f69ab5 | 394 | struct rsnd_ssiu *ssiu; |
32e1b60d | 395 | struct rsnd_mod_ops *ops; |
4e7788fb | 396 | const int *list = NULL; |
c7f69ab5 KM |
397 | int i, nr, ret; |
398 | ||
4e7788fb KM |
399 | /* |
400 | * Keep DT compatibility. | |
401 | * if it has "rcar_sound,ssiu", use it. | |
402 | * if not, use "rcar_sound,ssi" | |
403 | * see | |
404 | * rsnd_ssiu_bufsif_to_id() | |
405 | */ | |
406 | node = rsnd_ssiu_of_node(priv); | |
407 | if (node) | |
408 | nr = of_get_child_count(node); | |
409 | else | |
410 | nr = priv->ssi_nr; | |
411 | ||
a86854d0 | 412 | ssiu = devm_kcalloc(dev, nr, sizeof(*ssiu), GFP_KERNEL); |
c7f69ab5 KM |
413 | if (!ssiu) |
414 | return -ENOMEM; | |
415 | ||
416 | priv->ssiu = ssiu; | |
417 | priv->ssiu_nr = nr; | |
418 | ||
419 | if (rsnd_is_gen1(priv)) | |
420 | ops = &rsnd_ssiu_ops_gen1; | |
421 | else | |
422 | ops = &rsnd_ssiu_ops_gen2; | |
423 | ||
4e7788fb KM |
424 | /* Keep compatibility */ |
425 | nr = 0; | |
426 | if ((node) && | |
427 | (ops == &rsnd_ssiu_ops_gen2)) { | |
428 | ops->id = rsnd_ssiu_id; | |
429 | ops->id_sub = rsnd_ssiu_id_sub; | |
430 | ||
431 | if (rsnd_is_gen2(priv)) { | |
432 | list = gen2_id; | |
433 | nr = ARRAY_SIZE(gen2_id); | |
434 | } else if (rsnd_is_gen3(priv)) { | |
435 | list = gen3_id; | |
436 | nr = ARRAY_SIZE(gen3_id); | |
437 | } else { | |
438 | dev_err(dev, "unknown SSIU\n"); | |
439 | return -ENODEV; | |
440 | } | |
441 | } | |
442 | ||
c7f69ab5 | 443 | for_each_rsnd_ssiu(ssiu, priv, i) { |
4e7788fb KM |
444 | if (node) { |
445 | int j; | |
446 | ||
447 | /* | |
448 | * see | |
449 | * rsnd_ssiu_get_id() | |
450 | * rsnd_ssiu_get_id_sub() | |
451 | */ | |
452 | for (j = 0; j < nr; j++) { | |
453 | if (list[j] > i) | |
454 | break; | |
455 | ssiu->id = j; | |
456 | ssiu->id_sub = i - list[ssiu->id]; | |
457 | } | |
458 | } else { | |
459 | ssiu->id = i; | |
460 | } | |
461 | ||
c7f69ab5 | 462 | ret = rsnd_mod_init(priv, rsnd_mod_get(ssiu), |
7e7fe06d | 463 | ops, NULL, RSND_MOD_SSIU, i); |
c7f69ab5 KM |
464 | if (ret) |
465 | return ret; | |
466 | } | |
467 | ||
468 | return 0; | |
469 | } | |
470 | ||
2ea6b074 | 471 | void rsnd_ssiu_remove(struct rsnd_priv *priv) |
c7f69ab5 KM |
472 | { |
473 | struct rsnd_ssiu *ssiu; | |
474 | int i; | |
475 | ||
476 | for_each_rsnd_ssiu(ssiu, priv, i) { | |
477 | rsnd_mod_quit(rsnd_mod_get(ssiu)); | |
478 | } | |
479 | } |