--- /dev/null
+/* $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;
+}
--- /dev/null
+/* $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;
+}