xref: /freebsd/sys/dev/sound/fdt/audio_soc.c (revision 43a5ec4eb41567cc92586503212743d89686d78f)
1 /*-
2  * Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 #include "opt_platform.h"
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/clock.h>
35 #include <sys/kernel.h>
36 #include <sys/lock.h>
37 #include <sys/module.h>
38 #include <sys/endian.h>
39 
40 #include <dev/ofw/ofw_bus.h>
41 #include <dev/ofw/ofw_bus_subr.h>
42 
43 #include <dev/sound/fdt/audio_dai.h>
44 #include <dev/sound/pcm/sound.h>
45 #include "audio_dai_if.h"
46 
47 #define	AUDIO_BUFFER_SIZE	48000 * 4
48 
49 struct audio_soc_aux_node {
50 	SLIST_ENTRY(audio_soc_aux_node)	link;
51 	device_t			dev;
52 };
53 
54 struct audio_soc_channel {
55 	struct audio_soc_softc	*sc;	/* parent device's softc */
56 	struct pcm_channel 	*pcm;	/* PCM channel */
57 	struct snd_dbuf		*buf;	/* PCM buffer */
58 	int			dir;	/* direction */
59 };
60 
61 struct audio_soc_softc {
62 	/*
63 	 * pcm_register assumes that sc is snddev_info,
64 	 * so this has to be first structure member for "compatibility"
65 	 */
66 	struct snddev_info	info;
67 	device_t		dev;
68 	char			*name;
69 	struct intr_config_hook init_hook;
70 	device_t		cpu_dev;
71 	device_t		codec_dev;
72 	SLIST_HEAD(, audio_soc_aux_node)	aux_devs;
73 	unsigned int		mclk_fs;
74 	struct audio_soc_channel 	play_channel;
75 	struct audio_soc_channel 	rec_channel;
76 	/*
77 	 * The format is from the CPU node, for CODEC node clock roles
78 	 * need to be reversed.
79 	 */
80 	uint32_t		format;
81 	uint32_t		link_mclk_fs;
82 };
83 
84 static struct ofw_compat_data compat_data[] = {
85 	{"simple-audio-card",	1},
86 	{NULL,			0},
87 };
88 
89 static struct {
90 	const char *name;
91 	unsigned int fmt;
92 } ausoc_dai_formats[] = {
93 	{ "i2s",	AUDIO_DAI_FORMAT_I2S },
94 	{ "right_j",	AUDIO_DAI_FORMAT_RJ },
95 	{ "left_j",	AUDIO_DAI_FORMAT_LJ },
96 	{ "dsp_a",	AUDIO_DAI_FORMAT_DSPA },
97 	{ "dsp_b",	AUDIO_DAI_FORMAT_DSPB },
98 	{ "ac97",	AUDIO_DAI_FORMAT_AC97 },
99 	{ "pdm",	AUDIO_DAI_FORMAT_PDM },
100 };
101 
102 static int	audio_soc_probe(device_t dev);
103 static int	audio_soc_attach(device_t dev);
104 static int	audio_soc_detach(device_t dev);
105 
106 /*
107  * Invert master/slave roles for CODEC side of the node
108  */
109 static uint32_t
110 audio_soc_reverse_clocks(uint32_t format)
111 {
112 	int fmt, pol, clk;
113 
114 	fmt = AUDIO_DAI_FORMAT_FORMAT(format);
115 	pol = AUDIO_DAI_FORMAT_POLARITY(format);
116 	clk = AUDIO_DAI_FORMAT_CLOCK(format);
117 
118 	switch (clk) {
119 	case AUDIO_DAI_CLOCK_CBM_CFM:
120 		clk = AUDIO_DAI_CLOCK_CBS_CFS;
121 		break;
122 	case AUDIO_DAI_CLOCK_CBS_CFM:
123 		clk = AUDIO_DAI_CLOCK_CBM_CFS;
124 		break;
125 	case AUDIO_DAI_CLOCK_CBM_CFS:
126 		clk = AUDIO_DAI_CLOCK_CBS_CFM;
127 		break;
128 	case AUDIO_DAI_CLOCK_CBS_CFS:
129 		clk = AUDIO_DAI_CLOCK_CBM_CFM;
130 		break;
131 	}
132 
133 	return AUDIO_DAI_FORMAT(fmt, pol, clk);
134 }
135 
136 static uint32_t
137 audio_soc_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksz)
138 {
139 
140 	return (blocksz);
141 }
142 
143 static int
144 audio_soc_chan_setformat(kobj_t obj, void *data, uint32_t format)
145 {
146 
147 	struct audio_soc_softc *sc;
148 	struct audio_soc_channel *ausoc_chan;
149 
150 	ausoc_chan = data;
151 	sc = ausoc_chan->sc;
152 
153 	return AUDIO_DAI_SET_CHANFORMAT(sc->cpu_dev, format);
154 }
155 
156 static uint32_t
157 audio_soc_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
158 {
159 
160 	struct audio_soc_softc *sc;
161 	struct audio_soc_channel *ausoc_chan;
162 	uint32_t rate;
163 	struct audio_soc_aux_node *aux_node;
164 
165 	ausoc_chan = data;
166 	sc = ausoc_chan->sc;
167 
168 	if (sc->link_mclk_fs) {
169 		rate = speed * sc->link_mclk_fs;
170 		if (AUDIO_DAI_SET_SYSCLK(sc->cpu_dev, rate, AUDIO_DAI_CLOCK_IN))
171 			device_printf(sc->dev, "failed to set sysclk for CPU node\n");
172 
173 		if (AUDIO_DAI_SET_SYSCLK(sc->codec_dev, rate, AUDIO_DAI_CLOCK_OUT))
174 			device_printf(sc->dev, "failed to set sysclk for codec node\n");
175 
176 		SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
177 			if (AUDIO_DAI_SET_SYSCLK(aux_node->dev, rate, AUDIO_DAI_CLOCK_OUT))
178 				device_printf(sc->dev, "failed to set sysclk for aux node\n");
179 		}
180 	}
181 
182 	/*
183 	 * Let CPU node determine speed
184 	 */
185 	speed = AUDIO_DAI_SET_CHANSPEED(sc->cpu_dev, speed);
186 	AUDIO_DAI_SET_CHANSPEED(sc->codec_dev, speed);
187 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
188 		AUDIO_DAI_SET_CHANSPEED(aux_node->dev, speed);
189 	}
190 
191 	return (speed);
192 }
193 
194 static uint32_t
195 audio_soc_chan_getptr(kobj_t obj, void *data)
196 {
197 	struct audio_soc_softc *sc;
198 	struct audio_soc_channel *ausoc_chan;
199 
200 	ausoc_chan = data;
201 	sc = ausoc_chan->sc;
202 
203 	return AUDIO_DAI_GET_PTR(sc->cpu_dev, ausoc_chan->dir);
204 }
205 
206 static void *
207 audio_soc_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
208 	struct pcm_channel *c, int dir)
209 {
210 	struct audio_soc_softc *sc;
211 	struct audio_soc_channel *ausoc_chan;
212 	void *buffer;
213 
214 	ausoc_chan = devinfo;
215 	sc = ausoc_chan->sc;
216 	buffer = malloc(AUDIO_BUFFER_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
217 
218 	if (sndbuf_setup(b, buffer, AUDIO_BUFFER_SIZE) != 0) {
219 		free(buffer, M_DEVBUF);
220 		return NULL;
221 	}
222 
223 	ausoc_chan->dir = dir;
224 	ausoc_chan->buf = b;
225 	ausoc_chan->pcm = c;
226 
227 	return (devinfo);
228 }
229 
230 static int
231 audio_soc_chan_trigger(kobj_t obj, void *data, int go)
232 {
233 	struct audio_soc_softc *sc;
234 	struct audio_soc_channel *ausoc_chan;
235 	struct audio_soc_aux_node *aux_node;
236 
237 	ausoc_chan = (struct audio_soc_channel *)data;
238 	sc = ausoc_chan->sc;
239 	AUDIO_DAI_TRIGGER(sc->codec_dev, go, ausoc_chan->dir);
240 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
241 		AUDIO_DAI_TRIGGER(aux_node->dev, go, ausoc_chan->dir);
242 	}
243 
244 	return AUDIO_DAI_TRIGGER(sc->cpu_dev, go, ausoc_chan->dir);
245 }
246 
247 static int
248 audio_soc_chan_free(kobj_t obj, void *data)
249 {
250 
251 	struct audio_soc_softc *sc;
252 	struct audio_soc_channel *ausoc_chan;
253 	void *buffer;
254 
255 	ausoc_chan = (struct audio_soc_channel *)data;
256 	sc = ausoc_chan->sc;
257 
258 	buffer = sndbuf_getbuf(ausoc_chan->buf);
259 	if (buffer)
260 		free(buffer, M_DEVBUF);
261 
262 	return (0);
263 }
264 
265 static struct pcmchan_caps *
266 audio_soc_chan_getcaps(kobj_t obj, void *data)
267 {
268 	struct audio_soc_softc *sc;
269 	struct audio_soc_channel *ausoc_chan;
270 
271 	ausoc_chan = data;
272 	sc = ausoc_chan->sc;
273 
274 	return AUDIO_DAI_GET_CAPS(sc->cpu_dev);
275 }
276 
277 static kobj_method_t audio_soc_chan_methods[] = {
278 	KOBJMETHOD(channel_init, 	audio_soc_chan_init),
279 	KOBJMETHOD(channel_free, 	audio_soc_chan_free),
280 	KOBJMETHOD(channel_setformat, 	audio_soc_chan_setformat),
281 	KOBJMETHOD(channel_setspeed, 	audio_soc_chan_setspeed),
282 	KOBJMETHOD(channel_setblocksize,audio_soc_chan_setblocksize),
283 	KOBJMETHOD(channel_trigger,	audio_soc_chan_trigger),
284 	KOBJMETHOD(channel_getptr,	audio_soc_chan_getptr),
285 	KOBJMETHOD(channel_getcaps,	audio_soc_chan_getcaps),
286 	KOBJMETHOD_END
287 };
288 CHANNEL_DECLARE(audio_soc_chan);
289 
290 static void
291 audio_soc_intr(void *arg)
292 {
293 	struct audio_soc_softc *sc;
294 	int channel_intr_required;
295 
296 	sc = (struct audio_soc_softc *)arg;
297 	channel_intr_required = AUDIO_DAI_INTR(sc->cpu_dev, sc->play_channel.buf, sc->rec_channel.buf);
298 	if (channel_intr_required & AUDIO_DAI_PLAY_INTR)
299 		chn_intr(sc->play_channel.pcm);
300 	if (channel_intr_required & AUDIO_DAI_REC_INTR)
301 		chn_intr(sc->rec_channel.pcm);
302 }
303 
304 static int
305 audio_soc_probe(device_t dev)
306 {
307 
308 	if (!ofw_bus_status_okay(dev))
309 		return (ENXIO);
310 
311 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) {
312 		device_set_desc(dev, "simple-audio-card");
313 		return (BUS_PROBE_DEFAULT);
314 	}
315 
316 	return (ENXIO);
317 }
318 
319 static void
320 audio_soc_init(void *arg)
321 {
322 	struct audio_soc_softc *sc;
323 	phandle_t node, child;
324 	device_t daidev, auxdev;
325 	uint32_t xref;
326 	uint32_t *aux_devs;
327 	int ncells, i;
328 	struct audio_soc_aux_node *aux_node;
329 
330 	sc = (struct audio_soc_softc *)arg;
331 	config_intrhook_disestablish(&sc->init_hook);
332 
333 	node = ofw_bus_get_node(sc->dev);
334 	/* TODO: handle multi-link nodes */
335 	child = ofw_bus_find_child(node, "simple-audio-card,cpu");
336 	if (child == 0) {
337 		device_printf(sc->dev, "cpu node is missing\n");
338 		return;
339 	}
340 	if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
341 		device_printf(sc->dev, "missing sound-dai property in cpu node\n");
342 		return;
343 	}
344 	daidev = OF_device_from_xref(xref);
345 	if (daidev == NULL) {
346 		device_printf(sc->dev, "no driver attached to cpu node\n");
347 		return;
348 	}
349 	sc->cpu_dev = daidev;
350 
351 	child = ofw_bus_find_child(node, "simple-audio-card,codec");
352 	if (child == 0) {
353 		device_printf(sc->dev, "codec node is missing\n");
354 		return;
355 	}
356 	if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
357 		device_printf(sc->dev, "missing sound-dai property in codec node\n");
358 		return;
359 	}
360 	daidev = OF_device_from_xref(xref);
361 	if (daidev == NULL) {
362 		device_printf(sc->dev, "no driver attached to codec node\n");
363 		return;
364 	}
365 	sc->codec_dev = daidev;
366 
367 	/* Add AUX devices */
368 	aux_devs = NULL;
369 	ncells = OF_getencprop_alloc_multi(node, "simple-audio-card,aux-devs", sizeof(*aux_devs),
370 	    (void **)&aux_devs);
371 
372 	for (i = 0; i < ncells; i++) {
373 		auxdev = OF_device_from_xref(aux_devs[i]);
374 		if (auxdev == NULL)
375 			device_printf(sc->dev, "warning: no driver attached to aux node\n");
376 		aux_node = (struct audio_soc_aux_node *)malloc(sizeof(*aux_node), M_DEVBUF, M_NOWAIT);
377 		if (aux_node == NULL) {
378 			device_printf(sc->dev, "failed to allocate aux node struct\n");
379 			return;
380 		}
381 		aux_node->dev = auxdev;
382 		SLIST_INSERT_HEAD(&sc->aux_devs, aux_node, link);
383 	}
384 
385 	if (aux_devs)
386 		OF_prop_free(aux_devs);
387 
388 	if (AUDIO_DAI_INIT(sc->cpu_dev, sc->format)) {
389 		device_printf(sc->dev, "failed to initialize cpu node\n");
390 		return;
391 	}
392 
393 	/* Reverse clock roles for CODEC */
394 	if (AUDIO_DAI_INIT(sc->codec_dev, audio_soc_reverse_clocks(sc->format))) {
395 		device_printf(sc->dev, "failed to initialize codec node\n");
396 		return;
397 	}
398 
399 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
400 		if (AUDIO_DAI_INIT(aux_node->dev, audio_soc_reverse_clocks(sc->format))) {
401 			device_printf(sc->dev, "failed to initialize aux node\n");
402 			return;
403 		}
404 	}
405 
406 	if (pcm_register(sc->dev, sc, 1, 1)) {
407 		device_printf(sc->dev, "failed to register PCM\n");
408 		return;
409 	}
410 
411 	sc->play_channel.sc = sc;
412 	sc->rec_channel.sc = sc;
413 
414 	pcm_addchan(sc->dev, PCMDIR_PLAY, &audio_soc_chan_class, &sc->play_channel);
415 	pcm_addchan(sc->dev, PCMDIR_REC, &audio_soc_chan_class, &sc->rec_channel);
416 
417 	pcm_setstatus(sc->dev, "at EXPERIMENT");
418 
419 	AUDIO_DAI_SETUP_INTR(sc->cpu_dev, audio_soc_intr, sc);
420 	AUDIO_DAI_SETUP_MIXER(sc->codec_dev, sc->dev);
421 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
422 		AUDIO_DAI_SETUP_MIXER(aux_node->dev, sc->dev);
423 	}
424 }
425 
426 static int
427 audio_soc_attach(device_t dev)
428 {
429 	struct audio_soc_softc *sc;
430 	char *name;
431 	phandle_t node, cpu_child;
432 	uint32_t xref;
433 	int i, ret;
434 	char tmp[32];
435 	unsigned int fmt, pol, clk;
436 	bool frame_master, bitclock_master;
437 
438 	sc = device_get_softc(dev);
439 	sc->dev = dev;
440 	node = ofw_bus_get_node(dev);
441 
442 	ret = OF_getprop_alloc(node, "name", (void **)&name);
443 	if (ret == -1)
444 		name = "SoC audio";
445 
446 	sc->name = strdup(name, M_DEVBUF);
447 	device_set_desc(dev, sc->name);
448 
449 	if (ret != -1)
450 		OF_prop_free(name);
451 
452 	SLIST_INIT(&sc->aux_devs);
453 
454 	ret = OF_getprop(node, "simple-audio-card,format", tmp, sizeof(tmp));
455 	if (ret == 0) {
456 		for (i = 0; i < nitems(ausoc_dai_formats); i++) {
457 			if (strcmp(tmp, ausoc_dai_formats[i].name) == 0) {
458 				fmt = ausoc_dai_formats[i].fmt;
459 				break;
460 			}
461 		}
462 		if (i == nitems(ausoc_dai_formats))
463 			return (EINVAL);
464 	} else
465 		fmt = AUDIO_DAI_FORMAT_I2S;
466 
467 	if (OF_getencprop(node, "simple-audio-card,mclk-fs",
468 	    &sc->link_mclk_fs, sizeof(sc->link_mclk_fs)) <= 0)
469 		sc->link_mclk_fs = 0;
470 
471 	/* Unless specified otherwise, CPU node is the master */
472 	frame_master = bitclock_master = true;
473 
474 	cpu_child = ofw_bus_find_child(node, "simple-audio-card,cpu");
475 
476 	if ((OF_getencprop(node, "simple-audio-card,frame-master", &xref, sizeof(xref))) > 0)
477 		frame_master = cpu_child == OF_node_from_xref(xref);
478 
479 	if ((OF_getencprop(node, "simple-audio-card,bitclock-master", &xref, sizeof(xref))) > 0)
480 		bitclock_master = cpu_child == OF_node_from_xref(xref);
481 
482 	if (frame_master) {
483 		clk = bitclock_master ?
484 		    AUDIO_DAI_CLOCK_CBM_CFM : AUDIO_DAI_CLOCK_CBS_CFM;
485 	} else {
486 		clk = bitclock_master ?
487 		    AUDIO_DAI_CLOCK_CBM_CFS : AUDIO_DAI_CLOCK_CBS_CFS;
488 	}
489 
490 	bool bitclock_inversion = OF_hasprop(node, "simple-audio-card,bitclock-inversion");
491 	bool frame_inversion = OF_hasprop(node, "simple-audio-card,frame-inversion");
492 	if (bitclock_inversion) {
493 		pol = frame_inversion ?
494 		    AUDIO_DAI_POLARITY_IB_IF : AUDIO_DAI_POLARITY_IB_NF;
495 	} else {
496 		pol = frame_inversion ?
497 		    AUDIO_DAI_POLARITY_NB_IF : AUDIO_DAI_POLARITY_NB_NF;
498 	}
499 
500 	sc->format = AUDIO_DAI_FORMAT(fmt, pol, clk);
501 
502 	sc->init_hook.ich_func = audio_soc_init;
503 	sc->init_hook.ich_arg = sc;
504 	if (config_intrhook_establish(&sc->init_hook) != 0)
505 		return (ENOMEM);
506 
507 	return (0);
508 }
509 
510 static int
511 audio_soc_detach(device_t dev)
512 {
513 	struct audio_soc_softc *sc;
514 	struct audio_soc_aux_node *aux;
515 
516 	sc = device_get_softc(dev);
517 	if (sc->name)
518 		free(sc->name, M_DEVBUF);
519 
520 	while ((aux = SLIST_FIRST(&sc->aux_devs)) != NULL) {
521 		SLIST_REMOVE_HEAD(&sc->aux_devs, link);
522 		free(aux, M_DEVBUF);
523 	}
524 
525 	return (0);
526 }
527 
528 static device_method_t audio_soc_methods[] = {
529         /* device_if methods */
530 	DEVMETHOD(device_probe,		audio_soc_probe),
531 	DEVMETHOD(device_attach,	audio_soc_attach),
532 	DEVMETHOD(device_detach,	audio_soc_detach),
533 
534 	DEVMETHOD_END,
535 };
536 
537 static driver_t audio_soc_driver = {
538 	"pcm",
539 	audio_soc_methods,
540 	sizeof(struct audio_soc_softc),
541 };
542 
543 DRIVER_MODULE(audio_soc, simplebus, audio_soc_driver, pcm_devclass, NULL, NULL);
544 MODULE_VERSION(audio_soc, 1);
545