Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F111987234
D27831.id81381.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
74 KB
Referenced Files
None
Subscribers
None
D27831.id81381.diff
View Options
Index: sys/arm/allwinner/a64/sun50i_a64_acodec.c
===================================================================
--- /dev/null
+++ sys/arm/allwinner/a64/sun50i_a64_acodec.c
@@ -0,0 +1,488 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <sys/resource.h>
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+#include <dev/extres/regulator/regulator.h>
+
+#include "syscon_if.h"
+
+#include "opt_snd.h"
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/fdt/audio_dai.h>
+#include "audio_dai_if.h"
+#include "mixer_if.h"
+
+#define A64_PR_CFG 0x00
+#define A64_AC_PR_RST (1 << 28)
+#define A64_AC_PR_RW (1 << 24)
+#define A64_AC_PR_ADDR_MASK (0x1f << 16)
+#define A64_AC_PR_ADDR(n) (((n) & 0x1f) << 16)
+#define A64_ACDA_PR_WDAT_MASK (0xff << 8)
+#define A64_ACDA_PR_WDAT(n) (((n) & 0xff) << 8)
+#define A64_ACDA_PR_RDAT(n) ((n) & 0xff)
+
+#define A64_HP_CTRL 0x00
+#define A64_HPPA_EN (1 << 6)
+#define A64_HPVOL_MASK 0x3f
+#define A64_HPVOL(n) ((n) & 0x3f)
+#define A64_OL_MIX_CTRL 0x01
+#define A64_LMIXMUTE_LDAC (1 << 1)
+#define A64_OR_MIX_CTRL 0x02
+#define A64_RMIXMUTE_RDAC (1 << 1)
+#define A64_LINEOUT_CTRL0 0x05
+#define A64_LINEOUT_LEFT_EN (1 << 7)
+#define A64_LINEOUT_RIGHT_EN (1 << 6)
+#define A64_LINEOUT_EN (A64_LINEOUT_LEFT_EN|A64_LINEOUT_RIGHT_EN)
+#define A64_LINEOUT_CTRL1 0x06
+#define A64_LINEOUT_VOL __BITS(4,0)
+#define A64_MIC1_CTRL 0x07
+#define A64_MIC1G __BITS(6,4)
+#define A64_MIC1AMPEN (1 << 3)
+#define A64_MIC1BOOST __BITS(2,0)
+#define A64_MIC2_CTRL 0x08
+#define A64_MIC2_SEL (1 << 7)
+#define A64_MIC2G_MASK (7 << 4)
+#define A64_MIC2G(n) (((n) & 7) << 4)
+#define A64_MIC2AMPEN (1 << 3)
+#define A64_MIC2BOOST_MASK (7 << 0)
+#define A64_MIC2BOOST(n) (((n) & 7) << 0)
+#define A64_LINEIN_CTRL 0x09
+#define A64_LINEING __BITS(6,4)
+#define A64_MIX_DAC_CTRL 0x0a
+#define A64_DACAREN (1 << 7)
+#define A64_DACALEN (1 << 6)
+#define A64_RMIXEN (1 << 5)
+#define A64_LMIXEN (1 << 4)
+#define A64_RHPPAMUTE (1 << 3)
+#define A64_LHPPAMUTE (1 << 2)
+#define A64_RHPIS (1 << 1)
+#define A64_LHPIS (1 << 0)
+#define A64_L_ADCMIX_SRC 0x0b
+#define A64_R_ADCMIX_SRC 0x0c
+#define A64_ADCMIX_SRC_MIC1 (1 << 6)
+#define A64_ADCMIX_SRC_MIC2 (1 << 5)
+#define A64_ADCMIX_SRC_LINEIN (1 << 2)
+#define A64_ADCMIX_SRC_OMIXER (1 << 1)
+#define A64_ADC_CTRL 0x0d
+#define A64_ADCREN (1 << 7)
+#define A64_ADCLEN (1 << 6)
+#define A64_ADCG __BITS(2,0)
+#define A64_JACK_MIC_CTRL 0x1d
+#define A64_JACKDETEN (1 << 7)
+#define A64_INNERRESEN (1 << 6)
+#define A64_HMICBIASEN (1 << 5)
+#define A64_AUTOPLEN (1 << 1)
+
+#define A64CODEC_MIXER_DEVS ((1 << SOUND_MIXER_VOLUME) | \
+ (1 << SOUND_MIXER_MIC))
+
+static struct ofw_compat_data compat_data[] = {
+ { "allwinner,sun50i-a64-codec-analog", 1},
+ { NULL, 0 }
+};
+
+struct a64codec_softc {
+ device_t dev;
+ struct resource *res;
+ struct mtx mtx;
+ u_int regaddr; /* address for the sysctl */
+};
+
+#define A64CODEC_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define A64CODEC_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
+#define A64CODEC_READ(sc, reg) bus_read_4((sc)->res, (reg))
+#define A64CODEC_WRITE(sc, reg, val) bus_write_4((sc)->res, (reg), (val))
+
+static int a64codec_probe(device_t dev);
+static int a64codec_attach(device_t dev);
+static int a64codec_detach(device_t dev);
+
+static u_int
+a64_acodec_pr_read(struct a64codec_softc *sc, u_int addr)
+{
+ uint32_t val;
+
+ /* Read current value */
+ val = A64CODEC_READ(sc, A64_PR_CFG);
+
+ /* De-assert reset */
+ val |= A64_AC_PR_RST;
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Read mode */
+ val &= ~A64_AC_PR_RW;
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Set address */
+ val &= ~A64_AC_PR_ADDR_MASK;
+ val |= A64_AC_PR_ADDR(addr);
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Read data */
+ val = A64CODEC_READ(sc, A64_PR_CFG);
+ return A64_ACDA_PR_RDAT(val);
+}
+
+static void
+a64_acodec_pr_write(struct a64codec_softc *sc, u_int addr, u_int data)
+{
+ uint32_t val;
+
+ /* Read current value */
+ val = A64CODEC_READ(sc, A64_PR_CFG);
+
+ /* De-assert reset */
+ val |= A64_AC_PR_RST;
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Set address */
+ val &= ~A64_AC_PR_ADDR_MASK;
+ val |= A64_AC_PR_ADDR(addr);
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Write data */
+ val &= ~A64_ACDA_PR_WDAT_MASK;
+ val |= A64_ACDA_PR_WDAT(data);
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Write mode */
+ val |= A64_AC_PR_RW;
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+
+ /* Clear write mode */
+ val &= ~A64_AC_PR_RW;
+ A64CODEC_WRITE(sc, A64_PR_CFG, val);
+}
+
+static void
+a64_acodec_pr_set_clear(struct a64codec_softc *sc, u_int addr, u_int set, u_int clr)
+{
+ u_int old, new;
+
+ old = a64_acodec_pr_read(sc, addr);
+ new = set | (old & ~clr);
+ a64_acodec_pr_write(sc, addr, new);
+}
+
+static int
+a64codec_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, "Allwinner A64 Analog Codec");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+a64codec_attach(device_t dev)
+{
+ struct a64codec_softc *sc;
+ int error, rid;
+ phandle_t node;
+ regulator_t reg;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ rid = 0;
+ sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (!sc->res) {
+ device_printf(dev, "cannot allocate resource for device\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ if (regulator_get_by_ofw_property(dev, 0, "cpvdd-supply", ®) == 0) {
+ error = regulator_enable(reg);
+ if (error != 0) {
+ device_printf(dev, "cannot enable PHY regulator\n");
+ goto fail;
+ }
+ }
+
+ /* Right & Left Headphone PA enable */
+ a64_acodec_pr_set_clear(sc, A64_HP_CTRL,
+ A64_HPPA_EN, 0);
+
+ /* Microphone BIAS enable */
+ a64_acodec_pr_set_clear(sc, A64_JACK_MIC_CTRL,
+ A64_HMICBIASEN | A64_INNERRESEN, 0);
+
+ /* Unmute DAC to output mixer */
+ a64_acodec_pr_set_clear(sc, A64_OL_MIX_CTRL,
+ A64_LMIXMUTE_LDAC, 0);
+ a64_acodec_pr_set_clear(sc, A64_OR_MIX_CTRL,
+ A64_RMIXMUTE_RDAC, 0);
+
+ /* For now we work only with headphones */
+ a64_acodec_pr_set_clear(sc, A64_LINEOUT_CTRL0,
+ 0, A64_LINEOUT_EN);
+ a64_acodec_pr_set_clear(sc, A64_HP_CTRL,
+ A64_HPPA_EN, 0);
+
+ u_int val = a64_acodec_pr_read(sc, A64_HP_CTRL);
+ val &= ~(0x3f);
+ val |= 0x25;
+ a64_acodec_pr_write(sc, A64_HP_CTRL, val);
+
+ a64_acodec_pr_set_clear(sc, A64_MIC2_CTRL,
+ A64_MIC2AMPEN | A64_MIC2_SEL | A64_MIC2G(0x3) | A64_MIC2BOOST(0x4),
+ A64_MIC2G_MASK | A64_MIC2BOOST_MASK);
+
+ a64_acodec_pr_write(sc, A64_L_ADCMIX_SRC,
+ A64_ADCMIX_SRC_MIC2);
+ a64_acodec_pr_write(sc, A64_R_ADCMIX_SRC,
+ A64_ADCMIX_SRC_MIC2);
+
+ /* Max out MIC2 gain */
+ val = a64_acodec_pr_read(sc, A64_MIC2_CTRL);
+ val &= ~(0x7);
+ val |= (0x7);
+ val &= ~(7 << 4);
+ val |= (7 << 4);
+ a64_acodec_pr_write(sc, A64_MIC2_CTRL, val);
+
+ node = ofw_bus_get_node(dev);
+ OF_device_register_xref(OF_xref_from_node(node), dev);
+
+ return (0);
+
+fail:
+ a64codec_detach(dev);
+ return (error);
+}
+
+static int
+a64codec_detach(device_t dev)
+{
+ struct a64codec_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->res)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->res);
+ mtx_destroy(&sc->mtx);
+
+ return (0);
+}
+
+static int
+a64codec_mixer_init(struct snd_mixer *m)
+{
+
+ mix_setdevs(m, A64CODEC_MIXER_DEVS);
+
+ return (0);
+}
+
+static int
+a64codec_mixer_uninit(struct snd_mixer *m)
+{
+
+ return (0);
+}
+
+static int
+a64codec_mixer_reinit(struct snd_mixer *m)
+{
+
+ return (0);
+}
+
+static int
+a64codec_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
+{
+ struct a64codec_softc *sc;
+ struct mtx *mixer_lock;
+ uint8_t do_unlock;
+ u_int val;
+
+ sc = device_get_softc(mix_getdevinfo(m));
+ mixer_lock = mixer_get_lock(m);
+
+ if (mtx_owned(mixer_lock)) {
+ do_unlock = 0;
+ } else {
+ do_unlock = 1;
+ mtx_lock(mixer_lock);
+ }
+
+ right = left;
+
+ A64CODEC_LOCK(sc);
+ switch(dev) {
+ case SOUND_MIXER_VOLUME:
+ val = a64_acodec_pr_read(sc, A64_HP_CTRL);
+ val &= ~(A64_HPVOL_MASK);
+ val |= A64_HPVOL(left * 63 / 100);
+ a64_acodec_pr_write(sc, A64_HP_CTRL, val);
+ break;
+
+ case SOUND_MIXER_MIC:
+ val = a64_acodec_pr_read(sc, A64_MIC2_CTRL);
+ val &= ~(A64_MIC2BOOST_MASK);
+ val |= A64_MIC2BOOST(left * 7 / 100);
+ a64_acodec_pr_write(sc, A64_MIC2_CTRL, val);
+ break;
+ default:
+ break;
+ }
+ A64CODEC_UNLOCK(sc);
+
+ if (do_unlock) {
+ mtx_unlock(mixer_lock);
+ }
+
+ return (left | (right << 8));
+}
+
+static unsigned
+a64codec_mixer_setrecsrc(struct snd_mixer *m, unsigned src)
+{
+
+ return (0);
+}
+
+static kobj_method_t a64codec_mixer_methods[] = {
+ KOBJMETHOD(mixer_init, a64codec_mixer_init),
+ KOBJMETHOD(mixer_uninit, a64codec_mixer_uninit),
+ KOBJMETHOD(mixer_reinit, a64codec_mixer_reinit),
+ KOBJMETHOD(mixer_set, a64codec_mixer_set),
+ KOBJMETHOD(mixer_setrecsrc, a64codec_mixer_setrecsrc),
+ KOBJMETHOD_END
+};
+
+MIXER_DECLARE(a64codec_mixer);
+
+static int
+a64codec_dai_init(device_t dev, uint32_t format)
+{
+
+ return (0);
+}
+
+static int
+a64codec_dai_trigger(device_t dev, int go, int pcm_dir)
+{
+ struct a64codec_softc *sc = device_get_softc(dev);
+
+ if ((pcm_dir != PCMDIR_PLAY) && (pcm_dir != PCMDIR_REC))
+ return (EINVAL);
+
+ switch (go) {
+ case PCMTRIG_START:
+ if (pcm_dir == PCMDIR_PLAY) {
+ /* Enable DAC analog l/r channels, HP PA, and output mixer */
+ a64_acodec_pr_set_clear(sc, A64_MIX_DAC_CTRL,
+ A64_DACAREN | A64_DACALEN | A64_RMIXEN | A64_LMIXEN |
+ A64_RHPPAMUTE | A64_LHPPAMUTE, 0);
+ }
+ else if (pcm_dir == PCMDIR_REC) {
+ /* Enable ADC analog l/r channels */
+ a64_acodec_pr_set_clear(sc, A64_ADC_CTRL,
+ A64_ADCREN | A64_ADCLEN, 0);
+ }
+ break;
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+ if (pcm_dir == PCMDIR_PLAY) {
+ /* Disable DAC analog l/r channels, HP PA, and output mixer */
+ a64_acodec_pr_set_clear(sc, A64_MIX_DAC_CTRL,
+ 0, A64_DACAREN | A64_DACALEN | A64_RMIXEN | A64_LMIXEN |
+ A64_RHPPAMUTE | A64_LHPPAMUTE);
+ }
+ else if (pcm_dir == PCMDIR_REC) {
+ /* Disable ADC analog l/r channels */
+ a64_acodec_pr_set_clear(sc, A64_ADC_CTRL,
+ 0, A64_ADCREN | A64_ADCLEN);
+ }
+ break;
+ }
+
+ return (0);
+}
+
+static int
+a64codec_dai_setup_mixer(device_t dev, device_t pcmdev)
+{
+
+ mixer_init(pcmdev, &a64codec_mixer_class, dev);
+
+ return (0);
+}
+
+static device_method_t a64codec_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, a64codec_probe),
+ DEVMETHOD(device_attach, a64codec_attach),
+ DEVMETHOD(device_detach, a64codec_detach),
+
+ DEVMETHOD(audio_dai_init, a64codec_dai_init),
+ DEVMETHOD(audio_dai_setup_mixer, a64codec_dai_setup_mixer),
+ DEVMETHOD(audio_dai_trigger, a64codec_dai_trigger),
+
+ DEVMETHOD_END
+};
+
+static driver_t a64codec_driver = {
+ "a64codec",
+ a64codec_methods,
+ sizeof(struct a64codec_softc),
+};
+
+static devclass_t a64codec_devclass;
+
+DRIVER_MODULE(a64codec, simplebus, a64codec_driver, a64codec_devclass, 0, 0);
+SIMPLEBUS_PNP_INFO(compat_data);
Index: sys/arm/allwinner/aw_i2s.c
===================================================================
--- /dev/null
+++ sys/arm/allwinner/aw_i2s.c
@@ -0,0 +1,813 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <sys/resource.h>
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include "syscon_if.h"
+
+#include "opt_snd.h"
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/fdt/audio_dai.h>
+#include "audio_dai_if.h"
+
+#define FIFO_LEVEL 0x40
+
+#define DA_CTL 0x00
+#define DA_CTL_BCLK_OUT (1 << 18) /* sun8i */
+#define DA_CLK_LRCK_OUT (1 << 17) /* sun8i */
+#define DA_CTL_SDO_EN (1 << 8)
+#define DA_CTL_MS (1 << 5) /* sun4i */
+#define DA_CTL_PCM (1 << 4) /* sun4i */
+#define DA_CTL_MODE_SEL_MASK (3 << 4) /* sun8i */
+#define DA_CTL_MODE_SEL_PCM (0 << 4) /* sun8i */
+#define DA_CTL_MODE_SEL_LJ (1 << 4) /* sun8i */
+#define DA_CTL_MODE_SEL_RJ (2 << 4) /* sun8i */
+#define DA_CTL_TXEN (1 << 2)
+#define DA_CTL_RXEN (1 << 1)
+#define DA_CTL_GEN (1 << 0)
+#define DA_FAT0 0x04
+#define DA_FAT0_LRCK_PERIOD_MASK (0x3ff << 8) /* sun8i */
+#define DA_FAT0_LRCK_PERIOD(n) (((n) & 0x3fff) << 8) /* sun8i */
+#define DA_FAT0_LRCP_MASK (1 << 7)
+#define DA_LRCP_NORMAL (0 << 7)
+#define DA_LRCP_INVERTED (1 << 7)
+#define DA_FAT0_BCP_MASK (1 << 6)
+#define DA_BCP_NORMAL (0 << 6)
+#define DA_BCP_INVERTED (1 << 6)
+#define DA_FAT0_SR __BITS(5,4)
+#define DA_FAT0_WSS __BITS(3,2)
+#define DA_FAT0_FMT_MASK (3 << 0)
+#define DA_FMT_I2S 0
+#define DA_FMT_LJ 1
+#define DA_FMT_RJ 2
+#define DA_FAT1 0x08
+#define DA_ISTA 0x0c
+#define DA_ISTA_TXUI_INT (1 << 6)
+#define DA_ISTA_TXEI_INT (1 << 4)
+#define DA_ISTA_RXAI_INT (1 << 0)
+#define DA_RXFIFO 0x10
+#define DA_FCTL 0x14
+#define DA_FCTL_HUB_EN (1 << 31)
+#define DA_FCTL_FTX (1 << 25)
+#define DA_FCTL_FRX (1 << 24)
+#define DA_FCTL_TXTL_MASK (0x7f << 12)
+#define DA_FCTL_TXTL(v) (((v) & 0x7f) << 12)
+#define DA_FCTL_TXIM (1 << 2)
+#define DA_FSTA 0x18
+#define DA_FSTA_TXE_CNT(v) (((v) >> 16) & 0xff)
+#define DA_FSTA_RXA_CNT(v) ((v) & 0x3f)
+#define DA_INT 0x1c
+#define DA_INT_TX_DRQ (1 << 7)
+#define DA_INT_TXUI_EN (1 << 6)
+#define DA_INT_TXEI_EN (1 << 4)
+#define DA_INT_RX_DRQ (1 << 3)
+#define DA_INT_RXAI_EN (1 << 0)
+#define DA_TXFIFO 0x20
+#define DA_CLKD 0x24
+#define DA_CLKD_MCLKO_EN_SUN8I (1 << 8)
+#define DA_CLKD_MCLKO_EN_SUN4I (1 << 7)
+#define DA_CLKD_BCLKDIV_SUN8I(n) (((n) & 0xf) << 4)
+#define DA_CLKD_BCLKDIV_SUN8I_MASK (0xf << 4)
+#define DA_CLKD_BCLKDIV_SUN4I(n) (((n) & 7) << 4)
+#define DA_CLKD_BCLKDIV_SUN4I_MASK (7 << 4)
+#define DA_CLKD_BCLKDIV_8 3
+#define DA_CLKD_BCLKDIV_16 5
+#define DA_CLKD_MCLKDIV(n) (((n) & 0xff) << 0)
+#define DA_CLKD_MCLKDIV_MASK (0xf << 0)
+#define DA_CLKD_MCLKDIV_1 0
+#define DA_TXCNT 0x28
+#define DA_RXCNT 0x2c
+#define DA_CHCFG 0x30 /* sun8i */
+#define DA_CHCFG_TX_SLOT_HIZ (1 << 9)
+#define DA_CHCFG_TXN_STATE (1 << 8)
+#define DA_CHCFG_RX_SLOT_NUM_MASK (7 << 4)
+#define DA_CHCFG_RX_SLOT_NUM(n) (((n) & 7) << 4)
+#define DA_CHCFG_TX_SLOT_NUM_MASK (7 << 0)
+#define DA_CHCFG_TX_SLOT_NUM(n) (((n) & 7) << 0)
+
+#define DA_CHSEL_OFFSET(n) (((n) & 3) << 12) /* sun8i */
+#define DA_CHSEL_OFFSET_MASK (3 << 12) /* sun8i */
+#define DA_CHSEL_EN(n) (((n) & 0xff) << 4)
+#define DA_CHSEL_EN_MASK (0xff << 4)
+#define DA_CHSEL_SEL(n) (((n) & 7) << 0)
+#define DA_CHSEL_SEL_MASK (7 << 0)
+
+#define AUDIO_BUFFER_SIZE 48000 * 4
+
+#define AW_I2S_SAMPLE_RATE 48000
+#define AW_I2S_CLK_RATE 24576000
+
+enum sunxi_i2s_type {
+ SUNXI_I2S_SUN4I,
+ SUNXI_I2S_SUN8I,
+};
+
+struct sunxi_i2s_config {
+ const char *name;
+ enum sunxi_i2s_type type;
+ bus_size_t txchsel;
+ bus_size_t txchmap;
+ bus_size_t rxchsel;
+ bus_size_t rxchmap;
+};
+
+static const struct sunxi_i2s_config sun50i_a64_codec_config = {
+ .name = "Audio Codec (digital part)",
+ .type = SUNXI_I2S_SUN4I,
+ .txchsel = 0x30,
+ .txchmap = 0x34,
+ .rxchsel = 0x38,
+ .rxchmap = 0x3c,
+};
+
+static const struct sunxi_i2s_config sun8i_h3_config = {
+ .name = "I2S/PCM controller",
+ .type = SUNXI_I2S_SUN8I,
+ .txchsel = 0x34,
+ .txchmap = 0x44,
+ .rxchsel = 0x54,
+ .rxchmap = 0x58,
+};
+
+static const u_int sun4i_i2s_bclk_divmap[] = {
+ [0] = 2,
+ [1] = 4,
+ [2] = 6,
+ [3] = 8,
+ [4] = 12,
+ [5] = 16,
+};
+
+static const u_int sun4i_i2s_mclk_divmap[] = {
+ [0] = 1,
+ [1] = 2,
+ [2] = 4,
+ [3] = 6,
+ [4] = 8,
+ [5] = 12,
+ [6] = 16,
+ [7] = 24,
+};
+
+static const u_int sun8i_i2s_divmap[] = {
+ [1] = 1,
+ [2] = 2,
+ [3] = 4,
+ [4] = 6,
+ [5] = 8,
+ [6] = 12,
+ [7] = 16,
+ [8] = 24,
+ [9] = 32,
+ [10] = 48,
+ [11] = 64,
+ [12] = 96,
+ [13] = 128,
+ [14] = 176,
+ [15] = 192,
+};
+
+
+static struct ofw_compat_data compat_data[] = {
+ { "allwinner,sun50i-a64-codec-i2s", (uintptr_t)&sun50i_a64_codec_config },
+ { "allwinner,sun8i-h3-i2s", (uintptr_t)&sun8i_h3_config },
+ { NULL, 0 }
+};
+
+static struct resource_spec aw_i2s_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE },
+ { -1, 0 }
+};
+
+struct aw_i2s_softc {
+ device_t dev;
+ struct resource *res[2];
+ struct mtx mtx;
+ clk_t clk;
+ struct sunxi_i2s_config *cfg;
+ void * intrhand;
+ /* pointers to playback/capture buffers */
+ uint32_t play_ptr;
+ uint32_t rec_ptr;
+};
+
+#define I2S_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define I2S_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
+#define I2S_READ(sc, reg) bus_read_4((sc)->res[0], (reg))
+#define I2S_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val))
+#define I2S_TYPE(sc) ((sc)->cfg->type)
+
+static int aw_i2s_probe(device_t dev);
+static int aw_i2s_attach(device_t dev);
+static int aw_i2s_detach(device_t dev);
+
+static u_int
+sunxi_i2s_div_to_regval(const u_int *divmap, u_int divmaplen, u_int div)
+{
+ u_int n;
+
+ for (n = 0; n < divmaplen; n++)
+ if (divmap[n] == div)
+ return n;
+
+ return -1;
+}
+
+static uint32_t sc_fmt[] = {
+ SND_FORMAT(AFMT_S16_LE, 2, 0),
+ 0
+};
+static struct pcmchan_caps aw_i2s_caps = {AW_I2S_SAMPLE_RATE, AW_I2S_SAMPLE_RATE, sc_fmt, 0};
+
+
+static int
+aw_i2s_init(struct aw_i2s_softc *sc)
+{
+ uint32_t val;
+ int error;
+
+ error = clk_enable(sc->clk);
+ if (error != 0) {
+ device_printf(sc->dev, "cannot enable mod clock\n");
+ return (ENXIO);
+ }
+
+ /* Reset */
+ val = I2S_READ(sc, DA_CTL);
+ val &= ~(DA_CTL_TXEN|DA_CTL_RXEN|DA_CTL_GEN);
+ I2S_WRITE(sc, DA_CTL, val);
+
+ val = I2S_READ(sc, DA_FCTL);
+ val &= ~(DA_FCTL_FTX|DA_FCTL_FRX);
+ val &= ~(DA_FCTL_TXTL_MASK);
+ val |= DA_FCTL_TXTL(FIFO_LEVEL);
+ I2S_WRITE(sc, DA_FCTL, val);
+
+ I2S_WRITE(sc, DA_TXCNT, 0);
+ I2S_WRITE(sc, DA_RXCNT, 0);
+
+ /* Enable */
+ val = I2S_READ(sc, DA_CTL);
+ val |= DA_CTL_GEN;
+ I2S_WRITE(sc, DA_CTL, val);
+ val |= DA_CTL_SDO_EN;
+ I2S_WRITE(sc, DA_CTL, val);
+
+ /* Setup channels */
+ I2S_WRITE(sc, sc->cfg->txchmap, 0x76543210);
+ val = I2S_READ(sc, sc->cfg->txchsel);
+ val &= ~DA_CHSEL_EN_MASK;
+ val |= DA_CHSEL_EN(3);
+ val &= ~DA_CHSEL_SEL_MASK;
+ val |= DA_CHSEL_SEL(1);
+ I2S_WRITE(sc, sc->cfg->txchsel, val);
+ I2S_WRITE(sc, sc->cfg->rxchmap, 0x76543210);
+ val = I2S_READ(sc, sc->cfg->rxchsel);
+ val &= ~DA_CHSEL_EN_MASK;
+ val |= DA_CHSEL_EN(3);
+ val &= ~DA_CHSEL_SEL_MASK;
+ val |= DA_CHSEL_SEL(1);
+ I2S_WRITE(sc, sc->cfg->rxchsel, val);
+
+ if (I2S_TYPE(sc) == SUNXI_I2S_SUN8I) {
+ val = I2S_READ(sc, DA_CHCFG);
+ val &= ~DA_CHCFG_TX_SLOT_NUM_MASK;
+ val |= DA_CHCFG_TX_SLOT_NUM(1);
+ val &= ~DA_CHCFG_RX_SLOT_NUM_MASK;
+ val |= DA_CHCFG_RX_SLOT_NUM(1);
+ I2S_WRITE(sc, DA_CHCFG, val);
+ }
+
+ return (0);
+}
+
+static int
+aw_i2s_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, "Rockchip I2S");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+aw_i2s_attach(device_t dev)
+{
+ struct aw_i2s_softc *sc;
+ int error;
+ phandle_t node;
+ hwreset_t rst;
+ clk_t clk;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ sc->cfg = (void*)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+
+ mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ if (bus_alloc_resources(dev, aw_i2s_spec, sc->res) != 0) {
+ device_printf(dev, "cannot allocate resources for device\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ error = clk_get_by_ofw_name(dev, 0, "mod", &sc->clk);
+ if (error != 0) {
+ device_printf(dev, "cannot get i2s_clk clock\n");
+ goto fail;
+ }
+
+ error = clk_get_by_ofw_name(dev, 0, "apb", &clk);
+ if (error != 0) {
+ device_printf(dev, "cannot get APB clock\n");
+ goto fail;
+ }
+
+ error = clk_enable(clk);
+ if (error != 0) {
+ device_printf(dev, "cannot enable APB clock\n");
+ goto fail;
+ }
+
+ if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) {
+ error = hwreset_deassert(rst);
+ if (error != 0) {
+ device_printf(dev, "cannot de-assert reset\n");
+ goto fail;
+ }
+ }
+
+ aw_i2s_init(sc);
+
+ node = ofw_bus_get_node(dev);
+ OF_device_register_xref(OF_xref_from_node(node), dev);
+
+ return (0);
+
+fail:
+ aw_i2s_detach(dev);
+ return (error);
+}
+
+static int
+aw_i2s_detach(device_t dev)
+{
+ struct aw_i2s_softc *i2s;
+
+ i2s = device_get_softc(dev);
+
+ if (i2s->clk)
+ clk_release(i2s->clk);
+
+ if (i2s->intrhand != NULL)
+ bus_teardown_intr(i2s->dev, i2s->res[1], i2s->intrhand);
+
+ bus_release_resources(dev, aw_i2s_spec, i2s->res);
+ mtx_destroy(&i2s->mtx);
+
+ return (0);
+}
+
+static int
+aw_i2s_dai_init(device_t dev, uint32_t format)
+{
+ struct aw_i2s_softc *sc;
+ int fmt, pol, clk;
+ uint32_t ctl, fat0, chsel;
+ u_int offset;
+
+ sc = device_get_softc(dev);
+
+ fmt = AUDIO_DAI_FORMAT_FORMAT(format);
+ pol = AUDIO_DAI_FORMAT_POLARITY(format);
+ clk = AUDIO_DAI_FORMAT_CLOCK(format);
+
+ ctl = I2S_READ(sc, DA_CTL);
+ fat0 = I2S_READ(sc, DA_FAT0);
+
+ if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) {
+ fat0 &= ~DA_FAT0_FMT_MASK;
+ switch (fmt) {
+ case AUDIO_DAI_FORMAT_I2S:
+ fat0 |= DA_FMT_I2S;
+ break;
+ case AUDIO_DAI_FORMAT_RJ:
+ fat0 |= DA_FMT_RJ;
+ break;
+ case AUDIO_DAI_FORMAT_LJ:
+ fat0 |= DA_FMT_LJ;
+ break;
+ default:
+ return EINVAL;
+ }
+ ctl &= ~DA_CTL_PCM;
+ } else {
+ ctl &= ~DA_CTL_MODE_SEL_MASK;
+ switch (fmt) {
+ case AUDIO_DAI_FORMAT_I2S:
+ ctl |= DA_CTL_MODE_SEL_LJ;
+ offset = 1;
+ break;
+ case AUDIO_DAI_FORMAT_LJ:
+ ctl |= DA_CTL_MODE_SEL_LJ;
+ offset = 0;
+ break;
+ case AUDIO_DAI_FORMAT_RJ:
+ ctl |= DA_CTL_MODE_SEL_RJ;
+ offset = 0;
+ break;
+ case AUDIO_DAI_FORMAT_DSPA:
+ ctl |= DA_CTL_MODE_SEL_PCM;
+ offset = 1;
+ break;
+ case AUDIO_DAI_FORMAT_DSPB:
+ ctl |= DA_CTL_MODE_SEL_PCM;
+ offset = 0;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ chsel = I2S_READ(sc, sc->cfg->txchsel);
+ chsel &= ~DA_CHSEL_OFFSET_MASK;
+ chsel |= DA_CHSEL_OFFSET(offset);
+ I2S_WRITE(sc, sc->cfg->txchsel, chsel);
+
+ chsel = I2S_READ(sc, sc->cfg->rxchsel);
+ chsel &= ~DA_CHSEL_OFFSET_MASK;
+ chsel |= DA_CHSEL_OFFSET(offset);
+ I2S_WRITE(sc, sc->cfg->rxchsel, chsel);
+ }
+
+ fat0 &= ~(DA_FAT0_LRCP_MASK|DA_FAT0_BCP_MASK);
+ if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) {
+ if (AUDIO_DAI_POLARITY_INVERTED_BCLK(pol))
+ fat0 |= DA_BCP_INVERTED;
+ if (AUDIO_DAI_POLARITY_INVERTED_FRAME(pol))
+ fat0 |= DA_LRCP_INVERTED;
+ } else {
+ if (AUDIO_DAI_POLARITY_INVERTED_BCLK(pol))
+ fat0 |= DA_BCP_INVERTED;
+ if (!AUDIO_DAI_POLARITY_INVERTED_FRAME(pol))
+ fat0 |= DA_LRCP_INVERTED;
+
+ fat0 &= ~DA_FAT0_LRCK_PERIOD_MASK;
+ fat0 |= DA_FAT0_LRCK_PERIOD(32 - 1);
+ }
+
+ I2S_WRITE(sc, DA_CTL, ctl);
+ I2S_WRITE(sc, DA_FAT0, fat0);
+
+ return (0);
+}
+
+
+static int
+aw_i2s_dai_intr(device_t dev, struct snd_dbuf *play_buf, struct snd_dbuf *rec_buf)
+{
+ struct aw_i2s_softc *sc;
+ int ret = 0;
+ uint32_t val, status;
+
+ sc = device_get_softc(dev);
+
+ I2S_LOCK(sc);
+
+ status = I2S_READ(sc, DA_ISTA);
+ /* Clear interrupts */
+ // device_printf(sc->dev, "status: %08x\n", status);
+ I2S_WRITE(sc, DA_ISTA, status);
+
+ if (status & DA_ISTA_TXEI_INT) {
+ uint8_t *samples;
+ uint32_t count, size, readyptr, written, empty;
+
+ val = I2S_READ(sc, DA_FSTA);
+ empty = DA_FSTA_TXE_CNT(val);
+ count = sndbuf_getready(play_buf);
+ size = sndbuf_getsize(play_buf);
+ readyptr = sndbuf_getreadyptr(play_buf);
+
+ samples = (uint8_t*)sndbuf_getbuf(play_buf);
+ written = 0;
+ if (empty > count / 2)
+ empty = count / 2;
+ for (; empty > 0; empty--) {
+ val = (samples[readyptr++ % size] << 16);
+ val |= (samples[readyptr++ % size] << 24);
+ written += 2;
+ I2S_WRITE(sc, DA_TXFIFO, val);
+ }
+ sc->play_ptr += written;
+ sc->play_ptr %= size;
+ ret |= AUDIO_DAI_PLAY_INTR;
+ }
+
+ if (status & DA_ISTA_RXAI_INT) {
+ uint8_t *samples;
+ uint32_t count, size, freeptr, recorded, available;
+
+ val = I2S_READ(sc, DA_FSTA);
+ available = DA_FSTA_RXA_CNT(val);
+
+ count = sndbuf_getfree(rec_buf);
+ size = sndbuf_getsize(rec_buf);
+ freeptr = sndbuf_getfreeptr(rec_buf);
+ samples = (uint8_t*)sndbuf_getbuf(rec_buf);
+ recorded = 0;
+ if (available > count / 2)
+ available = count / 2;
+
+ for (; available > 0; available--) {
+ val = I2S_READ(sc, DA_RXFIFO);
+ samples[freeptr++ % size] = (val >> 16) & 0xff;
+ samples[freeptr++ % size] = (val >> 24) & 0xff;
+ recorded += 2;
+ }
+ sc->rec_ptr += recorded;
+ sc->rec_ptr %= size;
+ ret |= AUDIO_DAI_REC_INTR;
+ }
+
+ I2S_UNLOCK(sc);
+
+ return (ret);
+}
+
+static struct pcmchan_caps *
+aw_i2s_dai_get_caps(device_t dev)
+{
+ return (&aw_i2s_caps);
+}
+
+static int
+aw_i2s_dai_trigger(device_t dev, int go, int pcm_dir)
+{
+ struct aw_i2s_softc *sc = device_get_softc(dev);
+ uint32_t val;
+
+ if ((pcm_dir != PCMDIR_PLAY) && (pcm_dir != PCMDIR_REC))
+ return (EINVAL);
+
+ switch (go) {
+ case PCMTRIG_START:
+ if (pcm_dir == PCMDIR_PLAY) {
+ /* Flush FIFO */
+ val = I2S_READ(sc, DA_FCTL);
+ I2S_WRITE(sc, DA_FCTL, val | DA_FCTL_FTX);
+ I2S_WRITE(sc, DA_FCTL, val & ~DA_FCTL_FTX);
+
+ /* Reset TX sample counter */
+ I2S_WRITE(sc, DA_TXCNT, 0);
+
+ /* Enable TX block */
+ val = I2S_READ(sc, DA_CTL);
+ I2S_WRITE(sc, DA_CTL, val | DA_CTL_TXEN);
+
+ /* Enable TX underrun interrupt */
+ val = I2S_READ(sc, DA_INT);
+ I2S_WRITE(sc, DA_INT, val | DA_INT_TXEI_EN);
+ }
+
+ if (pcm_dir == PCMDIR_REC) {
+ /* Flush FIFO */
+ val = I2S_READ(sc, DA_FCTL);
+ I2S_WRITE(sc, DA_FCTL, val | DA_FCTL_FRX);
+ I2S_WRITE(sc, DA_FCTL, val & ~DA_FCTL_FRX);
+
+ /* Reset RX sample counter */
+ I2S_WRITE(sc, DA_RXCNT, 0);
+
+ /* Enable RX block */
+ val = I2S_READ(sc, DA_CTL);
+ I2S_WRITE(sc, DA_CTL, val | DA_CTL_RXEN);
+
+ /* Enable RX data available interrupt */
+ val = I2S_READ(sc, DA_INT);
+ I2S_WRITE(sc, DA_INT, val | DA_INT_RXAI_EN);
+ }
+
+ break;
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+ I2S_LOCK(sc);
+
+ if (pcm_dir == PCMDIR_PLAY) {
+ /* Disable TX block */
+ val = I2S_READ(sc, DA_CTL);
+ I2S_WRITE(sc, DA_CTL, val & ~DA_CTL_TXEN);
+
+ /* Enable TX underrun interrupt */
+ val = I2S_READ(sc, DA_INT);
+ I2S_WRITE(sc, DA_INT, val & ~DA_INT_TXEI_EN);
+
+ sc->play_ptr = 0;
+ } else {
+ /* Disable RX block */
+ val = I2S_READ(sc, DA_CTL);
+ I2S_WRITE(sc, DA_CTL, val & ~DA_CTL_RXEN);
+
+ /* Disable RX data available interrupt */
+ val = I2S_READ(sc, DA_INT);
+ I2S_WRITE(sc, DA_INT, val & ~DA_INT_RXAI_EN);
+
+ sc->rec_ptr = 0;
+ }
+
+ I2S_UNLOCK(sc);
+ break;
+ }
+
+ return (0);
+}
+
+static uint32_t
+aw_i2s_dai_get_ptr(device_t dev, int pcm_dir)
+{
+ struct aw_i2s_softc *sc;
+ uint32_t ptr;
+
+ sc = device_get_softc(dev);
+
+ I2S_LOCK(sc);
+ if (pcm_dir == PCMDIR_PLAY)
+ ptr = sc->play_ptr;
+ else
+ ptr = sc->rec_ptr;
+ I2S_UNLOCK(sc);
+
+ return ptr;
+}
+
+static int
+aw_i2s_dai_setup_intr(device_t dev, driver_intr_t intr_handler, void *intr_arg)
+{
+ struct aw_i2s_softc *sc = device_get_softc(dev);
+
+ if (bus_setup_intr(dev, sc->res[1],
+ INTR_TYPE_MISC | INTR_MPSAFE, NULL, intr_handler, intr_arg,
+ &sc->intrhand)) {
+ device_printf(dev, "cannot setup interrupt handler\n");
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static uint32_t
+aw_i2s_dai_set_chanformat(device_t dev, uint32_t format)
+{
+
+ return (0);
+}
+
+static int
+aw_i2s_dai_set_sysclk(device_t dev, unsigned int rate, int dai_dir)
+{
+ struct aw_i2s_softc *sc;
+ int bclk_val, mclk_val;
+ uint32_t val;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ error = clk_set_freq(sc->clk, AW_I2S_CLK_RATE, CLK_SET_ROUND_DOWN);
+ if (error != 0) {
+ device_printf(sc->dev,
+ "couldn't set mod clock rate to %u Hz: %d\n", AW_I2S_CLK_RATE, error);
+ return error;
+ }
+ error = clk_enable(sc->clk);
+ if (error != 0) {
+ device_printf(sc->dev,
+ "couldn't enable mod clock: %d\n", error);
+ return error;
+ }
+
+ const u_int bclk_prate = I2S_TYPE(sc) == SUNXI_I2S_SUN4I ? rate : AW_I2S_CLK_RATE;
+
+ const u_int bclk_div = bclk_prate / (2 * 32 * AW_I2S_SAMPLE_RATE);
+ const u_int mclk_div = AW_I2S_CLK_RATE / rate;
+
+ if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) {
+ bclk_val = sunxi_i2s_div_to_regval(sun4i_i2s_bclk_divmap,
+ nitems(sun4i_i2s_bclk_divmap), bclk_div);
+ mclk_val = sunxi_i2s_div_to_regval(sun4i_i2s_mclk_divmap,
+ nitems(sun4i_i2s_mclk_divmap), mclk_div);
+ } else {
+ bclk_val = sunxi_i2s_div_to_regval(sun8i_i2s_divmap,
+ nitems(sun8i_i2s_divmap), bclk_div);
+ mclk_val = sunxi_i2s_div_to_regval(sun8i_i2s_divmap,
+ nitems(sun8i_i2s_divmap), mclk_div);
+ }
+ if (bclk_val == -1 || mclk_val == -1) {
+ device_printf(sc->dev, "couldn't configure bclk/mclk dividers\n");
+ return EIO;
+ }
+
+ val = I2S_READ(sc, DA_CLKD);
+ if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) {
+ val |= DA_CLKD_MCLKO_EN_SUN4I;
+ val &= ~DA_CLKD_BCLKDIV_SUN4I_MASK;
+ val |= DA_CLKD_BCLKDIV_SUN4I(bclk_val);
+ } else {
+ val |= DA_CLKD_MCLKO_EN_SUN8I;
+ val &= ~DA_CLKD_BCLKDIV_SUN8I_MASK;
+ val |= DA_CLKD_BCLKDIV_SUN8I(bclk_val);
+ }
+ val &= ~DA_CLKD_MCLKDIV_MASK;
+ val |= DA_CLKD_MCLKDIV(mclk_val);
+ I2S_WRITE(sc, DA_CLKD, val);
+
+
+ return (0);
+}
+
+static uint32_t
+aw_i2s_dai_set_chanspeed(device_t dev, uint32_t speed)
+{
+
+ return (speed);
+}
+
+static device_method_t aw_i2s_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, aw_i2s_probe),
+ DEVMETHOD(device_attach, aw_i2s_attach),
+ DEVMETHOD(device_detach, aw_i2s_detach),
+
+ DEVMETHOD(audio_dai_init, aw_i2s_dai_init),
+ DEVMETHOD(audio_dai_setup_intr, aw_i2s_dai_setup_intr),
+ DEVMETHOD(audio_dai_set_sysclk, aw_i2s_dai_set_sysclk),
+ DEVMETHOD(audio_dai_set_chanspeed, aw_i2s_dai_set_chanspeed),
+ DEVMETHOD(audio_dai_set_chanformat, aw_i2s_dai_set_chanformat),
+ DEVMETHOD(audio_dai_intr, aw_i2s_dai_intr),
+ DEVMETHOD(audio_dai_get_caps, aw_i2s_dai_get_caps),
+ DEVMETHOD(audio_dai_trigger, aw_i2s_dai_trigger),
+ DEVMETHOD(audio_dai_get_ptr, aw_i2s_dai_get_ptr),
+
+ DEVMETHOD_END
+};
+
+static driver_t aw_i2s_driver = {
+ "i2s",
+ aw_i2s_methods,
+ sizeof(struct aw_i2s_softc),
+};
+
+static devclass_t aw_i2s_devclass;
+
+DRIVER_MODULE(aw_i2s, simplebus, aw_i2s_driver, aw_i2s_devclass, 0, 0);
+SIMPLEBUS_PNP_INFO(compat_data);
Index: sys/arm/allwinner/sun8i_codec.c
===================================================================
--- /dev/null
+++ sys/arm/allwinner/sun8i_codec.c
@@ -0,0 +1,417 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <sys/resource.h>
+#include <machine/bus.h>
+#include <sys/gpio.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+
+#include <dev/gpio/gpiobusvar.h>
+
+#include "opt_snd.h"
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/fdt/audio_dai.h>
+#include "audio_dai_if.h"
+
+#define SYSCLK_CTL 0x00c
+#define AIF1CLK_ENA (1 << 11)
+#define AIF1CLK_SRC_MASK (3 << 8)
+#define AIF1CLK_SRC_PLL (2 << 8)
+#define SYSCLK_ENA (1 << 3)
+#define SYSCLK_SRC (1 << 0)
+
+#define MOD_CLK_ENA 0x010
+#define MOD_RST_CTL 0x014
+#define MOD_AIF1 (1 << 15)
+#define MOD_ADC (1 << 3)
+#define MOD_DAC (1 << 2)
+
+#define SYS_SR_CTRL 0x018
+#define AIF1_FS_MASK (0xf << 12)
+#define AIF_FS_48KHZ (8 << 12)
+
+#define AIF1CLK_CTRL 0x040
+#define AIF1_MSTR_MOD (1 << 15)
+#define AIF1_BCLK_INV (1 << 14)
+#define AIF1_LRCK_INV (1 << 13)
+#define AIF1_BCLK_DIV_MASK (0xf << 9)
+#define AIF1_BCLK_DIV_16 (6 << 9)
+#define AIF1_LRCK_DIV_MASK (7 << 6)
+#define AIF1_LRCK_DIV_16 (0 << 6)
+#define AIF1_LRCK_DIV_64 (2 << 6)
+#define AIF1_WORD_SIZ_MASK (3 << 4)
+#define AIF1_WORD_SIZ_16 (1 << 4)
+#define AIF1_DATA_FMT_MASK (3 << 2)
+#define AIF1_DATA_FMT_I2S (0 << 2)
+#define AIF1_DATA_FMT_LJ (1 << 2)
+#define AIF1_DATA_FMT_RJ (2 << 2)
+#define AIF1_DATA_FMT_DSP (3 << 2)
+
+#define AIF1_ADCDAT_CTRL 0x044
+#define AIF1_ADC0L_ENA (1 << 15)
+#define AIF1_ADC0R_ENA (1 << 14)
+
+#define AIF1_DACDAT_CTRL 0x048
+#define AIF1_DAC0L_ENA (1 << 15)
+#define AIF1_DAC0R_ENA (1 << 14)
+
+#define AIF1_MXR_SRC 0x04c
+#define AIF1L_MXR_SRC_MASK (0xf << 12)
+#define AIF1L_MXR_SRC_AIF1 (0x8 << 12)
+#define AIF1L_MXR_SRC_ADC (0x2 << 12)
+#define AIF1R_MXR_SRC_MASK (0xf << 8)
+#define AIF1R_MXR_SRC_AIF1 (0x8 << 8)
+#define AIF1R_MXR_SRC_ADC (0x2 << 8)
+
+#define ADC_DIG_CTRL 0x100
+#define ADC_DIG_CTRL_ENAD (1 << 15)
+
+#define HMIC_CTRL1 0x110
+#define HMIC_CTRL1_N_MASK (0xf << 8)
+#define HMIC_CTRL1_N(n) (((n) & 0xf) << 8)
+#define HMIC_CTRL1_JACK_IN_IRQ_EN (1 << 4)
+#define HMIC_CTRL1_JACK_OUT_IRQ_EN (1 << 3)
+#define HMIC_CTRL1_MIC_DET_IRQ_EN (1 << 0)
+
+#define HMIC_CTRL2 0x114
+#define HMIC_CTRL2_MDATA_THRES __BITS(12,8)
+
+#define HMIC_STS 0x118
+#define HMIC_STS_MIC_PRESENT (1 << 6)
+#define HMIC_STS_JACK_DET_OIRQ (1 << 4)
+#define HMIC_STS_JACK_DET_IIRQ (1 << 3)
+#define HMIC_STS_MIC_DET_ST (1 << 0)
+
+#define DAC_DIG_CTRL 0x120
+#define DAC_DIG_CTRL_ENDA (1 << 15)
+
+#define DAC_MXR_SRC 0x130
+#define DACL_MXR_SRC_MASK (0xf << 12)
+#define DACL_MXR_SRC_AIF1_DAC0L (0x8 << 12)
+#define DACR_MXR_SRC_MASK (0xf << 8)
+#define DACR_MXR_SRC_AIF1_DAC0R (0x8 << 8)
+
+static struct ofw_compat_data compat_data[] = {
+ { "allwinner,sun8i-a33-codec", 1},
+ { NULL, 0 }
+};
+
+static struct resource_spec sun8i_codec_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE },
+ { -1, 0 }
+};
+
+struct sun8i_codec_softc {
+ device_t dev;
+ struct resource *res[2];
+ struct mtx mtx;
+ clk_t clk_gate;
+ clk_t clk_mod;
+ void * intrhand;
+};
+
+#define CODEC_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define CODEC_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
+#define CODEC_READ(sc, reg) bus_read_4((sc)->res[0], (reg))
+#define CODEC_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val))
+
+static int sun8i_codec_probe(device_t dev);
+static int sun8i_codec_attach(device_t dev);
+static int sun8i_codec_detach(device_t dev);
+
+static int
+sun8i_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, "Allwinner Codec");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+sun8i_codec_attach(device_t dev)
+{
+ struct sun8i_codec_softc *sc;
+ int error;
+ uint32_t val;
+ struct gpiobus_pin *pa_pin;
+ phandle_t node;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ node = ofw_bus_get_node(dev);
+
+ mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ if (bus_alloc_resources(dev, sun8i_codec_spec, sc->res) != 0) {
+ device_printf(dev, "cannot allocate resources for device\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ error = clk_get_by_ofw_name(dev, 0, "mod", &sc->clk_mod);
+ if (error != 0) {
+ device_printf(dev, "cannot get \"mod\" clock\n");
+ goto fail;
+ }
+
+ error = clk_get_by_ofw_name(dev, 0, "bus", &sc->clk_gate);
+ if (error != 0) {
+ device_printf(dev, "cannot get \"bus\" clock\n");
+ goto fail;
+ }
+
+ error = clk_enable(sc->clk_gate);
+ if (error != 0) {
+ device_printf(dev, "cannot enable \"bus\" clock\n");
+ goto fail;
+ }
+
+ /* Enable clocks */
+ val = CODEC_READ(sc, SYSCLK_CTL);
+ val |= AIF1CLK_ENA;
+ val &= ~AIF1CLK_SRC_MASK;
+ val |= AIF1CLK_SRC_PLL;
+ val |= SYSCLK_ENA;
+ val &= ~SYSCLK_SRC;
+ CODEC_WRITE(sc, SYSCLK_CTL, val);
+ CODEC_WRITE(sc, MOD_CLK_ENA, MOD_AIF1 | MOD_ADC | MOD_DAC);
+ CODEC_WRITE(sc, MOD_RST_CTL, MOD_AIF1 | MOD_ADC | MOD_DAC);
+
+ /* Enable digital parts */
+ CODEC_WRITE(sc, DAC_DIG_CTRL, DAC_DIG_CTRL_ENDA);
+ CODEC_WRITE(sc, ADC_DIG_CTRL, ADC_DIG_CTRL_ENAD);
+
+ /* Set AIF1 to 48 kHz */
+ val = CODEC_READ(sc, SYS_SR_CTRL);
+ val &= ~AIF1_FS_MASK;
+ val |= AIF_FS_48KHZ;
+ CODEC_WRITE(sc, SYS_SR_CTRL, val);
+
+ /* Set AIF1 to 16-bit */
+ val = CODEC_READ(sc, AIF1CLK_CTRL);
+ val &= ~AIF1_WORD_SIZ_MASK;
+ val |= AIF1_WORD_SIZ_16;
+ CODEC_WRITE(sc, AIF1CLK_CTRL, val);
+
+ /* Enable AIF1 DAC timelot 0 */
+ val = CODEC_READ(sc, AIF1_DACDAT_CTRL);
+ val |= AIF1_DAC0L_ENA;
+ val |= AIF1_DAC0R_ENA;
+ CODEC_WRITE(sc, AIF1_DACDAT_CTRL, val);
+
+ /* Enable AIF1 ADC timelot 0 */
+ val = CODEC_READ(sc, AIF1_ADCDAT_CTRL);
+ val |= AIF1_ADC0L_ENA;
+ val |= AIF1_ADC0R_ENA;
+ CODEC_WRITE(sc, AIF1_ADCDAT_CTRL, val);
+
+ /* DAC mixer source select */
+ val = CODEC_READ(sc, DAC_MXR_SRC);
+ val &= ~DACL_MXR_SRC_MASK;
+ val |= DACL_MXR_SRC_AIF1_DAC0L;
+ val &= ~DACR_MXR_SRC_MASK;
+ val |= DACR_MXR_SRC_AIF1_DAC0R;
+ CODEC_WRITE(sc, DAC_MXR_SRC, val);
+
+ /* ADC mixer source select */
+ val = CODEC_READ(sc, AIF1_MXR_SRC);
+ val &= ~AIF1L_MXR_SRC_MASK;
+ val |= AIF1L_MXR_SRC_ADC;
+ val &= ~AIF1R_MXR_SRC_MASK;
+ val |= AIF1R_MXR_SRC_ADC;
+ CODEC_WRITE(sc, AIF1_MXR_SRC, val);
+
+ /* Enable PA power */
+ /* Unmute PA */
+ if (gpio_pin_get_by_ofw_property(dev, node, "allwinner,pa-gpios",
+ &pa_pin) == 0) {
+ error = gpio_pin_set_active(pa_pin, 1);
+ if (error != 0)
+ device_printf(dev, "failed to unmute PA\n");
+ }
+
+ OF_device_register_xref(OF_xref_from_node(node), dev);
+
+ return (0);
+
+fail:
+ sun8i_codec_detach(dev);
+ return (error);
+}
+
+static int
+sun8i_codec_detach(device_t dev)
+{
+ struct sun8i_codec_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->clk_gate)
+ clk_release(sc->clk_gate);
+
+ if (sc->clk_mod)
+ clk_release(sc->clk_mod);
+
+ if (sc->intrhand != NULL)
+ bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand);
+
+ bus_release_resources(dev, sun8i_codec_spec, sc->res);
+ mtx_destroy(&sc->mtx);
+
+ return (0);
+}
+
+static int
+sun8i_codec_dai_init(device_t dev, uint32_t format)
+{
+ struct sun8i_codec_softc *sc;
+ int fmt, pol, clk;
+ uint32_t val;
+
+ sc = device_get_softc(dev);
+
+ fmt = AUDIO_DAI_FORMAT_FORMAT(format);
+ pol = AUDIO_DAI_FORMAT_POLARITY(format);
+ clk = AUDIO_DAI_FORMAT_CLOCK(format);
+
+ val = CODEC_READ(sc, AIF1CLK_CTRL);
+
+ val &= ~AIF1_DATA_FMT_MASK;
+ switch (fmt) {
+ case AUDIO_DAI_FORMAT_I2S:
+ val |= AIF1_DATA_FMT_I2S;
+ break;
+ case AUDIO_DAI_FORMAT_RJ:
+ val |= AIF1_DATA_FMT_RJ;
+ break;
+ case AUDIO_DAI_FORMAT_LJ:
+ val |= AIF1_DATA_FMT_LJ;
+ break;
+ case AUDIO_DAI_FORMAT_DSPA:
+ case AUDIO_DAI_FORMAT_DSPB:
+ val |= AIF1_DATA_FMT_DSP;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ val &= ~(AIF1_BCLK_INV|AIF1_LRCK_INV);
+ /* Codec LRCK polarity is inverted (datasheet is wrong) */
+ if (!AUDIO_DAI_POLARITY_INVERTED_FRAME(pol))
+ val |= AIF1_LRCK_INV;
+ if (AUDIO_DAI_POLARITY_INVERTED_BCLK(pol))
+ val |= AIF1_BCLK_INV;
+
+ switch (clk) {
+ case AUDIO_DAI_CLOCK_CBM_CFM:
+ val &= ~AIF1_MSTR_MOD; /* codec is master */
+ break;
+ case AUDIO_DAI_CLOCK_CBS_CFS:
+ val |= AIF1_MSTR_MOD; /* codec is slave */
+ break;
+ default:
+ return EINVAL;
+ }
+
+ val &= ~AIF1_LRCK_DIV_MASK;
+ val |= AIF1_LRCK_DIV_64;
+
+ val &= ~AIF1_BCLK_DIV_MASK;
+ val |= AIF1_BCLK_DIV_16;
+
+ CODEC_WRITE(sc, AIF1CLK_CTRL, val);
+
+ return (0);
+}
+
+static int
+sun8i_codec_dai_trigger(device_t dev, int go, int pcm_dir)
+{
+
+ return (0);
+}
+
+static int
+sun8i_codec_dai_setup_mixer(device_t dev, device_t pcmdev)
+{
+ struct sun8i_codec_softc *sc;
+
+ sc = device_get_softc(dev);
+ /* Do nothing for now */
+
+ return (0);
+}
+
+
+static device_method_t sun8i_codec_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, sun8i_codec_probe),
+ DEVMETHOD(device_attach, sun8i_codec_attach),
+ DEVMETHOD(device_detach, sun8i_codec_detach),
+
+ DEVMETHOD(audio_dai_init, sun8i_codec_dai_init),
+ DEVMETHOD(audio_dai_setup_mixer, sun8i_codec_dai_setup_mixer),
+ DEVMETHOD(audio_dai_trigger, sun8i_codec_dai_trigger),
+
+ DEVMETHOD_END
+};
+
+static driver_t sun8i_codec_driver = {
+ "sun8icodec",
+ sun8i_codec_methods,
+ sizeof(struct sun8i_codec_softc),
+};
+
+static devclass_t sun8i_codec_devclass;
+
+DRIVER_MODULE(sun8i_codec, simplebus, sun8i_codec_driver, sun8i_codec_devclass, 0, 0);
+SIMPLEBUS_PNP_INFO(compat_data);
Index: sys/conf/files.arm64
===================================================================
--- sys/conf/files.arm64
+++ sys/conf/files.arm64
@@ -28,9 +28,11 @@
arm/allwinner/a10_timer.c optional a10_timer fdt
arm/allwinner/a10_codec.c optional sound a10_codec
arm/allwinner/a31_dmac.c optional a31_dmac
+arm/allwinner/a64/sun50i_a64_acodec.c optional fdt sound
arm/allwinner/sunxi_dma_if.m optional a31_dmac
arm/allwinner/aw_cir.c optional evdev aw_cir fdt
arm/allwinner/aw_dwc3.c optional aw_dwc3 fdt
+arm/allwinner/aw_i2s.c optional fdt sound
arm/allwinner/aw_gpio.c optional gpio aw_gpio fdt
arm/allwinner/aw_mmc.c optional mmc aw_mmc fdt | mmccam aw_mmc fdt
arm/allwinner/aw_nmi.c optional aw_nmi fdt \
@@ -47,6 +49,7 @@
arm/allwinner/aw_wdog.c optional aw_wdog fdt
arm/allwinner/axp81x.c optional axp81x fdt
arm/allwinner/if_awg.c optional awg ext_resources syscon aw_sid nvmem fdt
+arm/allwinner/sun8i_codec.c optional fdt sound
# Allwinner clock driver
arm/allwinner/clkng/aw_ccung.c optional aw_ccu fdt
@@ -382,6 +385,10 @@
dev/safexcel/safexcel.c optional safexcel fdt
dev/sdhci/sdhci_xenon.c optional sdhci_xenon sdhci fdt
dev/uart/uart_cpu_arm64.c optional uart
+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
dev/uart/uart_dev_mu.c optional uart uart_mu
dev/uart/uart_dev_pl011.c optional uart pl011
dev/usb/controller/dwc_otg_hisi.c optional dwcotg fdt soc_hisi_hi6220
Index: sys/dev/sound/fdt/audio_dai.h
===================================================================
--- /dev/null
+++ sys/dev/sound/fdt/audio_dai.h
@@ -0,0 +1,72 @@
+/*-
+ * Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ *
+ * 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__ */
Index: sys/dev/sound/fdt/audio_dai_if.m
===================================================================
--- /dev/null
+++ sys/dev/sound/fdt/audio_dai_if.m
@@ -0,0 +1,95 @@
+#-
+# Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+#
+# 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 <sys/param.h>
+ #include <sys/bus.h>
+ #include <dev/sound/pcm/sound.h>
+}
+
+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;
+}
Index: sys/dev/sound/fdt/audio_soc.c
===================================================================
--- /dev/null
+++ sys/dev/sound/fdt/audio_soc.c
@@ -0,0 +1,546 @@
+/*-
+ * Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_platform.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/clock.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/endian.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/sound/fdt/audio_dai.h>
+#include <dev/sound/pcm/sound.h>
+#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);
Index: sys/dev/sound/fdt/dummy_codec.c
===================================================================
--- /dev/null
+++ sys/dev/sound/fdt/dummy_codec.c
@@ -0,0 +1,127 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <sys/resource.h>
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include "opt_snd.h"
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/fdt/audio_dai.h>
+#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);
Index: sys/dev/sound/fdt/simple_amplifier.c
===================================================================
--- /dev/null
+++ sys/dev/sound/fdt/simple_amplifier.c
@@ -0,0 +1,206 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <sys/resource.h>
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/extres/regulator/regulator.h>
+#include <dev/gpio/gpiobusvar.h>
+
+#include "opt_snd.h"
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/fdt/audio_dai.h>
+#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);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 12, 5:28 AM (1 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17113681
Default Alt Text
D27831.id81381.diff (74 KB)
Attached To
Mode
D27831: rockchip: add audio-related clocks to the CRU driver
Attached
Detach File
Event Timeline
Log In to Comment