Add aplaudio(4) and aplmca(4). The aplmca(4) driver controls the hardware
authorkettenis <kettenis@openbsd.org>
Wed, 3 Aug 2022 13:42:16 +0000 (13:42 +0000)
committerkettenis <kettenis@openbsd.org>
Wed, 3 Aug 2022 13:42:16 +0000 (13:42 +0000)
block that takes data from apldma(4), serializes it and sends it out on
the i2s ports.  The aplaudio(4) driver ties together aplmca(4) and various
codecs to present an audio(4) interface to the system.

This is still WIP, but good enough to play back audio on the speaker in the
M1 mini.

ok patrick@

sys/arch/arm64/conf/GENERIC
sys/arch/arm64/conf/files.arm64
sys/arch/arm64/dev/aplaudio.c [new file with mode: 0644]
sys/arch/arm64/dev/aplmca.c [new file with mode: 0644]
sys/arch/arm64/dev/aplmca.h [new file with mode: 0644]

index 10876ec..2f59c31 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.233 2022/07/14 19:06:29 kettenis Exp $
+# $OpenBSD: GENERIC,v 1.234 2022/08/03 13:42:16 kettenis Exp $
 #
 # GENERIC machine description file
 #
@@ -141,13 +141,18 @@ gpiokeys* at fdt?
 gpioleds*      at fdt?
 
 # Apple
+aplaudio*      at fdt?
+audio*         at aplaudio?
 aplcpu*                at fdt?
 apldart*       at fdt? early 1
+apldma*                at fdt?
 apldog*                at fdt? early 1
 apliic*                at fdt?
 iic*           at apliic?
 aplintc*       at fdt? early 1
 aplmbox*       at fdt?
+aplmca*                at fdt?
+aplnco*                at fdt?
 aplns*         at fdt?                 # Apple NVME Storage controllers
 nvme*          at aplns?
 aplpcie*       at fdt?
@@ -508,6 +513,7 @@ pcxrtc*             at iic?                 # PCF8563 RTC
 pcyrtc*                at iic?                 # PCF85063A/TP RTC
 rkpmic*                at iic?                 # RK808 PMIC
 sypwr*         at iic?                 # SY8106A regulator
+tascodec*      at iic?                 # TAS2770 audio codec
 tcpci*         at iic?                 # USB Type-C controller
 
 # GPIO "pin bus" drivers
index 920455b..f3f7157 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: files.arm64,v 1.57 2022/06/12 16:00:12 kettenis Exp $
+# $OpenBSD: files.arm64,v 1.58 2022/08/03 13:42:16 kettenis Exp $
 
 maxpartitions  16
 maxusers       2 8 128
@@ -139,6 +139,10 @@ device     agtimer
 attach agtimer at fdt
 file   arch/arm64/dev/agtimer.c                agtimer
 
+device aplaudio: audio
+attach aplaudio at fdt
+file   arch/arm64/dev/aplaudio.c               aplaudio
+
 device aplcpu
 attach aplcpu at fdt
 file   arch/arm64/dev/aplcpu.c                 aplcpu
@@ -175,6 +179,10 @@ device     aplmbox
 attach aplmbox at fdt
 file   arch/arm64/dev/aplmbox.c                aplmbox
 
+device aplmca
+attach aplmca at fdt
+file   arch/arm64/dev/aplmca.c                 aplmca
+
 device aplnco
 attach aplnco at fdt
 file   arch/arm64/dev/aplnco.c                 aplnco
