From 7400b10707056ecef3b64bcd12a4351dc94a1512 Mon Sep 17 00:00:00 2001 From: kettenis Date: Wed, 3 Aug 2022 13:42:16 +0000 Subject: [PATCH] Add aplaudio(4) and aplmca(4). The aplmca(4) driver controls the hardware 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 | 8 +- sys/arch/arm64/conf/files.arm64 | 10 +- sys/arch/arm64/dev/aplaudio.c | 475 ++++++++++++++++++++++++++++++ sys/arch/arm64/dev/aplmca.c | 494 ++++++++++++++++++++++++++++++++ sys/arch/arm64/dev/aplmca.h | 3 + 5 files changed, 988 insertions(+), 2 deletions(-) create mode 100644 sys/arch/arm64/dev/aplaudio.c create mode 100644 sys/arch/arm64/dev/aplmca.c create mode 100644 sys/arch/arm64/dev/aplmca.h diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index 10876ec302e..2f59c319cd9 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -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 diff --git a/sys/arch/arm64/conf/files.arm64 b/sys/arch/arm64/conf/files.arm64 index 920455b71f2..f3f71571c19 100644 --- a/sys/arch/arm64/conf/files.arm64 +++ b/sys/arch/arm64/conf/files.arm64 @@ -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 index 00000000000..ee61f810373 --- /dev/null +++ b/sys/arch/arm64/dev/aplaudio.c @@ -0,0 +1,475 @@ +/* $OpenBSD: aplaudio.c,v 1.1 2022/08/03 13:42:16 kettenis Exp $ */ +/* + * Copyright (c) 2022 Mark Kettenis + * Copyright (c) 2020 Patrick Wildt + * + * 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 +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +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 index 00000000000..116d116f056 --- /dev/null +++ b/sys/arch/arm64/dev/aplmca.c @@ -0,0 +1,494 @@ +/* $OpenBSD: aplmca.c,v 1.1 2022/08/03 13:42:16 kettenis Exp $ */ +/* + * Copyright (c) 2022 Mark Kettenis + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +/* + * 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 index 00000000000..f5133d7fc26 --- /dev/null +++ b/sys/arch/arm64/dev/aplmca.h @@ -0,0 +1,3 @@ +/* public domain */ + +struct dai_device *aplmca_alloc_cluster(int); -- 2.20.1