1 /* $OpenBSD: aplaudio.c,v 1.6 2023/01/14 23:35:09 kettenis Exp $ */
2 /*
3 * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
4 * Copyright (c) 2020 Patrick Wildt <patrick@blueri.se>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/device.h>
22 #include <sys/malloc.h>
23
24 #include <machine/bus.h>
25 #include <machine/fdt.h>
26
27 #include <dev/ofw/openfirm.h>
28 #include <dev/ofw/ofw_misc.h>
29 #include <dev/ofw/fdt.h>
30
31 #include <sys/audioio.h>
32 #include <dev/audio_if.h>
33
34 #include <arm64/dev/aplmca.h>
35
36 struct aplaudio_softc {
37 struct device sc_dev;
38
39 struct dai_device *sc_dai_cpu;
40 struct dai_device *sc_dai_codec[6];
41 };
42
43 void aplaudio_set_format(struct aplaudio_softc *, uint32_t,
44 uint32_t, uint32_t);
45 void aplaudio_set_tdm_slots(struct aplaudio_softc *);
46
47 int aplaudio_open(void *, int);
48 void aplaudio_close(void *);
49 int aplaudio_set_params(void *, int, int,
50 struct audio_params *, struct audio_params *);
51 void *aplaudio_allocm(void *, int, size_t, int, int);
52 void aplaudio_freem(void *, void *, int);
53 int aplaudio_set_port(void *, mixer_ctrl_t *);
54 int aplaudio_get_port(void *, mixer_ctrl_t *);
55 int aplaudio_query_devinfo(void *, mixer_devinfo_t *);
56 int aplaudio_round_blocksize(void *, int);
57 size_t aplaudio_round_buffersize(void *, int, size_t);
58 int aplaudio_trigger_output(void *, void *, void *, int,
59 void (*)(void *), void *, struct audio_params *);
60 int aplaudio_trigger_input(void *, void *, void *, int,
61 void (*)(void *), void *, struct audio_params *);
62 int aplaudio_halt_output(void *);
63 int aplaudio_halt_input(void *);
64
65 const struct audio_hw_if aplaudio_hw_if = {
66 .open = aplaudio_open,
67 .close = aplaudio_close,
68 .set_params = aplaudio_set_params,
69 .allocm = aplaudio_allocm,
70 .freem = aplaudio_freem,
71 .set_port = aplaudio_set_port,
72 .get_port = aplaudio_get_port,
73 .query_devinfo = aplaudio_query_devinfo,
74 .round_blocksize = aplaudio_round_blocksize,
75 .round_buffersize = aplaudio_round_buffersize,
76 .trigger_output = aplaudio_trigger_output,
77 .trigger_input = aplaudio_trigger_input,
78 .halt_output = aplaudio_halt_output,
79 .halt_input = aplaudio_halt_input,
80 };
81
82 int aplaudio_match(struct device *, void *, void *);
83 void aplaudio_attach(struct device *, struct device *, void *);
84
85 const struct cfattach aplaudio_ca = {
86 sizeof (struct aplaudio_softc), aplaudio_match, aplaudio_attach
87 };
88
89 struct cfdriver aplaudio_cd = {
90 NULL, "aplaudio", DV_DULL
91 };
92
93 int
aplaudio_match(struct device * parent,void * match,void * aux)94 aplaudio_match(struct device *parent, void *match, void *aux)
95 {
96 struct fdt_attach_args *faa = aux;
97
98 return OF_is_compatible(faa->fa_node, "apple,macaudio");
99 }
100
101 void
aplaudio_attach(struct device * parent,struct device * self,void * aux)102 aplaudio_attach(struct device *parent, struct device *self, void *aux)
103 {
104 struct aplaudio_softc *sc = (struct aplaudio_softc *)self;
105 struct fdt_attach_args *faa = aux;
106 uint32_t fmt, pol, clk;
107 uint32_t node, cpu, codec;
108 uint32_t *dais;
109 char status[32];
110 int count = 0;
111 int i, ncells;
112 int len;
113
114 printf("\n");
115
116 for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
117 if (OF_getprop(node, "status", status, sizeof(status)) > 0 &&
118 strcmp(status, "disabled") == 0)
119 continue;
120
121 cpu = OF_getnodebyname(node, "cpu");
122 if (cpu == 0)
123 continue;
124
125 sc->sc_dai_cpu = aplmca_alloc_cluster(cpu);
126 if (sc->sc_dai_cpu == NULL)
127 continue;
128
129 codec = OF_getnodebyname(node, "codec");
130 if (codec == 0)
131 continue;
132
133 len = OF_getproplen(codec, "sound-dai");
134 if (len < 0)
135 continue;
136
137 dais = malloc(len, M_TEMP, M_WAITOK);
138 OF_getpropintarray(codec, "sound-dai", dais, len);
139
140 ncells = len / sizeof(uint32_t);
141 ncells = MIN(ncells, nitems(sc->sc_dai_codec));
142 for (i = 0; i < ncells; i++) {
143 sc->sc_dai_codec[i] = dai_byphandle(dais[i]);
144 if (sc->sc_dai_codec[i] == NULL)
145 continue;
146 count++;
147 }
148
149 free(dais, M_TEMP, len);
150
151 if (count == 0)
152 continue;
153 if (count > 1)
154 aplaudio_set_tdm_slots(sc);
155
156 /* XXX Parameters are missing from the device tree? */
157 fmt = DAI_FORMAT_LJ;
158 pol = 0;
159 clk = DAI_CLOCK_CFM | DAI_CLOCK_CBM;
160 aplaudio_set_format(sc, fmt, pol, clk);
161
162 audio_attach_mi(&aplaudio_hw_if, sc, NULL, self);
163
164 /* XXX Only attach the first set of interfaces for now. */
165 return;
166 }
167 }
168
169 void
aplaudio_set_format(struct aplaudio_softc * sc,uint32_t fmt,uint32_t pol,uint32_t clk)170 aplaudio_set_format(struct aplaudio_softc *sc, uint32_t fmt, uint32_t pol,
171 uint32_t clk)
172 {
173 struct dai_device *dai;
174 int i;
175
176 if (sc->sc_dai_cpu->dd_set_format)
177 sc->sc_dai_cpu->dd_set_format(sc->sc_dai_cpu->dd_cookie,
178 fmt, pol, clk);
179 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
180 dai = sc->sc_dai_codec[i];
181 if (dai == NULL)
182 continue;
183 if (dai->dd_set_format)
184 dai->dd_set_format(dai->dd_cookie, fmt, pol, clk);
185 }
186 }
187
188 void
aplaudio_set_tdm_slots(struct aplaudio_softc * sc)189 aplaudio_set_tdm_slots(struct aplaudio_softc *sc)
190 {
191 struct dai_device *dai;
192 int i;
193
194 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
195 dai = sc->sc_dai_codec[i];
196 if (dai == NULL)
197 continue;
198 if (dai->dd_set_tdm_slot) {
199 char prefix[8];
200 int slot = 0;
201
202 if (OF_getprop(dai->dd_node, "sound-name-prefix",
203 prefix, sizeof(prefix)) > 0) {
204 if (strncmp(prefix, "Right", 5) == 0)
205 slot = 1;
206 }
207
208 dai->dd_set_tdm_slot(dai->dd_cookie, slot);
209 }
210 }
211 }
212
213 int
aplaudio_open(void * cookie,int flags)214 aplaudio_open(void *cookie, int flags)
215 {
216 struct aplaudio_softc *sc = cookie;
217 struct dai_device *dai;
218 const struct audio_hw_if *hwif;
219 int error;
220 int i;
221
222 dai = sc->sc_dai_cpu;
223 hwif = dai->dd_hw_if;
224 if (hwif->open) {
225 error = hwif->open(dai->dd_cookie, flags);
226 if (error) {
227 aplaudio_close(cookie);
228 return error;
229 }
230 }
231
232 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
233 dai = sc->sc_dai_codec[i];
234 if (dai == NULL)
235 continue;
236 hwif = dai->dd_hw_if;
237 if (hwif->open) {
238 error = hwif->open(dai->dd_cookie, flags);
239 if (error) {
240 aplaudio_close(cookie);
241 return error;
242 }
243 }
244 }
245
246 return 0;
247 }
248
249 void
aplaudio_close(void * cookie)250 aplaudio_close(void *cookie)
251 {
252 struct aplaudio_softc *sc = cookie;
253 struct dai_device *dai;
254 const struct audio_hw_if *hwif;
255 int i;
256
257 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
258 dai = sc->sc_dai_codec[i];
259 if (dai == NULL)
260 continue;
261 hwif = dai->dd_hw_if;
262 if (hwif->close)
263 hwif->close(dai->dd_cookie);
264 }
265
266 dai = sc->sc_dai_cpu;
267 hwif = dai->dd_hw_if;
268 if (hwif->close)
269 hwif->close(dai->dd_cookie);
270 }
271
272 int
aplaudio_set_params(void * cookie,int setmode,int usemode,struct audio_params * play,struct audio_params * rec)273 aplaudio_set_params(void *cookie, int setmode, int usemode,
274 struct audio_params *play, struct audio_params *rec)
275 {
276 struct aplaudio_softc *sc = cookie;
277 struct dai_device *dai;
278 const struct audio_hw_if *hwif;
279 uint32_t rate;
280 int error;
281 int i;
282
283 dai = sc->sc_dai_cpu;
284 hwif = dai->dd_hw_if;
285 if (hwif->set_params) {
286 error = hwif->set_params(dai->dd_cookie,
287 setmode, usemode, play, rec);
288 if (error)
289 return error;
290 }
291
292 if (setmode & AUMODE_PLAY)
293 rate = play->sample_rate * play->channels * play->bps * 8;
294 else
295 rate = rec->sample_rate * rec->channels * rec->bps * 8;
296
297 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
298 dai = sc->sc_dai_codec[i];
299 if (dai == NULL)
300 continue;
301 if (dai->dd_set_sysclk) {
302 error = dai->dd_set_sysclk(dai->dd_cookie, rate);
303 if (error)
304 return error;
305 }
306 }
307
308 dai = sc->sc_dai_cpu;
309 if (dai->dd_set_sysclk) {
310 error = dai->dd_set_sysclk(dai->dd_cookie, rate);
311 if (error)
312 return error;
313 }
314
315 return 0;
316 }
317
318 void *
aplaudio_allocm(void * cookie,int direction,size_t size,int type,int flags)319 aplaudio_allocm(void *cookie, int direction, size_t size, int type,
320 int flags)
321 {
322 struct aplaudio_softc *sc = cookie;
323 struct dai_device *dai = sc->sc_dai_cpu;
324 const struct audio_hw_if *hwif = dai->dd_hw_if;
325
326 if (hwif->allocm)
327 return hwif->allocm(dai->dd_cookie,
328 direction, size, type, flags);
329
330 return NULL;
331 }
332
333 void
aplaudio_freem(void * cookie,void * addr,int type)334 aplaudio_freem(void *cookie, void *addr, int type)
335 {
336 struct aplaudio_softc *sc = cookie;
337 struct dai_device *dai = sc->sc_dai_cpu;
338 const struct audio_hw_if *hwif = dai->dd_hw_if;
339
340 if (hwif->freem)
341 hwif->freem(dai->dd_cookie, addr, type);
342 }
343
344 int
aplaudio_set_port(void * cookie,mixer_ctrl_t * cp)345 aplaudio_set_port(void *cookie, mixer_ctrl_t *cp)
346 {
347 struct aplaudio_softc *sc = cookie;
348 struct dai_device *dai;
349 const struct audio_hw_if *hwif;
350 int error = ENXIO;
351 int i;
352
353 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
354 dai = sc->sc_dai_codec[i];
355 if (dai == NULL)
356 continue;
357 hwif = dai->dd_hw_if;
358 if (hwif->set_port)
359 error = hwif->set_port(dai->dd_cookie, cp);
360 }
361
362 return error;
363 }
364
365 int
aplaudio_get_port(void * cookie,mixer_ctrl_t * cp)366 aplaudio_get_port(void *cookie, mixer_ctrl_t *cp)
367 {
368 struct aplaudio_softc *sc = cookie;
369 struct dai_device *dai;
370 const struct audio_hw_if *hwif;
371 int error = ENXIO;
372 int i;
373
374 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
375 dai = sc->sc_dai_codec[i];
376 if (dai == NULL)
377 continue;
378 hwif = dai->dd_hw_if;
379 if (hwif->get_port)
380 error = hwif->get_port(dai->dd_cookie, cp);
381 }
382
383 return error;
384 }
385
386 int
aplaudio_query_devinfo(void * cookie,mixer_devinfo_t * dip)387 aplaudio_query_devinfo(void *cookie, mixer_devinfo_t *dip)
388 {
389 struct aplaudio_softc *sc = cookie;
390 struct dai_device *dai;
391 const struct audio_hw_if *hwif;
392 int i;
393
394 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
395 dai = sc->sc_dai_codec[i];
396 if (dai == NULL)
397 continue;
398 hwif = dai->dd_hw_if;
399 if (hwif->query_devinfo)
400 return hwif->query_devinfo(dai->dd_cookie, dip);
401 }
402
403 return ENXIO;
404 }
405
406 int
aplaudio_round_blocksize(void * cookie,int block)407 aplaudio_round_blocksize(void *cookie, int block)
408 {
409 struct aplaudio_softc *sc = cookie;
410 struct dai_device *dai = sc->sc_dai_cpu;
411 const struct audio_hw_if *hwif = dai->dd_hw_if;
412
413 if (hwif->round_blocksize)
414 return hwif->round_blocksize(dai->dd_cookie, block);
415
416 return block;
417 }
418
419 size_t
aplaudio_round_buffersize(void * cookie,int direction,size_t bufsize)420 aplaudio_round_buffersize(void *cookie, int direction, size_t bufsize)
421 {
422 struct aplaudio_softc *sc = cookie;
423 struct dai_device *dai = sc->sc_dai_cpu;
424 const struct audio_hw_if *hwif = dai->dd_hw_if;
425
426 if (hwif->round_buffersize)
427 return hwif->round_buffersize(dai->dd_cookie,
428 direction, bufsize);
429
430 return bufsize;
431 }
432
433 int
aplaudio_trigger_output(void * cookie,void * start,void * end,int blksize,void (* intr)(void *),void * arg,struct audio_params * param)434 aplaudio_trigger_output(void *cookie, void *start, void *end, int blksize,
435 void (*intr)(void *), void *arg, struct audio_params *param)
436 {
437 struct aplaudio_softc *sc = cookie;
438 struct dai_device *dai;
439 const struct audio_hw_if *hwif;
440 int error;
441 int i;
442
443 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
444 dai = sc->sc_dai_codec[i];
445 if (dai == NULL)
446 continue;
447 hwif = dai->dd_hw_if;
448 if (hwif->trigger_output) {
449 error = hwif->trigger_output(dai->dd_cookie,
450 start, end, blksize, intr, arg, param);
451 if (error) {
452 aplaudio_halt_output(cookie);
453 return error;
454 }
455 }
456 }
457
458 dai = sc->sc_dai_cpu;
459 hwif = dai->dd_hw_if;
460 if (hwif->trigger_output) {
461 error = hwif->trigger_output(dai->dd_cookie,
462 start, end, blksize, intr, arg, param);
463 if (error) {
464 aplaudio_halt_output(cookie);
465 return error;
466 }
467 }
468
469 return 0;
470 }
471
472 int
aplaudio_trigger_input(void * cookie,void * start,void * end,int blksize,void (* intr)(void *),void * arg,struct audio_params * param)473 aplaudio_trigger_input(void *cookie, void *start, void *end, int blksize,
474 void (*intr)(void *), void *arg, struct audio_params *param)
475 {
476 struct aplaudio_softc *sc = cookie;
477 struct dai_device *dai;
478 const struct audio_hw_if *hwif;
479 int error;
480 int i;
481
482 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
483 dai = sc->sc_dai_codec[i];
484 if (dai == NULL)
485 continue;
486 hwif = dai->dd_hw_if;
487 if (hwif->trigger_input) {
488 error = hwif->trigger_input(dai->dd_cookie,
489 start, end, blksize, intr, arg, param);
490 if (error) {
491 aplaudio_halt_input(cookie);
492 return error;
493 }
494 }
495 }
496
497 dai = sc->sc_dai_cpu;
498 hwif = dai->dd_hw_if;
499 if (hwif->trigger_input) {
500 error = hwif->trigger_input(dai->dd_cookie,
501 start, end, blksize, intr, arg, param);
502 if (error) {
503 aplaudio_halt_input(cookie);
504 return error;
505 }
506 }
507
508 return 0;
509 }
510
511 int
aplaudio_halt_output(void * cookie)512 aplaudio_halt_output(void *cookie)
513 {
514 struct aplaudio_softc *sc = cookie;
515 struct dai_device *dai;
516 const struct audio_hw_if *hwif;
517 int i;
518
519 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
520 dai = sc->sc_dai_codec[i];
521 if (dai == NULL)
522 continue;
523 hwif = dai->dd_hw_if;
524 if (hwif->halt_output)
525 hwif->halt_output(dai->dd_cookie);
526 }
527
528 dai = sc->sc_dai_cpu;
529 hwif = dai->dd_hw_if;
530 if (hwif->halt_output)
531 hwif->halt_output(dai->dd_cookie);
532
533 return 0;
534 }
535
536 int
aplaudio_halt_input(void * cookie)537 aplaudio_halt_input(void *cookie)
538 {
539 struct aplaudio_softc *sc = cookie;
540 struct dai_device *dai;
541 const struct audio_hw_if *hwif;
542 int i;
543
544 for (i = 0; i < nitems(sc->sc_dai_codec); i++) {
545 dai = sc->sc_dai_codec[i];
546 if (dai == NULL)
547 continue;
548 hwif = dai->dd_hw_if;
549 if (hwif->halt_input)
550 hwif->halt_input(dai->dd_cookie);
551 }
552
553 dai = sc->sc_dai_cpu;
554 hwif = dai->dd_hw_if;
555 if (hwif->halt_input)
556 hwif->halt_input(dai->dd_cookie);
557
558 return 0;
559 }
560