1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. |
4 | // |
5 | // Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> |
6 | // Copyright (C) 2006 Carlos Munoz <carlos@kenati.com> |
7 | |
8 | #include <linux/delay.h> |
9 | #include <linux/firmware.h> |
10 | #include <linux/pm_runtime.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/module.h> |
13 | |
14 | #include <asm/clock.h> |
15 | #include <asm/siu.h> |
16 | |
17 | #include <sound/control.h> |
18 | #include <sound/soc.h> |
19 | |
20 | #include "siu.h" |
21 | |
22 | /* Board specifics */ |
23 | #if defined(CONFIG_CPU_SUBTYPE_SH7722) |
24 | # define SIU_MAX_VOLUME 0x1000 |
25 | #else |
26 | # define SIU_MAX_VOLUME 0x7fff |
27 | #endif |
28 | |
29 | #define PRAM_SIZE 0x2000 |
30 | #define XRAM_SIZE 0x800 |
31 | #define YRAM_SIZE 0x800 |
32 | |
33 | #define XRAM_OFFSET 0x4000 |
34 | #define YRAM_OFFSET 0x6000 |
35 | #define REG_OFFSET 0xc000 |
36 | |
37 | #define PLAYBACK_ENABLED 1 |
38 | #define CAPTURE_ENABLED 2 |
39 | |
40 | #define VOLUME_CAPTURE 0 |
41 | #define VOLUME_PLAYBACK 1 |
42 | #define DFLT_VOLUME_LEVEL 0x08000800 |
43 | |
44 | /* |
45 | * SPDIF is only available on port A and on some SIU implementations it is only |
46 | * available for input. Due to the lack of hardware to test it, SPDIF is left |
47 | * disabled in this driver version |
48 | */ |
49 | struct format_flag { |
50 | u32 i2s; |
51 | u32 pcm; |
52 | u32 spdif; |
53 | u32 mask; |
54 | }; |
55 | |
56 | struct port_flag { |
57 | struct format_flag playback; |
58 | struct format_flag capture; |
59 | }; |
60 | |
61 | struct siu_info *siu_i2s_data; |
62 | |
63 | static struct port_flag siu_flags[SIU_PORT_NUM] = { |
64 | [SIU_PORT_A] = { |
65 | .playback = { |
66 | .i2s = 0x50000000, |
67 | .pcm = 0x40000000, |
68 | .spdif = 0x80000000, /* not on all SIU versions */ |
69 | .mask = 0xd0000000, |
70 | }, |
71 | .capture = { |
72 | .i2s = 0x05000000, |
73 | .pcm = 0x04000000, |
74 | .spdif = 0x08000000, |
75 | .mask = 0x0d000000, |
76 | }, |
77 | }, |
78 | [SIU_PORT_B] = { |
79 | .playback = { |
80 | .i2s = 0x00500000, |
81 | .pcm = 0x00400000, |
82 | .spdif = 0, /* impossible - turn off */ |
83 | .mask = 0x00500000, |
84 | }, |
85 | .capture = { |
86 | .i2s = 0x00050000, |
87 | .pcm = 0x00040000, |
88 | .spdif = 0, /* impossible - turn off */ |
89 | .mask = 0x00050000, |
90 | }, |
91 | }, |
92 | }; |
93 | |
94 | static void siu_dai_start(struct siu_port *port_info) |
95 | { |
96 | struct siu_info *info = siu_i2s_data; |
97 | u32 __iomem *base = info->reg; |
98 | |
99 | dev_dbg(port_info->pcm->card->dev, "%s\n" , __func__); |
100 | |
101 | /* Issue software reset to siu */ |
102 | siu_write32(addr: base + SIU_SRCTL, val: 0); |
103 | |
104 | /* Wait for the reset to take effect */ |
105 | udelay(1); |
106 | |
107 | port_info->stfifo = 0; |
108 | port_info->trdat = 0; |
109 | |
110 | /* portA, portB, SIU operate */ |
111 | siu_write32(addr: base + SIU_SRCTL, val: 0x301); |
112 | |
113 | /* portA=256fs, portB=256fs */ |
114 | siu_write32(addr: base + SIU_CKCTL, val: 0x40400000); |
115 | |
116 | /* portA's BRG does not divide SIUCKA */ |
117 | siu_write32(addr: base + SIU_BRGASEL, val: 0); |
118 | siu_write32(addr: base + SIU_BRRA, val: 0); |
119 | |
120 | /* portB's BRG divides SIUCKB by half */ |
121 | siu_write32(addr: base + SIU_BRGBSEL, val: 1); |
122 | siu_write32(addr: base + SIU_BRRB, val: 0); |
123 | |
124 | siu_write32(addr: base + SIU_IFCTL, val: 0x44440000); |
125 | |
126 | /* portA: 32 bit/fs, master; portB: 32 bit/fs, master */ |
127 | siu_write32(addr: base + SIU_SFORM, val: 0x0c0c0000); |
128 | |
129 | /* |
130 | * Volume levels: looks like the DSP firmware implements volume controls |
131 | * differently from what's described in the datasheet |
132 | */ |
133 | siu_write32(addr: base + SIU_SBDVCA, val: port_info->playback.volume); |
134 | siu_write32(addr: base + SIU_SBDVCB, val: port_info->capture.volume); |
135 | } |
136 | |
137 | static void siu_dai_stop(struct siu_port *port_info) |
138 | { |
139 | struct siu_info *info = siu_i2s_data; |
140 | u32 __iomem *base = info->reg; |
141 | |
142 | /* SIU software reset */ |
143 | siu_write32(addr: base + SIU_SRCTL, val: 0); |
144 | } |
145 | |
146 | static void siu_dai_spbAselect(struct siu_port *port_info) |
147 | { |
148 | struct siu_info *info = siu_i2s_data; |
149 | struct siu_firmware *fw = &info->fw; |
150 | u32 *ydef = fw->yram0; |
151 | u32 idx; |
152 | |
153 | /* path A use */ |
154 | if (!info->port_id) |
155 | idx = 1; /* portA */ |
156 | else |
157 | idx = 2; /* portB */ |
158 | |
159 | ydef[0] = (fw->spbpar[idx].ab1a << 16) | |
160 | (fw->spbpar[idx].ab0a << 8) | |
161 | (fw->spbpar[idx].dir << 7) | 3; |
162 | ydef[1] = fw->yram0[1]; /* 0x03000300 */ |
163 | ydef[2] = (16 / 2) << 24; |
164 | ydef[3] = fw->yram0[3]; /* 0 */ |
165 | ydef[4] = fw->yram0[4]; /* 0 */ |
166 | ydef[7] = fw->spbpar[idx].event; |
167 | port_info->stfifo |= fw->spbpar[idx].stfifo; |
168 | port_info->trdat |= fw->spbpar[idx].trdat; |
169 | } |
170 | |
171 | static void siu_dai_spbBselect(struct siu_port *port_info) |
172 | { |
173 | struct siu_info *info = siu_i2s_data; |
174 | struct siu_firmware *fw = &info->fw; |
175 | u32 *ydef = fw->yram0; |
176 | u32 idx; |
177 | |
178 | /* path B use */ |
179 | if (!info->port_id) |
180 | idx = 7; /* portA */ |
181 | else |
182 | idx = 8; /* portB */ |
183 | |
184 | ydef[5] = (fw->spbpar[idx].ab1a << 16) | |
185 | (fw->spbpar[idx].ab0a << 8) | 1; |
186 | ydef[6] = fw->spbpar[idx].event; |
187 | port_info->stfifo |= fw->spbpar[idx].stfifo; |
188 | port_info->trdat |= fw->spbpar[idx].trdat; |
189 | } |
190 | |
191 | static void siu_dai_open(struct siu_stream *siu_stream) |
192 | { |
193 | struct siu_info *info = siu_i2s_data; |
194 | u32 __iomem *base = info->reg; |
195 | u32 srctl, ifctl; |
196 | |
197 | srctl = siu_read32(addr: base + SIU_SRCTL); |
198 | ifctl = siu_read32(addr: base + SIU_IFCTL); |
199 | |
200 | switch (info->port_id) { |
201 | case SIU_PORT_A: |
202 | /* portA operates */ |
203 | srctl |= 0x200; |
204 | ifctl &= ~0xc2; |
205 | break; |
206 | case SIU_PORT_B: |
207 | /* portB operates */ |
208 | srctl |= 0x100; |
209 | ifctl &= ~0x31; |
210 | break; |
211 | } |
212 | |
213 | siu_write32(addr: base + SIU_SRCTL, val: srctl); |
214 | /* Unmute and configure portA */ |
215 | siu_write32(addr: base + SIU_IFCTL, val: ifctl); |
216 | } |
217 | |
218 | /* |
219 | * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower |
220 | * packing is supported |
221 | */ |
222 | static void siu_dai_pcmdatapack(struct siu_stream *siu_stream) |
223 | { |
224 | struct siu_info *info = siu_i2s_data; |
225 | u32 __iomem *base = info->reg; |
226 | u32 dpak; |
227 | |
228 | dpak = siu_read32(addr: base + SIU_DPAK); |
229 | |
230 | switch (info->port_id) { |
231 | case SIU_PORT_A: |
232 | dpak &= ~0xc0000000; |
233 | break; |
234 | case SIU_PORT_B: |
235 | dpak &= ~0x00c00000; |
236 | break; |
237 | } |
238 | |
239 | siu_write32(addr: base + SIU_DPAK, val: dpak); |
240 | } |
241 | |
242 | static int siu_dai_spbstart(struct siu_port *port_info) |
243 | { |
244 | struct siu_info *info = siu_i2s_data; |
245 | u32 __iomem *base = info->reg; |
246 | struct siu_firmware *fw = &info->fw; |
247 | u32 *ydef = fw->yram0; |
248 | int cnt; |
249 | u32 __iomem *add; |
250 | u32 *ptr; |
251 | |
252 | /* Load SPB Program in PRAM */ |
253 | ptr = fw->pram0; |
254 | add = info->pram; |
255 | for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++) |
256 | siu_write32(addr: add, val: *ptr); |
257 | |
258 | ptr = fw->pram1; |
259 | add = info->pram + (0x0100 / sizeof(u32)); |
260 | for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++) |
261 | siu_write32(addr: add, val: *ptr); |
262 | |
263 | /* XRAM initialization */ |
264 | add = info->xram; |
265 | for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++) |
266 | siu_write32(addr: add, val: 0); |
267 | |
268 | /* YRAM variable area initialization */ |
269 | add = info->yram; |
270 | for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++) |
271 | siu_write32(addr: add, val: ydef[cnt]); |
272 | |
273 | /* YRAM FIR coefficient area initialization */ |
274 | add = info->yram + (0x0200 / sizeof(u32)); |
275 | for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++) |
276 | siu_write32(addr: add, val: fw->yram_fir_coeff[cnt]); |
277 | |
278 | /* YRAM IIR coefficient area initialization */ |
279 | add = info->yram + (0x0600 / sizeof(u32)); |
280 | for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++) |
281 | siu_write32(addr: add, val: 0); |
282 | |
283 | siu_write32(addr: base + SIU_TRDAT, val: port_info->trdat); |
284 | port_info->trdat = 0x0; |
285 | |
286 | |
287 | /* SPB start condition: software */ |
288 | siu_write32(addr: base + SIU_SBACTIV, val: 0); |
289 | /* Start SPB */ |
290 | siu_write32(addr: base + SIU_SBCTL, val: 0xc0000000); |
291 | /* Wait for program to halt */ |
292 | cnt = 0x10000; |
293 | while (--cnt && siu_read32(addr: base + SIU_SBCTL) != 0x80000000) |
294 | cpu_relax(); |
295 | |
296 | if (!cnt) |
297 | return -EBUSY; |
298 | |
299 | /* SPB program start address setting */ |
300 | siu_write32(addr: base + SIU_SBPSET, val: 0x00400000); |
301 | /* SPB hardware start(FIFOCTL source) */ |
302 | siu_write32(addr: base + SIU_SBACTIV, val: 0xc0000000); |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | static void siu_dai_spbstop(struct siu_port *port_info) |
308 | { |
309 | struct siu_info *info = siu_i2s_data; |
310 | u32 __iomem *base = info->reg; |
311 | |
312 | siu_write32(addr: base + SIU_SBACTIV, val: 0); |
313 | /* SPB stop */ |
314 | siu_write32(addr: base + SIU_SBCTL, val: 0); |
315 | |
316 | port_info->stfifo = 0; |
317 | } |
318 | |
319 | /* API functions */ |
320 | |
321 | /* Playback and capture hardware properties are identical */ |
322 | static const struct snd_pcm_hardware siu_dai_pcm_hw = { |
323 | .info = SNDRV_PCM_INFO_INTERLEAVED, |
324 | .formats = SNDRV_PCM_FMTBIT_S16, |
325 | .rates = SNDRV_PCM_RATE_8000_48000, |
326 | .rate_min = 8000, |
327 | .rate_max = 48000, |
328 | .channels_min = 2, |
329 | .channels_max = 2, |
330 | .buffer_bytes_max = SIU_BUFFER_BYTES_MAX, |
331 | .period_bytes_min = SIU_PERIOD_BYTES_MIN, |
332 | .period_bytes_max = SIU_PERIOD_BYTES_MAX, |
333 | .periods_min = SIU_PERIODS_MIN, |
334 | .periods_max = SIU_PERIODS_MAX, |
335 | }; |
336 | |
337 | static int siu_dai_info_volume(struct snd_kcontrol *kctrl, |
338 | struct snd_ctl_elem_info *uinfo) |
339 | { |
340 | struct siu_port *port_info = snd_kcontrol_chip(kctrl); |
341 | |
342 | dev_dbg(port_info->pcm->card->dev, "%s\n" , __func__); |
343 | |
344 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
345 | uinfo->count = 2; |
346 | uinfo->value.integer.min = 0; |
347 | uinfo->value.integer.max = SIU_MAX_VOLUME; |
348 | |
349 | return 0; |
350 | } |
351 | |
352 | static int siu_dai_get_volume(struct snd_kcontrol *kctrl, |
353 | struct snd_ctl_elem_value *ucontrol) |
354 | { |
355 | struct siu_port *port_info = snd_kcontrol_chip(kctrl); |
356 | struct device *dev = port_info->pcm->card->dev; |
357 | u32 vol; |
358 | |
359 | dev_dbg(dev, "%s\n" , __func__); |
360 | |
361 | switch (kctrl->private_value) { |
362 | case VOLUME_PLAYBACK: |
363 | /* Playback is always on port 0 */ |
364 | vol = port_info->playback.volume; |
365 | ucontrol->value.integer.value[0] = vol & 0xffff; |
366 | ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; |
367 | break; |
368 | case VOLUME_CAPTURE: |
369 | /* Capture is always on port 1 */ |
370 | vol = port_info->capture.volume; |
371 | ucontrol->value.integer.value[0] = vol & 0xffff; |
372 | ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; |
373 | break; |
374 | default: |
375 | dev_err(dev, "%s() invalid private_value=%ld\n" , |
376 | __func__, kctrl->private_value); |
377 | return -EINVAL; |
378 | } |
379 | |
380 | return 0; |
381 | } |
382 | |
383 | static int siu_dai_put_volume(struct snd_kcontrol *kctrl, |
384 | struct snd_ctl_elem_value *ucontrol) |
385 | { |
386 | struct siu_port *port_info = snd_kcontrol_chip(kctrl); |
387 | struct device *dev = port_info->pcm->card->dev; |
388 | struct siu_info *info = siu_i2s_data; |
389 | u32 __iomem *base = info->reg; |
390 | u32 new_vol; |
391 | u32 cur_vol; |
392 | |
393 | dev_dbg(dev, "%s\n" , __func__); |
394 | |
395 | if (ucontrol->value.integer.value[0] < 0 || |
396 | ucontrol->value.integer.value[0] > SIU_MAX_VOLUME || |
397 | ucontrol->value.integer.value[1] < 0 || |
398 | ucontrol->value.integer.value[1] > SIU_MAX_VOLUME) |
399 | return -EINVAL; |
400 | |
401 | new_vol = ucontrol->value.integer.value[0] | |
402 | ucontrol->value.integer.value[1] << 16; |
403 | |
404 | /* See comment above - DSP firmware implementation */ |
405 | switch (kctrl->private_value) { |
406 | case VOLUME_PLAYBACK: |
407 | /* Playback is always on port 0 */ |
408 | cur_vol = port_info->playback.volume; |
409 | siu_write32(addr: base + SIU_SBDVCA, val: new_vol); |
410 | port_info->playback.volume = new_vol; |
411 | break; |
412 | case VOLUME_CAPTURE: |
413 | /* Capture is always on port 1 */ |
414 | cur_vol = port_info->capture.volume; |
415 | siu_write32(addr: base + SIU_SBDVCB, val: new_vol); |
416 | port_info->capture.volume = new_vol; |
417 | break; |
418 | default: |
419 | dev_err(dev, "%s() invalid private_value=%ld\n" , |
420 | __func__, kctrl->private_value); |
421 | return -EINVAL; |
422 | } |
423 | |
424 | if (cur_vol != new_vol) |
425 | return 1; |
426 | |
427 | return 0; |
428 | } |
429 | |
430 | static const struct snd_kcontrol_new playback_controls = { |
431 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
432 | .name = "PCM Playback Volume" , |
433 | .index = 0, |
434 | .info = siu_dai_info_volume, |
435 | .get = siu_dai_get_volume, |
436 | .put = siu_dai_put_volume, |
437 | .private_value = VOLUME_PLAYBACK, |
438 | }; |
439 | |
440 | static const struct snd_kcontrol_new capture_controls = { |
441 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
442 | .name = "PCM Capture Volume" , |
443 | .index = 0, |
444 | .info = siu_dai_info_volume, |
445 | .get = siu_dai_get_volume, |
446 | .put = siu_dai_put_volume, |
447 | .private_value = VOLUME_CAPTURE, |
448 | }; |
449 | |
450 | int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card) |
451 | { |
452 | struct device *dev = card->dev; |
453 | struct snd_kcontrol *kctrl; |
454 | int ret; |
455 | |
456 | *port_info = kzalloc(size: sizeof(**port_info), GFP_KERNEL); |
457 | if (!*port_info) |
458 | return -ENOMEM; |
459 | |
460 | dev_dbg(dev, "%s: port #%d@%p\n" , __func__, port, *port_info); |
461 | |
462 | (*port_info)->playback.volume = DFLT_VOLUME_LEVEL; |
463 | (*port_info)->capture.volume = DFLT_VOLUME_LEVEL; |
464 | |
465 | /* |
466 | * Add mixer support. The SPB is used to change the volume. Both |
467 | * ports use the same SPB. Therefore, we only register one |
468 | * control instance since it will be used by both channels. |
469 | * In error case we continue without controls. |
470 | */ |
471 | kctrl = snd_ctl_new1(kcontrolnew: &playback_controls, private_data: *port_info); |
472 | ret = snd_ctl_add(card, kcontrol: kctrl); |
473 | if (ret < 0) |
474 | dev_err(dev, |
475 | "failed to add playback controls %p port=%d err=%d\n" , |
476 | kctrl, port, ret); |
477 | |
478 | kctrl = snd_ctl_new1(kcontrolnew: &capture_controls, private_data: *port_info); |
479 | ret = snd_ctl_add(card, kcontrol: kctrl); |
480 | if (ret < 0) |
481 | dev_err(dev, |
482 | "failed to add capture controls %p port=%d err=%d\n" , |
483 | kctrl, port, ret); |
484 | |
485 | return 0; |
486 | } |
487 | |
488 | void siu_free_port(struct siu_port *port_info) |
489 | { |
490 | kfree(objp: port_info); |
491 | } |
492 | |
493 | static int siu_dai_startup(struct snd_pcm_substream *substream, |
494 | struct snd_soc_dai *dai) |
495 | { |
496 | struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
497 | struct snd_pcm_runtime *rt = substream->runtime; |
498 | struct siu_port *port_info = siu_port_info(substream); |
499 | int ret; |
500 | |
501 | dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n" , __func__, |
502 | info->port_id, port_info); |
503 | |
504 | snd_soc_set_runtime_hwparams(substream, hw: &siu_dai_pcm_hw); |
505 | |
506 | ret = snd_pcm_hw_constraint_integer(runtime: rt, SNDRV_PCM_HW_PARAM_PERIODS); |
507 | if (unlikely(ret < 0)) |
508 | return ret; |
509 | |
510 | siu_dai_start(port_info); |
511 | |
512 | return 0; |
513 | } |
514 | |
515 | static void siu_dai_shutdown(struct snd_pcm_substream *substream, |
516 | struct snd_soc_dai *dai) |
517 | { |
518 | struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
519 | struct siu_port *port_info = siu_port_info(substream); |
520 | |
521 | dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n" , __func__, |
522 | info->port_id, port_info); |
523 | |
524 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
525 | port_info->play_cap &= ~PLAYBACK_ENABLED; |
526 | else |
527 | port_info->play_cap &= ~CAPTURE_ENABLED; |
528 | |
529 | /* Stop the siu if the other stream is not using it */ |
530 | if (!port_info->play_cap) { |
531 | /* during stmread or stmwrite ? */ |
532 | if (WARN_ON(port_info->playback.rw_flg || port_info->capture.rw_flg)) |
533 | return; |
534 | siu_dai_spbstop(port_info); |
535 | siu_dai_stop(port_info); |
536 | } |
537 | } |
538 | |
539 | /* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */ |
540 | static int siu_dai_prepare(struct snd_pcm_substream *substream, |
541 | struct snd_soc_dai *dai) |
542 | { |
543 | struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
544 | struct snd_pcm_runtime *rt = substream->runtime; |
545 | struct siu_port *port_info = siu_port_info(substream); |
546 | struct siu_stream *siu_stream; |
547 | int self, ret; |
548 | |
549 | dev_dbg(substream->pcm->card->dev, |
550 | "%s: port %d, active streams %lx, %d channels\n" , |
551 | __func__, info->port_id, port_info->play_cap, rt->channels); |
552 | |
553 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
554 | self = PLAYBACK_ENABLED; |
555 | siu_stream = &port_info->playback; |
556 | } else { |
557 | self = CAPTURE_ENABLED; |
558 | siu_stream = &port_info->capture; |
559 | } |
560 | |
561 | /* Set up the siu if not already done */ |
562 | if (!port_info->play_cap) { |
563 | siu_stream->rw_flg = 0; /* stream-data transfer flag */ |
564 | |
565 | siu_dai_spbAselect(port_info); |
566 | siu_dai_spbBselect(port_info); |
567 | |
568 | siu_dai_open(siu_stream); |
569 | |
570 | siu_dai_pcmdatapack(siu_stream); |
571 | |
572 | ret = siu_dai_spbstart(port_info); |
573 | if (ret < 0) |
574 | goto fail; |
575 | } else { |
576 | ret = 0; |
577 | } |
578 | |
579 | port_info->play_cap |= self; |
580 | |
581 | fail: |
582 | return ret; |
583 | } |
584 | |
585 | /* |
586 | * SIU can set bus format to I2S / PCM / SPDIF independently for playback and |
587 | * capture, however, the current API sets the bus format globally for a DAI. |
588 | */ |
589 | static int siu_dai_set_fmt(struct snd_soc_dai *dai, |
590 | unsigned int fmt) |
591 | { |
592 | struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
593 | u32 __iomem *base = info->reg; |
594 | u32 ifctl; |
595 | |
596 | dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n" , |
597 | __func__, fmt, info->port_id); |
598 | |
599 | if (info->port_id < 0) |
600 | return -ENODEV; |
601 | |
602 | /* Here select between I2S / PCM / SPDIF */ |
603 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
604 | case SND_SOC_DAIFMT_I2S: |
605 | ifctl = siu_flags[info->port_id].playback.i2s | |
606 | siu_flags[info->port_id].capture.i2s; |
607 | break; |
608 | case SND_SOC_DAIFMT_LEFT_J: |
609 | ifctl = siu_flags[info->port_id].playback.pcm | |
610 | siu_flags[info->port_id].capture.pcm; |
611 | break; |
612 | /* SPDIF disabled - see comment at the top */ |
613 | default: |
614 | return -EINVAL; |
615 | } |
616 | |
617 | ifctl |= ~(siu_flags[info->port_id].playback.mask | |
618 | siu_flags[info->port_id].capture.mask) & |
619 | siu_read32(addr: base + SIU_IFCTL); |
620 | siu_write32(addr: base + SIU_IFCTL, val: ifctl); |
621 | |
622 | return 0; |
623 | } |
624 | |
625 | static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, |
626 | unsigned int freq, int dir) |
627 | { |
628 | struct clk *siu_clk, *parent_clk; |
629 | char *siu_name, *parent_name; |
630 | int ret; |
631 | |
632 | if (dir != SND_SOC_CLOCK_IN) |
633 | return -EINVAL; |
634 | |
635 | dev_dbg(dai->dev, "%s: using clock %d\n" , __func__, clk_id); |
636 | |
637 | switch (clk_id) { |
638 | case SIU_CLKA_PLL: |
639 | siu_name = "siua_clk" ; |
640 | parent_name = "pll_clk" ; |
641 | break; |
642 | case SIU_CLKA_EXT: |
643 | siu_name = "siua_clk" ; |
644 | parent_name = "siumcka_clk" ; |
645 | break; |
646 | case SIU_CLKB_PLL: |
647 | siu_name = "siub_clk" ; |
648 | parent_name = "pll_clk" ; |
649 | break; |
650 | case SIU_CLKB_EXT: |
651 | siu_name = "siub_clk" ; |
652 | parent_name = "siumckb_clk" ; |
653 | break; |
654 | default: |
655 | return -EINVAL; |
656 | } |
657 | |
658 | siu_clk = clk_get(dai->dev, siu_name); |
659 | if (IS_ERR(ptr: siu_clk)) { |
660 | dev_err(dai->dev, "%s: cannot get a SIU clock: %ld\n" , __func__, |
661 | PTR_ERR(siu_clk)); |
662 | return PTR_ERR(ptr: siu_clk); |
663 | } |
664 | |
665 | parent_clk = clk_get(dai->dev, parent_name); |
666 | if (IS_ERR(ptr: parent_clk)) { |
667 | ret = PTR_ERR(ptr: parent_clk); |
668 | dev_err(dai->dev, "cannot get a SIU clock parent: %d\n" , ret); |
669 | goto epclkget; |
670 | } |
671 | |
672 | ret = clk_set_parent(siu_clk, parent_clk); |
673 | if (ret < 0) { |
674 | dev_err(dai->dev, "cannot reparent the SIU clock: %d\n" , ret); |
675 | goto eclksetp; |
676 | } |
677 | |
678 | ret = clk_set_rate(siu_clk, freq); |
679 | if (ret < 0) |
680 | dev_err(dai->dev, "cannot set SIU clock rate: %d\n" , ret); |
681 | |
682 | /* TODO: when clkdev gets reference counting we'll move these to siu_dai_shutdown() */ |
683 | eclksetp: |
684 | clk_put(parent_clk); |
685 | epclkget: |
686 | clk_put(siu_clk); |
687 | |
688 | return ret; |
689 | } |
690 | |
691 | static const struct snd_soc_dai_ops siu_dai_ops = { |
692 | .startup = siu_dai_startup, |
693 | .shutdown = siu_dai_shutdown, |
694 | .prepare = siu_dai_prepare, |
695 | .set_sysclk = siu_dai_set_sysclk, |
696 | .set_fmt = siu_dai_set_fmt, |
697 | }; |
698 | |
699 | static struct snd_soc_dai_driver siu_i2s_dai = { |
700 | .name = "siu-i2s-dai" , |
701 | .playback = { |
702 | .channels_min = 2, |
703 | .channels_max = 2, |
704 | .formats = SNDRV_PCM_FMTBIT_S16, |
705 | .rates = SNDRV_PCM_RATE_8000_48000, |
706 | }, |
707 | .capture = { |
708 | .channels_min = 2, |
709 | .channels_max = 2, |
710 | .formats = SNDRV_PCM_FMTBIT_S16, |
711 | .rates = SNDRV_PCM_RATE_8000_48000, |
712 | }, |
713 | .ops = &siu_dai_ops, |
714 | }; |
715 | |
716 | static int siu_probe(struct platform_device *pdev) |
717 | { |
718 | const struct firmware *fw_entry; |
719 | struct resource *res, *region; |
720 | struct siu_info *info; |
721 | int ret; |
722 | |
723 | info = devm_kmalloc(dev: &pdev->dev, size: sizeof(*info), GFP_KERNEL); |
724 | if (!info) |
725 | return -ENOMEM; |
726 | siu_i2s_data = info; |
727 | info->dev = &pdev->dev; |
728 | |
729 | ret = request_firmware(fw: &fw_entry, name: "siu_spb.bin" , device: &pdev->dev); |
730 | if (ret) |
731 | return ret; |
732 | |
733 | /* |
734 | * Loaded firmware is "const" - read only, but we have to modify it in |
735 | * snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect() |
736 | */ |
737 | memcpy(&info->fw, fw_entry->data, fw_entry->size); |
738 | |
739 | release_firmware(fw: fw_entry); |
740 | |
741 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
742 | if (!res) |
743 | return -ENODEV; |
744 | |
745 | region = devm_request_mem_region(&pdev->dev, res->start, |
746 | resource_size(res), pdev->name); |
747 | if (!region) { |
748 | dev_err(&pdev->dev, "SIU region already claimed\n" ); |
749 | return -EBUSY; |
750 | } |
751 | |
752 | info->pram = devm_ioremap(dev: &pdev->dev, offset: res->start, PRAM_SIZE); |
753 | if (!info->pram) |
754 | return -ENOMEM; |
755 | info->xram = devm_ioremap(dev: &pdev->dev, offset: res->start + XRAM_OFFSET, |
756 | XRAM_SIZE); |
757 | if (!info->xram) |
758 | return -ENOMEM; |
759 | info->yram = devm_ioremap(dev: &pdev->dev, offset: res->start + YRAM_OFFSET, |
760 | YRAM_SIZE); |
761 | if (!info->yram) |
762 | return -ENOMEM; |
763 | info->reg = devm_ioremap(dev: &pdev->dev, offset: res->start + REG_OFFSET, |
764 | size: resource_size(res) - REG_OFFSET); |
765 | if (!info->reg) |
766 | return -ENOMEM; |
767 | |
768 | dev_set_drvdata(dev: &pdev->dev, data: info); |
769 | |
770 | /* register using ARRAY version so we can keep dai name */ |
771 | ret = devm_snd_soc_register_component(dev: &pdev->dev, component_driver: &siu_component, |
772 | dai_drv: &siu_i2s_dai, num_dai: 1); |
773 | if (ret < 0) |
774 | return ret; |
775 | |
776 | pm_runtime_enable(dev: &pdev->dev); |
777 | |
778 | return 0; |
779 | } |
780 | |
781 | static void siu_remove(struct platform_device *pdev) |
782 | { |
783 | pm_runtime_disable(dev: &pdev->dev); |
784 | } |
785 | |
786 | static struct platform_driver siu_driver = { |
787 | .driver = { |
788 | .name = "siu-pcm-audio" , |
789 | }, |
790 | .probe = siu_probe, |
791 | .remove_new = siu_remove, |
792 | }; |
793 | |
794 | module_platform_driver(siu_driver); |
795 | |
796 | MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>" ); |
797 | MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver" ); |
798 | MODULE_LICENSE("GPL" ); |
799 | |
800 | MODULE_FIRMWARE("siu_spb.bin" ); |
801 | |