diff --git a/sys/arch/arm64/dev/aplaudio.c b/sys/arch/arm64/dev/aplaudio.c
new file mode 100644 (file)
index 0000000..ee61f81
--- /dev/null
@@ -0,0 +1,475 @@
+/*     $OpenBSD: aplaudio.c,v 1.1 2022/08/03 13:42:16 kettenis Exp $   */
+/*
+ * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
+ * Copyright (c) 2020 Patrick Wildt <patrick@blueri.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/fdt.h>
+
+#include <sys/audioio.h>
+#include <dev/audio_if.h>
+
+#include <arm64/dev/aplmca.h>
+
+struct aplaudio_softc {
+       struct device           sc_dev;
+
+       uint32_t                sc_mclk_fs;
+
+       struct dai_device       *sc_dai_cpu;
+       struct dai_device       *sc_dai_codec;
+};
+
+void   aplaudio_set_format(struct aplaudio_softc *, uint32_t,
+           uint32_t, uint32_t);
+
+int    aplaudio_open(void *, int);
+void   aplaudio_close(void *);
+int    aplaudio_set_params(void *, int, int,
+           struct audio_params *, struct audio_params *);
+void   *aplaudio_allocm(void *, int, size_t, int, int);
+void   aplaudio_freem(void *, void *, int);
+int    aplaudio_set_port(void *, mixer_ctrl_t *);
+int    aplaudio_get_port(void *, mixer_ctrl_t *);
+int    aplaudio_query_devinfo(void *, mixer_devinfo_t *);
+int    aplaudio_get_props(void *);
+int    aplaudio_round_blocksize(void *, int);
+size_t aplaudio_round_buffersize(void *, int, size_t);
+int    aplaudio_trigger_output(void *, void *, void *, int,
+           void (*)(void *), void *, struct audio_params *);
+int    aplaudio_trigger_input(void *, void *, void *, int,
+           void (*)(void *), void *, struct audio_params *);
+int    aplaudio_halt_output(void *);
+int    aplaudio_halt_input(void *);
+
+const struct audio_hw_if aplaudio_hw_if = {
+       .open = aplaudio_open,
+       .close = aplaudio_close,
+       .set_params = aplaudio_set_params,
+       .allocm = aplaudio_allocm,
+       .freem = aplaudio_freem,
+       .set_port = aplaudio_set_port,
+       .get_port = aplaudio_get_port,
+       .query_devinfo = aplaudio_query_devinfo,
+       .get_props = aplaudio_get_props,
+       .round_blocksize = aplaudio_round_blocksize,
+       .round_buffersize = aplaudio_round_buffersize,
+       .trigger_output = aplaudio_trigger_output,
+       .trigger_input = aplaudio_trigger_input,
+       .halt_output = aplaudio_halt_output,
+       .halt_input = aplaudio_halt_input,
+};
+
+int    aplaudio_match(struct device *, void *, void *);
+void   aplaudio_attach(struct device *, struct device *, void *);
+
+const struct cfattach aplaudio_ca = {
+       sizeof (struct aplaudio_softc), aplaudio_match, aplaudio_attach
+};
+
+struct cfdriver aplaudio_cd = {
+       NULL, "aplaudio", DV_DULL
+};
+
+int
+aplaudio_match(struct device *parent, void *match, void *aux)
+{
+       struct fdt_attach_args *faa = aux;
+
+       return OF_is_compatible(faa->fa_node, "apple,macaudio");
+}
+
+void
+aplaudio_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct aplaudio_softc *sc = (struct aplaudio_softc *)self;
+       struct fdt_attach_args *faa = aux;
+       uint32_t fmt, pol, clk;
+       uint32_t node, cpu, codec;
+       uint32_t phandle;
+
+       printf("\n");
+
+       for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
+               cpu = OF_getnodebyname(node, "cpu");
+               if (cpu == 0)
+                       continue;
+
+               sc->sc_dai_cpu = aplmca_alloc_cluster(cpu);
+               if (sc->sc_dai_cpu == NULL)
+                       continue;
+
+               codec = OF_getnodebyname(node, "codec");
+               if (codec == 0)
+                       continue;
+
+               phandle = 0;
+               OF_getpropintarray(codec, "sound-dai",
+                    &phandle, sizeof(phandle));
+
+               sc->sc_dai_codec = dai_byphandle(phandle);
+               if (sc->sc_dai_codec == NULL)
+                       continue;
+
+               sc->sc_mclk_fs = OF_getpropint(node, "mclk-fs", 0);
+
+               /* XXX Parameters are missing from the device tree? */
+               fmt = DAI_FORMAT_LJ;
+               pol = 0;
+               clk = DAI_CLOCK_CFM | DAI_CLOCK_CBM;
+               aplaudio_set_format(sc, fmt, pol, clk);
+
+               audio_attach_mi(&aplaudio_hw_if, sc, NULL, self);
+
+               /* XXX Only attach the first set of interfaces for now. */
+               return;
+       }
+}
+
+void
+aplaudio_set_format(struct aplaudio_softc *sc, uint32_t fmt, uint32_t pol,
+    uint32_t clk)
+{
+       if (sc->sc_dai_cpu->dd_set_format)
+               sc->sc_dai_cpu->dd_set_format(sc->sc_dai_cpu->dd_cookie,
+                   fmt, pol, clk);
+       if (sc->sc_dai_codec->dd_set_format)
+               sc->sc_dai_codec->dd_set_format(sc->sc_dai_codec->dd_cookie,
+                   fmt, pol, clk);
+}
+
+int
+aplaudio_open(void *cookie, int flags)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+       int error;
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->open) {
+               error = hwif->open(dai->dd_cookie, flags);
+               if (error) {
+                       aplaudio_close(cookie);
+                       return error;
+               }
+       }
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->open) {
+               error = hwif->open(dai->dd_cookie, flags);
+               if (error) {
+                       aplaudio_close(cookie);
+                       return error;
+               }
+       }
+
+       return 0;
+}
+
+void
+aplaudio_close(void *cookie)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->close)
+               hwif->close(dai->dd_cookie);
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->close)
+               hwif->close(dai->dd_cookie);
+}
+
+int
+aplaudio_set_params(void *cookie, int setmode, int usemode,
+    struct audio_params *play, struct audio_params *rec)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+       uint32_t rate;
+       int error;
+
+       if (sc->sc_mclk_fs) {
+               if (setmode & AUMODE_PLAY)
+                       rate = play->sample_rate * sc->sc_mclk_fs;
+               else
+                       rate = rec->sample_rate * sc->sc_mclk_fs;
+
+               dai = sc->sc_dai_codec;
+               if (dai->dd_set_sysclk) {
+                       error = dai->dd_set_sysclk(dai->dd_cookie, rate);
+                       if (error)
+                               return error;
+               }
+
+               dai = sc->sc_dai_cpu;
+               if (dai->dd_set_sysclk) {
+                       error = dai->dd_set_sysclk(dai->dd_cookie, rate);
+                       if (error)
+                               return error;
+               }
+       }
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->set_params) {
+               error = hwif->set_params(dai->dd_cookie,
+                   setmode, usemode, play, rec);
+               if (error)
+                       return error;
+       }
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->set_params) {
+               error = hwif->set_params(dai->dd_cookie,
+                   setmode, usemode, play, rec);
+               if (error)
+                       return error;
+       }
+
+       return 0;
+}
+
+void *
+aplaudio_allocm(void *cookie, int direction, size_t size, int type,
+    int flags)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_cpu;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->allocm)
+               return hwif->allocm(dai->dd_cookie,
+                   direction, size, type, flags);
+
+       return NULL;
+}
+
+void
+aplaudio_freem(void *cookie, void *addr, int type)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_cpu;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->freem)
+               hwif->freem(dai->dd_cookie, addr, type);
+}
+
+int
+aplaudio_set_port(void *cookie, mixer_ctrl_t *cp)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_codec;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->set_port)
+               return hwif->set_port(dai->dd_cookie, cp);
+
+       return ENXIO;
+}
+
+int
+aplaudio_get_port(void *cookie, mixer_ctrl_t *cp)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_codec;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->get_port)
+               return hwif->get_port(dai->dd_cookie, cp);
+
+       return ENXIO;
+}
+
+int
+aplaudio_query_devinfo(void *cookie, mixer_devinfo_t *dip)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_codec;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->query_devinfo)
+               return hwif->query_devinfo(dai->dd_cookie, dip);
+
+       return ENXIO;
+}
+
+int
+aplaudio_get_props(void *cookie)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_cpu;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->get_props)
+               return hwif->get_props(dai->dd_cookie);
+
+       return 0;
+}
+
+int
+aplaudio_round_blocksize(void *cookie, int block)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_cpu;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->round_blocksize)
+               return hwif->round_blocksize(dai->dd_cookie, block);
+
+       return block;
+}
+
+size_t
+aplaudio_round_buffersize(void *cookie, int direction, size_t bufsize)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai = sc->sc_dai_cpu;
+       const struct audio_hw_if *hwif = dai->dd_hw_if;
+
+       if (hwif->round_buffersize)
+               return hwif->round_buffersize(dai->dd_cookie,
+                   direction, bufsize);
+
+       return bufsize;
+}
+
+int
+aplaudio_trigger_output(void *cookie, void *start, void *end, int blksize,
+    void (*intr)(void *), void *arg, struct audio_params *param)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+       int error;
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->trigger_output) {
+               error = hwif->trigger_output(dai->dd_cookie,
+                   start, end, blksize, intr, arg, param);
+               if (error) {
+                       aplaudio_halt_output(cookie);
+                       return error;
+               }
+       }
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->trigger_output) {
+               error = hwif->trigger_output(dai->dd_cookie,
+                   start, end, blksize, intr, arg, param);
+               if (error) {
+                       aplaudio_halt_output(cookie);
+                       return error;
+               }
+       }
+
+       return 0;
+}
+
+int
+aplaudio_trigger_input(void *cookie, void *start, void *end, int blksize,
+    void (*intr)(void *), void *arg, struct audio_params *param)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+       int error;
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->trigger_input) {
+               error = hwif->trigger_input(dai->dd_cookie,
+                   start, end, blksize, intr, arg, param);
+               if (error) {
+                       aplaudio_halt_input(cookie);
+                       return error;
+               }
+       }
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->trigger_input) {
+               error = hwif->trigger_input(dai->dd_cookie,
+                   start, end, blksize, intr, arg, param);
+               if (error) {
+                       aplaudio_halt_input(cookie);
+                       return error;
+               }
+       }
+
+       return 0;
+}
+
+int
+aplaudio_halt_output(void *cookie)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->halt_output)
+               hwif->halt_output(dai->dd_cookie);
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->halt_output)
+               hwif->halt_output(dai->dd_cookie);
+
+       return 0;
+}
+
+int
+aplaudio_halt_input(void *cookie)
+{
+       struct aplaudio_softc *sc = cookie;
+       struct dai_device *dai;
+       const struct audio_hw_if *hwif;
+
+       dai = sc->sc_dai_codec;
+       hwif = dai->dd_hw_if;
+       if (hwif->halt_input)
+               hwif->halt_input(dai->dd_cookie);
+
+       dai = sc->sc_dai_cpu;
+       hwif = dai->dd_hw_if;
+       if (hwif->halt_input)
+               hwif->halt_input(dai->dd_cookie);
+
+       return 0;
+}
diff --git a/sys/arch/arm64/dev/aplmca.c b/sys/arch/arm64/dev/aplmca.c
new file mode 100644 (file)
index 0000000..116d116
--- /dev/null
@@ -0,0 +1,494 @@
+/*     $OpenBSD: aplmca.c,v 1.1 2022/08/03 13:42:16 kettenis Exp $     */
+/*
+ * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/audioio.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/ofw_power.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/audio_if.h>
+
+#include <arm64/dev/apldma.h>
+
+/*
+ * This driver is based on preliminary device tree bindings and will
+ * almost certainly need changes once the official bindings land in
+ * mainline Linux.  Support for these preliminary bindings will be
+ * dropped as soon as official bindings are available.
+ */
+
+#define MCA_CL_STRIDE          0x4000
+#define MCA_SW_STRIDE          0x8000
+#define MCA_SERDES_TXA         0x0300
+
+#define MCA_STATUS(idx)                        ((idx) * MCA_CL_STRIDE + 0x0000)
+#define  MCA_STATUS_MCLK_EN            (1 << 0)
+
+#define MCA_SYNCGEN_STATUS(idx)                ((idx) * MCA_CL_STRIDE + 0x0100)
+#define  MCA_SYNCGEN_STATUS_EN         (1 << 0)
+#define MCA_SYNCGEN_MCLK_SEL(idx)      ((idx) * MCA_CL_STRIDE + 0x0104)
+#define MCA_SYNCGEN_HI_PERIOD(idx)     ((idx) * MCA_CL_STRIDE + 0x0108)
+#define MCA_SYNCGEN_LO_PERIOD(idx)     ((idx) * MCA_CL_STRIDE + 0x010c)
+
+#define MCA_SERDES_BASE(idx, off)      ((idx) * MCA_CL_STRIDE + (off))
+#define MCA_SERDES_STATUS(idx, off)    (MCA_SERDES_BASE(idx, off) + 0x0000)
+#define  MCA_SERDES_STATUS_EN          (1 << 0)
+#define  MCA_SERDES_STATUS_RST         (1 << 1)
+#define MCA_SERDES_CONF(idx, off)      (MCA_SERDES_BASE(idx, off) + 0x0004)
+#define MCA_SERDES_CONF_NSLOTS_MASK    (0xf << 0)
+#define MCA_SERDES_CONF_NSLOTS_SHIFT   0
+#define MCA_SERDES_CONF_WIDTH_MASK     (0x1f << 4)
+#define MCA_SERDES_CONF_WIDTH_32BIT    (0x10 << 4)
+#define MCA_SERDES_CONF_BCLK_POL       (1 << 10)
+#define MCA_SERDES_CONF_MAGIC          (0x7 << 12)
+#define MCA_SERDES_CONF_SYNC_SEL_MASK  (0x7 << 16)
+#define MCA_SERDES_CONF_SYNC_SEL_SHIFT 16
+#define MCA_SERDES_BITSTART(idx, off)  (MCA_SERDES_BASE(idx, off) + 0x0008)
+#define MCA_SERDES_CHANMASK0(idx, off) (MCA_SERDES_BASE(idx, off) + 0x000c)
+#define MCA_SERDES_CHANMASK1(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0010)
+#define MCA_SERDES_CHANMASK2(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0014)
+#define MCA_SERDES_CHANMASK3(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0018)
+
+#define MCA_PORT_ENABLE(idx)           ((idx) * MCA_CL_STRIDE + 0x0600)
+#define  MCA_PORT_ENABLE_CLOCKS                (0x3 << 1)
+#define  MCA_PORT_ENABLE_TX_DATA       (1 << 3)
+#define MCA_PORT_CLOCK_SEL(idx)                ((idx) * MCA_CL_STRIDE + 0x0604)
+#define  MCA_PORT_CLOCK_SEL_SHIFT      8
+#define MCA_PORT_DATA_SEL(idx)         ((idx) * MCA_CL_STRIDE + 0x0608)
+#define  MCA_PORT_DATA_SEL_TXA(idx)    (1 << ((idx) * 2))
+#define  MCA_PORT_DATA_SEL_TXB(idx)    (2 << ((idx) * 2))
+
+#define MCA_DMA_ADAPTER_A(idx)         ((idx) * MCA_SW_STRIDE + 0x0000)
+#define MCA_DMA_ADAPTER_B(idx)         ((idx) * MCA_SW_STRIDE + 0x4000)
+#define  MCA_DMA_ADAPTER_TX_LSB_PAD_SHIFT      0
+#define  MCA_DMA_ADAPTER_TX_NCHANS_SHIFT       5
+#define  MCA_DMA_ADAPTER_NCHANS_SHIFT          20
+
+
+#define HREAD4(sc, reg)                                                        \
+       (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
+#define HWRITE4(sc, reg, val)                                          \
+       bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
+#define HSET4(sc, reg, bits)                                           \
+       HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
+#define HCLR4(sc, reg, bits)                                           \
+       HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
+
+struct aplmca_dai {
+       struct aplmca_softc     *ad_sc;
+       struct dai_device       ad_dai;
+       int                     ad_cluster;
+
+       struct apldma_channel   *ad_ac;
+       void                    *ad_pbuf;
+};
+
+struct aplmca_softc {
+       struct device           sc_dev;
+       bus_space_tag_t         sc_iot;
+       bus_space_handle_t      sc_ioh;
+       bus_space_handle_t      sc_sw_ioh;
+
+       int                     sc_node;
+       uint32_t                sc_phandle;
+
+       int                     sc_nclusters;
+       struct aplmca_dai       *sc_ad;
+};
+
+int    aplmca_set_format(void *, uint32_t, uint32_t, uint32_t);
+int    aplmca_set_sysclk(void *, uint32_t);
+
+int    aplmca_set_params(void *, int, int,
+           struct audio_params *, struct audio_params *);
+void   *aplmca_allocm(void *, int, size_t, int, int);
+void   aplmca_freem(void *, void *, int);
+int    aplmca_get_props(void *);
+int    aplmca_trigger_output(void *, void *, void *, int,
+           void (*)(void *), void *, struct audio_params *);
+int    aplmca_trigger_input(void *, void *, void *, int,
+           void (*)(void *), void *, struct audio_params *);
+int    aplmca_halt_output(void *);
+int    aplmca_halt_input(void *);
+
+struct audio_hw_if aplmca_hw_if = {
+       .set_params = aplmca_set_params,
+       .get_props = aplmca_get_props,
+       .allocm = aplmca_allocm,
+       .freem = aplmca_freem,
+       .trigger_output = aplmca_trigger_output,
+       .trigger_input = aplmca_trigger_input,
+       .halt_output = aplmca_halt_output,
+       .halt_input = aplmca_halt_input,
+};
+
+int    aplmca_match(struct device *, void *, void *);
+void   aplmca_attach(struct device *, struct device *, void *);
+
+const struct cfattach aplmca_ca = {
+       sizeof (struct aplmca_softc), aplmca_match, aplmca_attach
+};
+
+struct cfdriver aplmca_cd = {
+       NULL, "aplmca", DV_DULL
+};
+
+int
+aplmca_match(struct device *parent, void *match, void *aux)
+{
+       struct fdt_attach_args *faa = aux;
+
+       return OF_is_compatible(faa->fa_node, "apple,mca");
+}
+
+void
+aplmca_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct aplmca_softc *sc = (struct aplmca_softc *)self;
+       struct fdt_attach_args *faa = aux;
+       int i;
+
+       if (faa->fa_nreg < 2) {
+               printf(": no registers\n");
+               return;
+       }
+
+       sc->sc_iot = faa->fa_iot;
+       if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
+           faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
+               printf(": can't map registers\n");
+               return;
+       }
+       if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr,
+           faa->fa_reg[1].size, 0, &sc->sc_sw_ioh)) {
+               bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size);
+               printf(": can't map registers\n");
+               return;
+       }
+
+       sc->sc_node = faa->fa_node;
+       sc->sc_phandle = OF_getpropint(faa->fa_node, "phandle", 0);
+
+       sc->sc_nclusters = OF_getpropint(faa->fa_node, "apple,nclusters", 6);
+       sc->sc_ad = mallocarray(sc->sc_nclusters, sizeof(*sc->sc_ad),
+           M_DEVBUF, M_WAITOK | M_ZERO);
+
+       for (i = 0; i < sc->sc_nclusters; i++) {
+               sc->sc_ad[i].ad_cluster = i;
+               sc->sc_ad[i].ad_sc = sc;
+               sc->sc_ad[i].ad_dai.dd_node = sc->sc_node;
+               sc->sc_ad[i].ad_dai.dd_cookie = &sc->sc_ad[0];
+               sc->sc_ad[i].ad_dai.dd_hw_if = &aplmca_hw_if;
+               sc->sc_ad[i].ad_dai.dd_set_format = aplmca_set_format;
+               sc->sc_ad[i].ad_dai.dd_set_sysclk = aplmca_set_sysclk;
+       }
+
+       printf("\n");
+
+       power_domain_enable_idx(sc->sc_node, 0);
+
+       for (i = 0; i < sc->sc_nclusters; i++) {
+               HCLR4(sc, MCA_SERDES_STATUS(i, MCA_SERDES_TXA),
+                   MCA_SERDES_STATUS_EN);
+               HCLR4(sc, MCA_SYNCGEN_STATUS(i), MCA_SYNCGEN_STATUS_EN);
+               HCLR4(sc, MCA_STATUS(i), MCA_STATUS_MCLK_EN);
+       }
+}
+
+int
+aplmca_dai_init(struct aplmca_softc *sc, int port)
+{
+       struct aplmca_dai *ad = &sc->sc_ad[port];
+       uint32_t conf;
+       char name[5];
+       int idx;
+
+       /* Allocate DMA channel. */
+       snprintf(name, sizeof(name), "tx%da", ad->ad_cluster);
+       idx = OF_getindex(sc->sc_node, name, "dma-names");
+       if (idx == -1)
+               return ENOENT;
+       ad->ad_ac = apldma_alloc_channel(idx);
+       if (ad->ad_ac == NULL)
+               return ENOENT;
+
+       power_domain_enable_idx(sc->sc_node, port + 1);
+
+       /* Basic SERDES configuration. */
+       conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA));
+       conf &= ~MCA_SERDES_CONF_SYNC_SEL_MASK;
+       conf |= (ad->ad_cluster + 1) << MCA_SERDES_CONF_SYNC_SEL_SHIFT;
+       conf |= MCA_SERDES_CONF_MAGIC;
+       HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf);
+
+       /* Output port configuration. */
+       HWRITE4(sc, MCA_PORT_CLOCK_SEL(port),
+           (ad->ad_cluster + 1) << MCA_PORT_CLOCK_SEL_SHIFT);
+       HWRITE4(sc, MCA_PORT_DATA_SEL(port),
+           MCA_PORT_DATA_SEL_TXA(ad->ad_cluster));
+       HWRITE4(sc, MCA_PORT_ENABLE(port),
+           MCA_PORT_ENABLE_CLOCKS | MCA_PORT_ENABLE_TX_DATA);
+
+       return 0;
+}
+
+uint32_t *
+aplmca_dai_next_dai(uint32_t *cells)
+{
+       uint32_t phandle = cells[0];
+       int node, ncells;
+
+       node = OF_getnodebyphandle(phandle);
+       if (node == 0)
+               return NULL;
+
+       ncells = OF_getpropint(node, "#sound-dai-cells", 0);
+       return cells + ncells + 1;
+}
+
+struct dai_device *
+aplmca_alloc_cluster(int node)
+{
+       struct aplmca_softc *sc = aplmca_cd.cd_devs[0];
+       uint32_t *dais;
+       uint32_t *dai;
+       uint32_t port;
+       int nports = 0;
+       int len;
+
+       len = OF_getproplen(node, "sound-dai");
+       if (len != 2 * sizeof(uint32_t) && len != 4 * sizeof(uint32_t))
+               return NULL;
+
+       dais = malloc(len, M_TEMP, M_WAITOK);
+       OF_getpropintarray(node, "sound-dai", dais, len);
+
+       dai = dais;
+       while (dai && dai < dais + (len / sizeof(uint32_t))) {
+               if (dai[0] == sc->sc_phandle) {
+                       port = dai[1];
+                       nports++;
+                       break;
+               }
+               dai = aplmca_dai_next_dai(dai);
+       }
+
+       free(dais, M_TEMP, len);
+
+       if (nports == 0)
+               return NULL;
+
+       if (sc->sc_ad[port].ad_ac != NULL)
+               return NULL;
+
+       if (aplmca_dai_init(sc, port))
+               return NULL;
+
+       return &sc->sc_ad[port].ad_dai;
+}
+
+int
+aplmca_set_format(void *cookie, uint32_t fmt, uint32_t pol,
+    uint32_t clk)
+{
+       struct aplmca_dai *ad = cookie;
+       struct aplmca_softc *sc = ad->ad_sc;
+       uint32_t conf;
+
+       conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA));
+       conf &= ~MCA_SERDES_CONF_WIDTH_MASK;
+       conf |= MCA_SERDES_CONF_WIDTH_32BIT;
+
+       switch (fmt) {
+       case DAI_FORMAT_I2S:
+               conf &= ~MCA_SERDES_CONF_BCLK_POL;
+               break;
+       case DAI_FORMAT_RJ:
+       case DAI_FORMAT_LJ:
+               conf |= MCA_SERDES_CONF_BCLK_POL;
+               break;
+       default:
+               return EINVAL;
+       }
+
+       if (pol & DAI_POLARITY_IB)
+               conf ^= MCA_SERDES_CONF_BCLK_POL;
+       if (pol & DAI_POLARITY_IF)
+               return EINVAL;
+
+       if (!(clk & DAI_CLOCK_CBM) || !(clk & DAI_CLOCK_CFM))
+               return EINVAL;
+
+       HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf);
+
+       return 0;
+}
+
+int
+aplmca_set_sysclk(void *cookie, uint32_t rate)
+{
+       struct aplmca_dai *ad = cookie;
+       struct aplmca_softc *sc = ad->ad_sc;
+
+       return clock_set_frequency_idx(sc->sc_node, ad->ad_cluster, rate);
+}
+
+int
+aplmca_set_params(void *cookie, int setmode, int usemode,
+    struct audio_params *play, struct audio_params *rec)
+{
+       if (setmode & AUMODE_PLAY) {
+               play->sample_rate = 48000;
+               play->encoding = AUDIO_ENCODING_SLINEAR_LE;
+               play->precision = 24;
+               play->bps = 4;
+               play->msb = 0;
+               play->channels = 2;
+       }
+
+       return 0;
+}
+
+int
+aplmca_get_props(void *cookie)
+{
+       return 0;
+}
+
+void *
+aplmca_allocm(void *cookie, int direction, size_t size, int type,
+    int flags)
+{
+       struct aplmca_dai *ad = cookie;
+
+       if (direction == AUMODE_PLAY) {
+               ad->ad_pbuf = apldma_allocm(ad->ad_ac, size, flags);
+               return ad->ad_pbuf;
+       }
+
+       return malloc(size, type, flags | M_ZERO);
+}
+
+void
+aplmca_freem(void *cookie, void *addr, int type)
+{
+       struct aplmca_dai *ad = cookie;
+
+       if (addr == ad->ad_pbuf) {
+               apldma_freem(ad->ad_ac);
+               return;
+       }
+
+       free(addr, type, 0);
+}
+
+int
+aplmca_trigger_output(void *cookie, void *start, void *end, int blksize,
+    void (*intr)(void *), void *intrarg, struct audio_params *params)
+{
+       struct aplmca_dai *ad = cookie;
+       struct aplmca_softc *sc = ad->ad_sc;
+       uint32_t conf, period;
+       int pad;
+
+       if (params->channels > 16)
+               return EINVAL;
+
+       /* Finalize SERDES configuration. */
+       conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA));
+       conf &= ~MCA_SERDES_CONF_NSLOTS_MASK;
+       conf |= ((params->channels - 1) << MCA_SERDES_CONF_NSLOTS_SHIFT);
+       HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf);
+       HWRITE4(sc, MCA_SERDES_CHANMASK0(ad->ad_cluster, MCA_SERDES_TXA),
+           0xffffffff);
+       HWRITE4(sc, MCA_SERDES_CHANMASK1(ad->ad_cluster, MCA_SERDES_TXA),
+           0xffffffff << params->channels);
+       HWRITE4(sc, MCA_SERDES_CHANMASK2(ad->ad_cluster, MCA_SERDES_TXA),
+           0xffffffff);
+       HWRITE4(sc, MCA_SERDES_CHANMASK3(ad->ad_cluster, MCA_SERDES_TXA),
+           0xffffffff << params->channels);
+
+       period = params->channels * 32;
+       HWRITE4(sc, MCA_SYNCGEN_HI_PERIOD(ad->ad_cluster), period - 2);
+       HWRITE4(sc, MCA_SYNCGEN_LO_PERIOD(ad->ad_cluster), 0);
+
+       clock_enable_idx(sc->sc_node, ad->ad_cluster);
+
+       HWRITE4(sc, MCA_SYNCGEN_MCLK_SEL(ad->ad_cluster),
+           ad->ad_cluster + 1);
+
+       HSET4(sc, MCA_STATUS(ad->ad_cluster), MCA_STATUS_MCLK_EN);
+       HSET4(sc, MCA_SYNCGEN_STATUS(ad->ad_cluster),
+           MCA_SYNCGEN_STATUS_EN);
+       HSET4(sc, MCA_SERDES_STATUS(ad->ad_cluster, MCA_SERDES_TXA),
+           MCA_SERDES_STATUS_EN);
+
+       pad = params->bps * 8 - params->precision;
+       bus_space_write_4(sc->sc_iot, sc->sc_sw_ioh,
+           MCA_DMA_ADAPTER_A(ad->ad_cluster),
+           pad << MCA_DMA_ADAPTER_TX_LSB_PAD_SHIFT |
+           2 << MCA_DMA_ADAPTER_TX_NCHANS_SHIFT |
+           2 << MCA_DMA_ADAPTER_NCHANS_SHIFT);
+
+       return apldma_trigger_output(ad->ad_ac, start, end, blksize,
+           intr, intrarg, params);
+}
+
+int
+aplmca_trigger_input(void *cookie, void *start, void *end, int blksize,
+    void (*intr)(void *), void *intrarg, struct audio_params *params)
+{
+       printf("%s\n", __func__);
+       return EIO;
+}
+
+int
+aplmca_halt_output(void *cookie)
+{
+       struct aplmca_dai *ad = cookie;
+       struct aplmca_softc *sc = ad->ad_sc;
+       int error;
+
+       error = apldma_halt_output(ad->ad_ac);
+
+       HCLR4(sc, MCA_SERDES_STATUS(ad->ad_cluster, MCA_SERDES_TXA),
+           MCA_SERDES_STATUS_EN);
+       HCLR4(sc, MCA_SYNCGEN_STATUS(ad->ad_cluster),
+           MCA_SYNCGEN_STATUS_EN);
+       HCLR4(sc, MCA_STATUS(ad->ad_cluster), MCA_STATUS_MCLK_EN);
+
+       clock_disable_idx(sc->sc_node, ad->ad_cluster);
+
+       return error;
+}
+
+int
+aplmca_halt_input(void *cookie)
+{
+       printf("%s\n", __func__);
+       return 0;
+}
diff --git a/sys/arch/arm64/dev/aplmca.h b/sys/arch/arm64/dev/aplmca.h
new file mode 100644 (file)
index 0000000..f5133d7
--- /dev/null
@@ -0,0 +1,3 @@
+/* public domain */
+
+struct dai_device *aplmca_alloc_cluster(int);