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
36struct 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
43void	aplaudio_set_format(struct aplaudio_softc *, uint32_t,
44	    uint32_t, uint32_t);
45void	aplaudio_set_tdm_slots(struct aplaudio_softc *);
46
47int	aplaudio_open(void *, int);
48void	aplaudio_close(void *);
49int	aplaudio_set_params(void *, int, int,
50	    struct audio_params *, struct audio_params *);
51void	*aplaudio_allocm(void *, int, size_t, int, int);
52void	aplaudio_freem(void *, void *, int);
53int	aplaudio_set_port(void *, mixer_ctrl_t *);
54int	aplaudio_get_port(void *, mixer_ctrl_t *);
55int	aplaudio_query_devinfo(void *, mixer_devinfo_t *);
56int	aplaudio_round_blocksize(void *, int);
57size_t	aplaudio_round_buffersize(void *, int, size_t);
58int	aplaudio_trigger_output(void *, void *, void *, int,
59	    void (*)(void *), void *, struct audio_params *);
60int	aplaudio_trigger_input(void *, void *, void *, int,
61	    void (*)(void *), void *, struct audio_params *);
62int	aplaudio_halt_output(void *);
63int	aplaudio_halt_input(void *);
64
65const 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
82int	aplaudio_match(struct device *, void *, void *);
83void	aplaudio_attach(struct device *, struct device *, void *);
84
85const struct cfattach aplaudio_ca = {
86	sizeof (struct aplaudio_softc), aplaudio_match, aplaudio_attach
87};
88
89struct cfdriver aplaudio_cd = {
90	NULL, "aplaudio", DV_DULL
91};
92
93int
94aplaudio_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
101void
102aplaudio_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
169void
170aplaudio_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
188void
189aplaudio_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
213int
214aplaudio_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
249void
250aplaudio_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
272int
273aplaudio_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
318void *
319aplaudio_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
333void
334aplaudio_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
344int
345aplaudio_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
365int
366aplaudio_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
386int
387aplaudio_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
406int
407aplaudio_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
419size_t
420aplaudio_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
433int
434aplaudio_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
472int
473aplaudio_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
511int
512aplaudio_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
536int
537aplaudio_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