diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64 --- a/sys/conf/files.arm64 +++ b/sys/conf/files.arm64 @@ -148,6 +148,14 @@ cddl/dev/dtrace/aarch64/dtrace_subr.c optional dtrace compile-with "${DTRACE_C}" cddl/dev/fbt/aarch64/fbt_isa.c optional dtrace_fbt | dtraceall compile-with "${FBT_C}" +## +## ASoC support +## +dev/sound/fdt/audio_dai_if.m optional sound fdt +dev/sound/fdt/audio_soc.c optional sound fdt +dev/sound/fdt/dummy_codec.c optional sound fdt +dev/sound/fdt/simple_amplifier.c optional sound fdt + ## ## Device drivers ## diff --git a/sys/dev/sound/fdt/audio_dai.h b/sys/dev/sound/fdt/audio_dai.h new file mode 100644 --- /dev/null +++ b/sys/dev/sound/fdt/audio_dai.h @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 2019 Oleksandr Tymoshenko + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __DAI_H__ +#define __DAI_H__ + +#define AUDIO_DAI_FORMAT_I2S 0 +#define AUDIO_DAI_FORMAT_RJ 1 +#define AUDIO_DAI_FORMAT_LJ 2 +#define AUDIO_DAI_FORMAT_DSPA 3 +#define AUDIO_DAI_FORMAT_DSPB 4 +#define AUDIO_DAI_FORMAT_AC97 5 +#define AUDIO_DAI_FORMAT_PDM 6 + +/* + * Polarity: Normal/Inverted BCLK/Frame + */ +#define AUDIO_DAI_POLARITY_NB_NF 0 +#define AUDIO_DAI_POLARITY_NB_IF 1 +#define AUDIO_DAI_POLARITY_IB_NF 2 +#define AUDIO_DAI_POLARITY_IB_IF 3 +#define AUDIO_DAI_POLARITY_INVERTED_FRAME(n) ((n) & 0x01) +#define AUDIO_DAI_POLARITY_INVERTED_BCLK(n) ((n) & 0x2) + +#define AUDIO_DAI_CLOCK_CBM_CFM 0 +#define AUDIO_DAI_CLOCK_CBS_CFM 1 +#define AUDIO_DAI_CLOCK_CBM_CFS 2 +#define AUDIO_DAI_CLOCK_CBS_CFS 3 + +#define AUDIO_DAI_CLOCK_IN 0 +#define AUDIO_DAI_CLOCK_OUT 1 + +#define AUDIO_DAI_JACK_HP 0 +#define AUDIO_DAI_JACK_MIC 1 + +/* + * Signal to audio_soc that chn_intr required + * for either recording or playback + */ +#define AUDIO_DAI_REC_INTR (1 << 1) +#define AUDIO_DAI_PLAY_INTR (1 << 0) + +#define AUDIO_DAI_FORMAT(fmt, pol, clk) (((fmt) << 16) | ((pol) << 8) | (clk)) +#define AUDIO_DAI_FORMAT_FORMAT(format) (((format) >> 16) & 0xff) +#define AUDIO_DAI_FORMAT_POLARITY(format) (((format) >> 8) & 0xff) +#define AUDIO_DAI_FORMAT_CLOCK(format) (((format) >> 0) & 0xff) + + +#endif /* __DAI_H__ */ diff --git a/sys/dev/sound/fdt/audio_dai_if.m b/sys/dev/sound/fdt/audio_dai_if.m new file mode 100644 --- /dev/null +++ b/sys/dev/sound/fdt/audio_dai_if.m @@ -0,0 +1,95 @@ +#- +# Copyright (c) 2019 Oleksandr Tymoshenko +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +CODE { + #include + #include + #include +} + +INTERFACE audio_dai; + +# set DAI format for communications between CPU/codec nodes +METHOD int init { + device_t dev; + uint32_t format; +} + +# Initialize DAI and set up interrrupt handler +METHOD int setup_intr { + device_t dev; + driver_intr_t intr_handler; + void *intr_arg; +} + +# Setup mixers for codec node +METHOD int setup_mixer { + device_t dev; + device_t ausocdev; +} + +# setup clock speed +METHOD int set_sysclk { + device_t dev; + uint32_t rate; + int dai_dir; +} + +METHOD int trigger { + device_t dev; + int go; + int pcm_dir; +} + +METHOD struct pcmchan_caps* get_caps { + device_t dev; +} + +METHOD uint32_t get_ptr { + device_t dev; + int pcm_dir; +} + +# Set PCM channel format +METHOD uint32_t set_chanformat { + device_t dev; + uint32_t format; +} + +# Set PCM channel sampling rate +METHOD uint32_t set_chanspeed { + device_t dev; + uint32_t speed; +} + +# call DAI interrupt handler +# returns 1 if call to chn_intr required, 0 otherwise +METHOD int intr { + device_t dev; + struct snd_dbuf *play_buf; + struct snd_dbuf *rec_buf; +} diff --git a/sys/dev/sound/fdt/audio_soc.c b/sys/dev/sound/fdt/audio_soc.c new file mode 100644 --- /dev/null +++ b/sys/dev/sound/fdt/audio_soc.c @@ -0,0 +1,546 @@ +/*- + * Copyright (c) 2019 Oleksandr Tymoshenko + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include "audio_dai_if.h" + +#define AUDIO_BUFFER_SIZE 48000 * 4 + +struct audio_soc_aux_node { + SLIST_ENTRY(audio_soc_aux_node) link; + device_t dev; +}; + +struct audio_soc_channel { + struct audio_soc_softc *sc; /* parent device's softc */ + struct pcm_channel *pcm; /* PCM channel */ + struct snd_dbuf *buf; /* PCM buffer */ + int dir; /* direction */ +}; + +struct audio_soc_softc { + /* + * pcm_register assumes that sc is snddev_info, + * so this has to be first structure member for "compatiblity" + */ + struct snddev_info info; + device_t dev; + char *name; + struct intr_config_hook init_hook; + device_t cpu_dev; + device_t codec_dev; + SLIST_HEAD(, audio_soc_aux_node) aux_devs; + unsigned int mclk_fs; + struct audio_soc_channel play_channel; + struct audio_soc_channel rec_channel; + /* + * The format is from the CPU node, for CODEC node clock roles + * need to be reversed. + */ + uint32_t format; + uint32_t link_mclk_fs; +}; + +static struct ofw_compat_data compat_data[] = { + {"simple-audio-card", 1}, + {NULL, 0}, +}; + +static struct { + const char *name; + unsigned int fmt; +} ausoc_dai_formats[] = { + { "i2s", AUDIO_DAI_FORMAT_I2S }, + { "right_j", AUDIO_DAI_FORMAT_RJ }, + { "left_j", AUDIO_DAI_FORMAT_LJ }, + { "dsp_a", AUDIO_DAI_FORMAT_DSPA }, + { "dsp_b", AUDIO_DAI_FORMAT_DSPB }, + { "ac97", AUDIO_DAI_FORMAT_AC97 }, + { "pdm", AUDIO_DAI_FORMAT_PDM }, +}; + +static int audio_soc_probe(device_t dev); +static int audio_soc_attach(device_t dev); +static int audio_soc_detach(device_t dev); + +/* + * Invert master/slave roles for CODEC side of the node + */ +static uint32_t +audio_soc_reverse_clocks(uint32_t format) +{ + int fmt, pol, clk; + + fmt = AUDIO_DAI_FORMAT_FORMAT(format); + pol = AUDIO_DAI_FORMAT_POLARITY(format); + clk = AUDIO_DAI_FORMAT_CLOCK(format); + + switch (clk) { + case AUDIO_DAI_CLOCK_CBM_CFM: + clk = AUDIO_DAI_CLOCK_CBS_CFS; + break; + case AUDIO_DAI_CLOCK_CBS_CFM: + clk = AUDIO_DAI_CLOCK_CBM_CFS; + break; + case AUDIO_DAI_CLOCK_CBM_CFS: + clk = AUDIO_DAI_CLOCK_CBS_CFM; + break; + case AUDIO_DAI_CLOCK_CBS_CFS: + clk = AUDIO_DAI_CLOCK_CBM_CFM; + break; + } + + return AUDIO_DAI_FORMAT(fmt, pol, clk); +} + +static uint32_t +audio_soc_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksz) +{ + + return (blocksz); +} + +static int +audio_soc_chan_setformat(kobj_t obj, void *data, uint32_t format) +{ + + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + + ausoc_chan = data; + sc = ausoc_chan->sc; + + return AUDIO_DAI_SET_CHANFORMAT(sc->cpu_dev, format); +} + +static uint32_t +audio_soc_chan_setspeed(kobj_t obj, void *data, uint32_t speed) +{ + + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + uint32_t rate; + struct audio_soc_aux_node *aux_node; + + ausoc_chan = data; + sc = ausoc_chan->sc; + + if (sc->link_mclk_fs) { + rate = speed * sc->link_mclk_fs; + if (AUDIO_DAI_SET_SYSCLK(sc->cpu_dev, rate, AUDIO_DAI_CLOCK_IN)) + device_printf(sc->dev, "failed to set sysclk for CPU node\n"); + + if (AUDIO_DAI_SET_SYSCLK(sc->codec_dev, rate, AUDIO_DAI_CLOCK_OUT)) + device_printf(sc->dev, "failed to set sysclk for codec node\n"); + + SLIST_FOREACH(aux_node, &sc->aux_devs, link) { + if (AUDIO_DAI_SET_SYSCLK(aux_node->dev, rate, AUDIO_DAI_CLOCK_OUT)) + device_printf(sc->dev, "failed to set sysclk for aux node\n"); + } + } + + /* + * Let CPU node determine speed + */ + speed = AUDIO_DAI_SET_CHANSPEED(sc->cpu_dev, speed); + AUDIO_DAI_SET_CHANSPEED(sc->codec_dev, speed); + SLIST_FOREACH(aux_node, &sc->aux_devs, link) { + AUDIO_DAI_SET_CHANSPEED(aux_node->dev, speed); + } + + return (speed); +} + +static uint32_t +audio_soc_chan_getptr(kobj_t obj, void *data) +{ + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + + ausoc_chan = data; + sc = ausoc_chan->sc; + + return AUDIO_DAI_GET_PTR(sc->cpu_dev, ausoc_chan->dir); +} + +static void * +audio_soc_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + void *buffer; + + ausoc_chan = devinfo; + sc = ausoc_chan->sc; + buffer = malloc(AUDIO_BUFFER_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); + + if (sndbuf_setup(b, buffer, AUDIO_BUFFER_SIZE) != 0) { + free(buffer, M_DEVBUF); + return NULL; + } + + ausoc_chan->dir = dir; + ausoc_chan->buf = b; + ausoc_chan->pcm = c; + + return (devinfo); +} + +static int +audio_soc_chan_trigger(kobj_t obj, void *data, int go) +{ + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + struct audio_soc_aux_node *aux_node; + + ausoc_chan = (struct audio_soc_channel *)data; + sc = ausoc_chan->sc; + AUDIO_DAI_TRIGGER(sc->codec_dev, go, ausoc_chan->dir); + SLIST_FOREACH(aux_node, &sc->aux_devs, link) { + AUDIO_DAI_TRIGGER(aux_node->dev, go, ausoc_chan->dir); + } + + return AUDIO_DAI_TRIGGER(sc->cpu_dev, go, ausoc_chan->dir); +} + +static int +audio_soc_chan_free(kobj_t obj, void *data) +{ + + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + void *buffer; + + ausoc_chan = (struct audio_soc_channel *)data; + sc = ausoc_chan->sc; + + buffer = sndbuf_getbuf(ausoc_chan->buf); + if (buffer) + free(buffer, M_DEVBUF); + + return (0); +} + +static struct pcmchan_caps * +audio_soc_chan_getcaps(kobj_t obj, void *data) +{ + struct audio_soc_softc *sc; + struct audio_soc_channel *ausoc_chan; + + ausoc_chan = data; + sc = ausoc_chan->sc; + + return AUDIO_DAI_GET_CAPS(sc->cpu_dev); +} + +static kobj_method_t audio_soc_chan_methods[] = { + KOBJMETHOD(channel_init, audio_soc_chan_init), + KOBJMETHOD(channel_free, audio_soc_chan_free), + KOBJMETHOD(channel_setformat, audio_soc_chan_setformat), + KOBJMETHOD(channel_setspeed, audio_soc_chan_setspeed), + KOBJMETHOD(channel_setblocksize,audio_soc_chan_setblocksize), + KOBJMETHOD(channel_trigger, audio_soc_chan_trigger), + KOBJMETHOD(channel_getptr, audio_soc_chan_getptr), + KOBJMETHOD(channel_getcaps, audio_soc_chan_getcaps), + KOBJMETHOD_END +}; +CHANNEL_DECLARE(audio_soc_chan); + +static void +audio_soc_intr(void *arg) +{ + struct audio_soc_softc *sc; + int channel_intr_required; + + sc = (struct audio_soc_softc *)arg; + channel_intr_required = AUDIO_DAI_INTR(sc->cpu_dev, sc->play_channel.buf, sc->rec_channel.buf); + if (channel_intr_required & AUDIO_DAI_PLAY_INTR) + chn_intr(sc->play_channel.pcm); + if (channel_intr_required & AUDIO_DAI_REC_INTR) + chn_intr(sc->rec_channel.pcm); +} + +static int +audio_soc_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { + device_set_desc(dev, "simple-audio-card"); + return (BUS_PROBE_DEFAULT); + } + + return (ENXIO); +} + +static void +audio_soc_init(void *arg) +{ + struct audio_soc_softc *sc; + phandle_t node, child; + device_t daidev, auxdev; + uint32_t xref; + uint32_t *aux_devs; + int ncells, i; + struct audio_soc_aux_node *aux_node; + + sc = (struct audio_soc_softc *)arg; + config_intrhook_disestablish(&sc->init_hook); + + node = ofw_bus_get_node(sc->dev); + /* TODO: handle multi-link nodes */ + child = ofw_bus_find_child(node, "simple-audio-card,cpu"); + if (child == 0) { + device_printf(sc->dev, "cpu node is missing\n"); + return; + } + if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) { + device_printf(sc->dev, "missing sound-dai property in cpu node\n"); + return; + } + daidev = OF_device_from_xref(xref); + if (daidev == NULL) { + device_printf(sc->dev, "no driver attached to cpu node\n"); + return; + } + sc->cpu_dev = daidev; + + child = ofw_bus_find_child(node, "simple-audio-card,codec"); + if (child == 0) { + device_printf(sc->dev, "codec node is missing\n"); + return; + } + if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) { + device_printf(sc->dev, "missing sound-dai property in codec node\n"); + return; + } + daidev = OF_device_from_xref(xref); + if (daidev == NULL) { + device_printf(sc->dev, "no driver attached to codec node\n"); + return; + } + sc->codec_dev = daidev; + + /* Add AUX devices */ + aux_devs = NULL; + ncells = OF_getencprop_alloc_multi(node, "simple-audio-card,aux-devs", sizeof(*aux_devs), + (void **)&aux_devs); + + for (i = 0; i < ncells; i++) { + auxdev = OF_device_from_xref(aux_devs[i]); + if (auxdev == NULL) + device_printf(sc->dev, "warning: no driver attached to aux node\n"); + aux_node = (struct audio_soc_aux_node *)malloc(sizeof(*aux_node), M_DEVBUF, M_NOWAIT); + if (aux_node == NULL) { + device_printf(sc->dev, "failed to allocate aux node struct\n"); + return; + } + aux_node->dev = auxdev; + SLIST_INSERT_HEAD(&sc->aux_devs, aux_node, link); + } + + if (aux_devs) + OF_prop_free(aux_devs); + + if (AUDIO_DAI_INIT(sc->cpu_dev, sc->format)) { + device_printf(sc->dev, "failed to initialize cpu node\n"); + return; + } + + /* Reverse clock roles for CODEC */ + if (AUDIO_DAI_INIT(sc->codec_dev, audio_soc_reverse_clocks(sc->format))) { + device_printf(sc->dev, "failed to initialize codec node\n"); + return; + } + + SLIST_FOREACH(aux_node, &sc->aux_devs, link) { + if (AUDIO_DAI_INIT(aux_node->dev, audio_soc_reverse_clocks(sc->format))) { + device_printf(sc->dev, "failed to initialize aux node\n"); + return; + } + } + + if (pcm_register(sc->dev, sc, 1, 1)) { + device_printf(sc->dev, "failed to register PCM\n"); + return; + } + + pcm_getbuffersize(sc->dev, AUDIO_BUFFER_SIZE, AUDIO_BUFFER_SIZE, + AUDIO_BUFFER_SIZE); + sc->play_channel.sc = sc; + sc->rec_channel.sc = sc; + + pcm_addchan(sc->dev, PCMDIR_PLAY, &audio_soc_chan_class, &sc->play_channel); + pcm_addchan(sc->dev, PCMDIR_REC, &audio_soc_chan_class, &sc->rec_channel); + + pcm_setstatus(sc->dev, "at EXPERIMENT"); + + AUDIO_DAI_SETUP_INTR(sc->cpu_dev, audio_soc_intr, sc); + AUDIO_DAI_SETUP_MIXER(sc->codec_dev, sc->dev); + SLIST_FOREACH(aux_node, &sc->aux_devs, link) { + AUDIO_DAI_SETUP_MIXER(aux_node->dev, sc->dev); + } +} + +static int +audio_soc_attach(device_t dev) +{ + struct audio_soc_softc *sc; + char *name; + phandle_t node, cpu_child; + uint32_t xref; + int i, ret; + char tmp[32]; + unsigned int fmt, pol, clk; + bool frame_master, bitclock_master; + + sc = device_get_softc(dev); + sc->dev = dev; + node = ofw_bus_get_node(dev); + + ret = OF_getprop_alloc(node, "name", (void **)&name); + if (ret == -1) + name = "SoC audio"; + + sc->name = strdup(name, M_DEVBUF); + device_set_desc(dev, sc->name); + + if (ret != -1) + OF_prop_free(name); + + SLIST_INIT(&sc->aux_devs); + + ret = OF_getprop(node, "simple-audio-card,format", tmp, sizeof(tmp)); + if (ret == 0) { + for (i = 0; i < nitems(ausoc_dai_formats); i++) { + if (strcmp(tmp, ausoc_dai_formats[i].name) == 0) { + fmt = ausoc_dai_formats[i].fmt; + break; + } + } + if (i == nitems(ausoc_dai_formats)) + return (EINVAL); + } else + fmt = AUDIO_DAI_FORMAT_I2S; + + if (OF_getencprop(node, "simple-audio-card,mclk-fs", + &sc->link_mclk_fs, sizeof(sc->link_mclk_fs)) <= 0) + sc->link_mclk_fs = 0; + + /* Unless specified otherwise, CPU node is the master */ + frame_master = bitclock_master = true; + + cpu_child = ofw_bus_find_child(node, "simple-audio-card,cpu"); + + if ((OF_getencprop(node, "simple-audio-card,frame-master", &xref, sizeof(xref))) > 0) + frame_master = cpu_child == OF_node_from_xref(xref); + + if ((OF_getencprop(node, "simple-audio-card,bitclock-master", &xref, sizeof(xref))) > 0) + bitclock_master = cpu_child == OF_node_from_xref(xref); + + if (frame_master) { + clk = bitclock_master ? + AUDIO_DAI_CLOCK_CBM_CFM : AUDIO_DAI_CLOCK_CBS_CFM; + } else { + clk = bitclock_master ? + AUDIO_DAI_CLOCK_CBM_CFS : AUDIO_DAI_CLOCK_CBS_CFS; + } + + bool bitclock_inversion = OF_hasprop(node, "simple-audio-card,bitclock-inversion"); + bool frame_inversion = OF_hasprop(node, "simple-audio-card,frame-inversion"); + if (bitclock_inversion) { + pol = frame_inversion ? + AUDIO_DAI_POLARITY_IB_IF : AUDIO_DAI_POLARITY_IB_NF; + } else { + pol = frame_inversion ? + AUDIO_DAI_POLARITY_NB_IF : AUDIO_DAI_POLARITY_NB_NF; + } + + sc->format = AUDIO_DAI_FORMAT(fmt, pol, clk); + + sc->init_hook.ich_func = audio_soc_init; + sc->init_hook.ich_arg = sc; + if (config_intrhook_establish(&sc->init_hook) != 0) + return (ENOMEM); + + return (0); +} + +static int +audio_soc_detach(device_t dev) +{ + struct audio_soc_softc *sc; + struct audio_soc_aux_node *aux; + + sc = device_get_softc(dev); + if (sc->name) + free(sc->name, M_DEVBUF); + + while ((aux = SLIST_FIRST(&sc->aux_devs)) != NULL) { + SLIST_REMOVE_HEAD(&sc->aux_devs, link); + free(aux, M_DEVBUF); + } + + return (0); +} + +static device_method_t audio_soc_methods[] = { + /* device_if methods */ + DEVMETHOD(device_probe, audio_soc_probe), + DEVMETHOD(device_attach, audio_soc_attach), + DEVMETHOD(device_detach, audio_soc_detach), + + DEVMETHOD_END, +}; + +static driver_t audio_soc_driver = { + "pcm", + audio_soc_methods, + sizeof(struct audio_soc_softc), +}; + +DRIVER_MODULE(audio_soc, simplebus, audio_soc_driver, pcm_devclass, NULL, NULL); +MODULE_VERSION(audio_soc, 1); diff --git a/sys/dev/sound/fdt/dummy_codec.c b/sys/dev/sound/fdt/dummy_codec.c new file mode 100644 --- /dev/null +++ b/sys/dev/sound/fdt/dummy_codec.c @@ -0,0 +1,127 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Oleksandr Tymoshenko + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "opt_snd.h" +#include +#include +#include "audio_dai_if.h" + +static struct ofw_compat_data compat_data[] = { + { "dummy-codec", 1}, + { NULL, 0 } +}; + +struct dummy_codec_softc { + device_t dev; +}; + +static int dummy_codec_probe(device_t dev); +static int dummy_codec_attach(device_t dev); +static int dummy_codec_detach(device_t dev); + +static int +dummy_codec_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "Dummy Codec"); + return (BUS_PROBE_DEFAULT); +} + +static int +dummy_codec_attach(device_t dev) +{ + struct dummy_codec_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + sc->dev = dev; + + node = ofw_bus_get_node(dev); + OF_device_register_xref(OF_xref_from_node(node), dev); + + return (0); +} + +static int +dummy_codec_detach(device_t dev) +{ + + return (0); +} + +static int +dummy_codec_dai_init(device_t dev, uint32_t format) +{ + + return (0); +} + +static device_method_t dummy_codec_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, dummy_codec_probe), + DEVMETHOD(device_attach, dummy_codec_attach), + DEVMETHOD(device_detach, dummy_codec_detach), + + DEVMETHOD(audio_dai_init, dummy_codec_dai_init), + + DEVMETHOD_END +}; + +static driver_t dummy_codec_driver = { + "dummycodec", + dummy_codec_methods, + sizeof(struct dummy_codec_softc), +}; + +static devclass_t dummy_codec_devclass; + +DRIVER_MODULE(dummy_codec, simplebus, dummy_codec_driver, dummy_codec_devclass, 0, 0); +SIMPLEBUS_PNP_INFO(compat_data); diff --git a/sys/dev/sound/fdt/simple_amplifier.c b/sys/dev/sound/fdt/simple_amplifier.c new file mode 100644 --- /dev/null +++ b/sys/dev/sound/fdt/simple_amplifier.c @@ -0,0 +1,206 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Oleksandr Tymoshenko + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "opt_snd.h" +#include +#include +#include "audio_dai_if.h" + +static struct ofw_compat_data compat_data[] = { + { "simple-audio-amplifier", 1}, + { NULL, 0} +}; + +struct simple_amp_softc { + device_t dev; + regulator_t supply_vcc; + gpio_pin_t gpio_enable; + bool gpio_is_valid; +}; + +static int simple_amp_probe(device_t dev); +static int simple_amp_attach(device_t dev); +static int simple_amp_detach(device_t dev); + +static int +simple_amp_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "Simple Amplifier"); + return (BUS_PROBE_DEFAULT); +} + +static int +simple_amp_attach(device_t dev) +{ + struct simple_amp_softc *sc; + phandle_t node; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + node = ofw_bus_get_node(dev); + + error = gpio_pin_get_by_ofw_property(dev, node, + "enable-gpios", &sc->gpio_enable); + if (error != 0) + sc->gpio_is_valid = false; + else + sc->gpio_is_valid = true; + + error = regulator_get_by_ofw_property(dev, 0, "VCC-supply", + &sc->supply_vcc); + if (error != 0) + device_printf(dev, "no VCC supply"); + + OF_device_register_xref(OF_xref_from_node(node), dev); + + return (0); +} + +static int +simple_amp_detach(device_t dev) +{ + + return (0); +} + +static int +simple_amp_dai_init(device_t dev, uint32_t format) +{ + + return (0); +} + +static int +simple_amp_dai_trigger(device_t dev, int go, int pcm_dir) +{ + struct simple_amp_softc *sc; + int error; + + if ((pcm_dir != PCMDIR_PLAY) && (pcm_dir != PCMDIR_REC)) + return (EINVAL); + + sc = device_get_softc(dev); + error = 0; + switch (go) { + case PCMTRIG_START: + if (sc->supply_vcc != NULL) { + error = regulator_enable(sc->supply_vcc); + if (error != 0) { + device_printf(sc->dev, + "could not enable 'VCC' regulator\n"); + break; + } + } + + if (sc->gpio_is_valid) { + error = gpio_pin_set_active(sc->gpio_enable, 1); + if (error != 0) { + device_printf(sc->dev, + "could not set 'gpio-enable' gpio\n"); + break; + } + } + + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + if (sc->gpio_is_valid) { + error = gpio_pin_set_active(sc->gpio_enable, 0); + if (error != 0) { + device_printf(sc->dev, + "could not clear 'gpio-enable' gpio\n"); + break; + } + } + + if (sc->supply_vcc != NULL) { + error = regulator_disable(sc->supply_vcc); + if (error != 0) { + device_printf(sc->dev, + "could not disable 'VCC' regulator\n"); + break; + } + } + + break; + } + + return (error); +} + +static device_method_t simple_amp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, simple_amp_probe), + DEVMETHOD(device_attach, simple_amp_attach), + DEVMETHOD(device_detach, simple_amp_detach), + + DEVMETHOD(audio_dai_init, simple_amp_dai_init), + DEVMETHOD(audio_dai_trigger, simple_amp_dai_trigger), + + DEVMETHOD_END +}; + +static driver_t simple_amp_driver = { + "simpleamp", + simple_amp_methods, + sizeof(struct simple_amp_softc), +}; + +static devclass_t simple_amp_devclass; + +DRIVER_MODULE(simple_amp, simplebus, simple_amp_driver, simple_amp_devclass, 0, 0); +SIMPLEBUS_PNP_INFO(compat_data);