2 * Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
29 #include "opt_platform.h"
31 #include <sys/param.h>
32 #include <sys/systm.h>
34 #include <sys/clock.h>
35 #include <sys/kernel.h>
37 #include <sys/module.h>
38 #include <sys/endian.h>
40 #include <dev/ofw/ofw_bus.h>
41 #include <dev/ofw/ofw_bus_subr.h>
43 #include <dev/sound/fdt/audio_dai.h>
44 #include <dev/sound/pcm/sound.h>
45 #include "audio_dai_if.h"
47 #define AUDIO_BUFFER_SIZE 48000 * 4
49 struct audio_soc_aux_node {
50 SLIST_ENTRY(audio_soc_aux_node) link;
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 */
61 struct audio_soc_softc {
63 * pcm_register assumes that sc is snddev_info,
64 * so this has to be first structure member for "compatibility"
66 struct snddev_info info;
69 struct intr_config_hook init_hook;
72 SLIST_HEAD(, audio_soc_aux_node) aux_devs;
74 struct audio_soc_channel play_channel;
75 struct audio_soc_channel rec_channel;
77 * The format is from the CPU node, for CODEC node clock roles
78 * need to be reversed.
81 uint32_t link_mclk_fs;
84 static struct ofw_compat_data compat_data[] = {
85 {"simple-audio-card", 1},
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 },
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);
107 * Invert master/slave roles for CODEC side of the node
110 audio_soc_reverse_clocks(uint32_t format)
114 fmt = AUDIO_DAI_FORMAT_FORMAT(format);
115 pol = AUDIO_DAI_FORMAT_POLARITY(format);
116 clk = AUDIO_DAI_FORMAT_CLOCK(format);
119 case AUDIO_DAI_CLOCK_CBM_CFM:
120 clk = AUDIO_DAI_CLOCK_CBS_CFS;
122 case AUDIO_DAI_CLOCK_CBS_CFM:
123 clk = AUDIO_DAI_CLOCK_CBM_CFS;
125 case AUDIO_DAI_CLOCK_CBM_CFS:
126 clk = AUDIO_DAI_CLOCK_CBS_CFM;
128 case AUDIO_DAI_CLOCK_CBS_CFS:
129 clk = AUDIO_DAI_CLOCK_CBM_CFM;
133 return AUDIO_DAI_FORMAT(fmt, pol, clk);
137 audio_soc_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksz)
144 audio_soc_chan_setformat(kobj_t obj, void *data, uint32_t format)
147 struct audio_soc_softc *sc;
148 struct audio_soc_channel *ausoc_chan;
153 return AUDIO_DAI_SET_CHANFORMAT(sc->cpu_dev, format);
157 audio_soc_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
160 struct audio_soc_softc *sc;
161 struct audio_soc_channel *ausoc_chan;
163 struct audio_soc_aux_node *aux_node;
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");
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");
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");
183 * Let CPU node determine speed
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);
195 audio_soc_chan_getptr(kobj_t obj, void *data)
197 struct audio_soc_softc *sc;
198 struct audio_soc_channel *ausoc_chan;
203 return AUDIO_DAI_GET_PTR(sc->cpu_dev, ausoc_chan->dir);
207 audio_soc_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
208 struct pcm_channel *c, int dir)
210 struct audio_soc_softc *sc;
211 struct audio_soc_channel *ausoc_chan;
214 ausoc_chan = devinfo;
216 buffer = malloc(AUDIO_BUFFER_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
218 if (sndbuf_setup(b, buffer, AUDIO_BUFFER_SIZE) != 0) {
219 free(buffer, M_DEVBUF);
223 ausoc_chan->dir = dir;
231 audio_soc_chan_trigger(kobj_t obj, void *data, int go)
233 struct audio_soc_softc *sc;
234 struct audio_soc_channel *ausoc_chan;
235 struct audio_soc_aux_node *aux_node;
237 ausoc_chan = (struct audio_soc_channel *)data;
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);
244 return AUDIO_DAI_TRIGGER(sc->cpu_dev, go, ausoc_chan->dir);
248 audio_soc_chan_free(kobj_t obj, void *data)
251 struct audio_soc_softc *sc;
252 struct audio_soc_channel *ausoc_chan;
255 ausoc_chan = (struct audio_soc_channel *)data;
258 buffer = sndbuf_getbuf(ausoc_chan->buf);
260 free(buffer, M_DEVBUF);
265 static struct pcmchan_caps *
266 audio_soc_chan_getcaps(kobj_t obj, void *data)
268 struct audio_soc_softc *sc;
269 struct audio_soc_channel *ausoc_chan;
274 return AUDIO_DAI_GET_CAPS(sc->cpu_dev);
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),
288 CHANNEL_DECLARE(audio_soc_chan);
291 audio_soc_intr(void *arg)
293 struct audio_soc_softc *sc;
294 int channel_intr_required;
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);
305 audio_soc_probe(device_t dev)
308 if (!ofw_bus_status_okay(dev))
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);
320 audio_soc_init(void *arg)
322 struct audio_soc_softc *sc;
323 phandle_t node, child;
324 device_t daidev, auxdev;
328 struct audio_soc_aux_node *aux_node;
330 sc = (struct audio_soc_softc *)arg;
331 config_intrhook_disestablish(&sc->init_hook);
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");
337 device_printf(sc->dev, "cpu node is missing\n");
340 if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
341 device_printf(sc->dev, "missing sound-dai property in cpu node\n");
344 daidev = OF_device_from_xref(xref);
345 if (daidev == NULL) {
346 device_printf(sc->dev, "no driver attached to cpu node\n");
349 sc->cpu_dev = daidev;
351 child = ofw_bus_find_child(node, "simple-audio-card,codec");
353 device_printf(sc->dev, "codec node is missing\n");
356 if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
357 device_printf(sc->dev, "missing sound-dai property in codec node\n");
360 daidev = OF_device_from_xref(xref);
361 if (daidev == NULL) {
362 device_printf(sc->dev, "no driver attached to codec node\n");
365 sc->codec_dev = daidev;
367 /* Add AUX devices */
369 ncells = OF_getencprop_alloc_multi(node, "simple-audio-card,aux-devs", sizeof(*aux_devs),
372 for (i = 0; i < ncells; i++) {
373 auxdev = OF_device_from_xref(aux_devs[i]);
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");
381 aux_node->dev = auxdev;
382 SLIST_INSERT_HEAD(&sc->aux_devs, aux_node, link);
386 OF_prop_free(aux_devs);
388 if (AUDIO_DAI_INIT(sc->cpu_dev, sc->format)) {
389 device_printf(sc->dev, "failed to initialize cpu node\n");
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");
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");
406 if (pcm_register(sc->dev, sc, 1, 1)) {
407 device_printf(sc->dev, "failed to register PCM\n");
411 pcm_getbuffersize(sc->dev, AUDIO_BUFFER_SIZE, AUDIO_BUFFER_SIZE,
413 sc->play_channel.sc = sc;
414 sc->rec_channel.sc = sc;
416 pcm_addchan(sc->dev, PCMDIR_PLAY, &audio_soc_chan_class, &sc->play_channel);
417 pcm_addchan(sc->dev, PCMDIR_REC, &audio_soc_chan_class, &sc->rec_channel);
419 pcm_setstatus(sc->dev, "at EXPERIMENT");
421 AUDIO_DAI_SETUP_INTR(sc->cpu_dev, audio_soc_intr, sc);
422 AUDIO_DAI_SETUP_MIXER(sc->codec_dev, sc->dev);
423 SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
424 AUDIO_DAI_SETUP_MIXER(aux_node->dev, sc->dev);
429 audio_soc_attach(device_t dev)
431 struct audio_soc_softc *sc;
433 phandle_t node, cpu_child;
437 unsigned int fmt, pol, clk;
438 bool frame_master, bitclock_master;
440 sc = device_get_softc(dev);
442 node = ofw_bus_get_node(dev);
444 ret = OF_getprop_alloc(node, "name", (void **)&name);
448 sc->name = strdup(name, M_DEVBUF);
449 device_set_desc(dev, sc->name);
454 SLIST_INIT(&sc->aux_devs);
456 ret = OF_getprop(node, "simple-audio-card,format", tmp, sizeof(tmp));
458 for (i = 0; i < nitems(ausoc_dai_formats); i++) {
459 if (strcmp(tmp, ausoc_dai_formats[i].name) == 0) {
460 fmt = ausoc_dai_formats[i].fmt;
464 if (i == nitems(ausoc_dai_formats))
467 fmt = AUDIO_DAI_FORMAT_I2S;
469 if (OF_getencprop(node, "simple-audio-card,mclk-fs",
470 &sc->link_mclk_fs, sizeof(sc->link_mclk_fs)) <= 0)
471 sc->link_mclk_fs = 0;
473 /* Unless specified otherwise, CPU node is the master */
474 frame_master = bitclock_master = true;
476 cpu_child = ofw_bus_find_child(node, "simple-audio-card,cpu");
478 if ((OF_getencprop(node, "simple-audio-card,frame-master", &xref, sizeof(xref))) > 0)
479 frame_master = cpu_child == OF_node_from_xref(xref);
481 if ((OF_getencprop(node, "simple-audio-card,bitclock-master", &xref, sizeof(xref))) > 0)
482 bitclock_master = cpu_child == OF_node_from_xref(xref);
485 clk = bitclock_master ?
486 AUDIO_DAI_CLOCK_CBM_CFM : AUDIO_DAI_CLOCK_CBS_CFM;
488 clk = bitclock_master ?
489 AUDIO_DAI_CLOCK_CBM_CFS : AUDIO_DAI_CLOCK_CBS_CFS;
492 bool bitclock_inversion = OF_hasprop(node, "simple-audio-card,bitclock-inversion");
493 bool frame_inversion = OF_hasprop(node, "simple-audio-card,frame-inversion");
494 if (bitclock_inversion) {
495 pol = frame_inversion ?
496 AUDIO_DAI_POLARITY_IB_IF : AUDIO_DAI_POLARITY_IB_NF;
498 pol = frame_inversion ?
499 AUDIO_DAI_POLARITY_NB_IF : AUDIO_DAI_POLARITY_NB_NF;
502 sc->format = AUDIO_DAI_FORMAT(fmt, pol, clk);
504 sc->init_hook.ich_func = audio_soc_init;
505 sc->init_hook.ich_arg = sc;
506 if (config_intrhook_establish(&sc->init_hook) != 0)
513 audio_soc_detach(device_t dev)
515 struct audio_soc_softc *sc;
516 struct audio_soc_aux_node *aux;
518 sc = device_get_softc(dev);
520 free(sc->name, M_DEVBUF);
522 while ((aux = SLIST_FIRST(&sc->aux_devs)) != NULL) {
523 SLIST_REMOVE_HEAD(&sc->aux_devs, link);
530 static device_method_t audio_soc_methods[] = {
531 /* device_if methods */
532 DEVMETHOD(device_probe, audio_soc_probe),
533 DEVMETHOD(device_attach, audio_soc_attach),
534 DEVMETHOD(device_detach, audio_soc_detach),
539 static driver_t audio_soc_driver = {
542 sizeof(struct audio_soc_softc),
545 DRIVER_MODULE(audio_soc, simplebus, audio_soc_driver, pcm_devclass, NULL, NULL);
546 MODULE_VERSION(audio_soc, 1);