/* $OpenBSD: graphaudio.c,v 1.1 2021/04/07 17:12:22 kettenis Exp $ */ /* * Copyright (c) 2020 Patrick Wildt * Copyright (c) 2021 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 struct graphaudio_softc { struct device sc_dev; int sc_node; uint32_t sc_mclk_fs; struct dai_device *sc_dai_cpu; struct dai_device *sc_dai_codec; }; int graphaudio_match(struct device *, void *, void *); void graphaudio_attach(struct device *, struct device *, void *); void graphaudio_attach_deferred(struct device *); void graphaudio_set_format(struct graphaudio_softc *, uint32_t, uint32_t, uint32_t); int graphaudio_open(void *, int); void graphaudio_close(void *); int graphaudio_set_params(void *, int, int, struct audio_params *, struct audio_params *); void *graphaudio_allocm(void *, int, size_t, int, int); void graphaudio_freem(void *, void *, int); int graphaudio_set_port(void *, mixer_ctrl_t *); int graphaudio_get_port(void *, mixer_ctrl_t *); int graphaudio_query_devinfo(void *, mixer_devinfo_t *); int graphaudio_get_props(void *); int graphaudio_round_blocksize(void *, int); size_t graphaudio_round_buffersize(void *, int, size_t); int graphaudio_trigger_output(void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); int graphaudio_trigger_input(void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); int graphaudio_halt_output(void *); int graphaudio_halt_input(void *); struct audio_hw_if graphaudio_hw_if = { .open = graphaudio_open, .close = graphaudio_close, .set_params = graphaudio_set_params, .allocm = graphaudio_allocm, .freem = graphaudio_freem, .set_port = graphaudio_set_port, .get_port = graphaudio_get_port, .query_devinfo = graphaudio_query_devinfo, .get_props = graphaudio_get_props, .round_blocksize = graphaudio_round_blocksize, .round_buffersize = graphaudio_round_buffersize, .trigger_output = graphaudio_trigger_output, .trigger_input = graphaudio_trigger_input, .halt_output = graphaudio_halt_output, .halt_input = graphaudio_halt_input, }; struct cfattach graphaudio_ca = { sizeof(struct graphaudio_softc), graphaudio_match, graphaudio_attach }; struct cfdriver graphaudio_cd = { NULL, "graphaudio", DV_DULL }; int graphaudio_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return OF_is_compatible(faa->fa_node, "audio-graph-card"); } void graphaudio_attach(struct device *parent, struct device *self, void *aux) { struct graphaudio_softc *sc = (struct graphaudio_softc *)self; struct fdt_attach_args *faa = aux; printf("\n"); sc->sc_node = faa->fa_node; config_defer(self, graphaudio_attach_deferred); } void graphaudio_attach_deferred(struct device *self) { struct graphaudio_softc *sc = (struct graphaudio_softc *)self; char format[16] = { 0 }; uint32_t fmt, pol, clk; uint32_t dais; struct device_ports *dp; struct endpoint *ep, *rep; dais = OF_getpropint(sc->sc_node, "dais", 0); dp = device_ports_byphandle(dais); if (dp == NULL) return; ep = endpoint_byreg(dp, -1, -1); if (ep == NULL) return; rep = endpoint_remote(ep); if (rep == NULL) return; sc->sc_mclk_fs = OF_getpropint(ep->ep_node, "mclk-fs", 0); sc->sc_dai_cpu = endpoint_get_cookie(ep); sc->sc_dai_codec = endpoint_get_cookie(rep); if (sc->sc_dai_cpu == NULL || sc->sc_dai_codec == NULL) return; OF_getprop(ep->ep_node, "dai-format", format, sizeof(format)); if (!strcmp(format, "i2s")) fmt = DAI_FORMAT_I2S; else if (!strcmp(format, "right_j")) fmt = DAI_FORMAT_RJ; else if (!strcmp(format, "left_j")) fmt = DAI_FORMAT_LJ; else if (!strcmp(format, "dsp_a")) fmt = DAI_FORMAT_DSPA; else if (!strcmp(format, "dsp_b")) fmt = DAI_FORMAT_DSPB; else if (!strcmp(format, "ac97")) fmt = DAI_FORMAT_AC97; else if (!strcmp(format, "pdm")) fmt = DAI_FORMAT_PDM; else if (!strcmp(format, "msb")) fmt = DAI_FORMAT_MSB; else if (!strcmp(format, "lsb")) fmt = DAI_FORMAT_LSB; else return; pol = 0; if (OF_getproplen(ep->ep_node, "frame-inversion") == 0) pol |= DAI_POLARITY_IF; else pol |= DAI_POLARITY_NF; if (OF_getproplen(ep->ep_node, "bitclock-inversion") == 0) pol |= DAI_POLARITY_IB; else pol |= DAI_POLARITY_NB; clk = 0; if (OF_getproplen(ep->ep_node, "frame-master") == 0) clk |= DAI_CLOCK_CFM; else clk |= DAI_CLOCK_CFS; if (OF_getproplen(ep->ep_node, "bitclock-master") == 0) clk |= DAI_CLOCK_CBM; else clk |= DAI_CLOCK_CBS; graphaudio_set_format(sc, fmt, pol, clk); audio_attach_mi(&graphaudio_hw_if, sc, &sc->sc_dev); } void graphaudio_set_format(struct graphaudio_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 graphaudio_open(void *cookie, int flags) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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) { graphaudio_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) { graphaudio_close(cookie); return error; } } return 0; } void graphaudio_close(void *cookie) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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 graphaudio_set_params(void *cookie, int setmode, int usemode, struct audio_params *play, struct audio_params *rec) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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 * graphaudio_allocm(void *cookie, int direction, size_t size, int type, int flags) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_cpu; 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 graphaudio_freem(void *cookie, void *addr, int type) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_cpu; struct audio_hw_if *hwif = dai->dd_hw_if; if (hwif->freem) hwif->freem(dai->dd_cookie, addr, type); } int graphaudio_set_port(void *cookie, mixer_ctrl_t *cp) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_codec; struct audio_hw_if *hwif = dai->dd_hw_if; if (hwif->set_port) return hwif->set_port(dai->dd_cookie, cp); return ENXIO; } int graphaudio_get_port(void *cookie, mixer_ctrl_t *cp) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_codec; struct audio_hw_if *hwif = dai->dd_hw_if; if (hwif->get_port) return hwif->get_port(dai->dd_cookie, cp); return ENXIO; } int graphaudio_query_devinfo(void *cookie, mixer_devinfo_t *dip) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_codec; struct audio_hw_if *hwif = dai->dd_hw_if; if (hwif->query_devinfo) return hwif->query_devinfo(dai->dd_cookie, dip); return ENXIO; } int graphaudio_get_props(void *cookie) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_cpu; struct audio_hw_if *hwif = dai->dd_hw_if; if (hwif->get_props) return hwif->get_props(dai->dd_cookie); return 0; } int graphaudio_round_blocksize(void *cookie, int block) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_cpu; 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 graphaudio_round_buffersize(void *cookie, int direction, size_t bufsize) { struct graphaudio_softc *sc = cookie; struct dai_device *dai = sc->sc_dai_cpu; 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 graphaudio_trigger_output(void *cookie, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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) { graphaudio_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) { graphaudio_halt_output(cookie); return error; } } return 0; } int graphaudio_trigger_input(void *cookie, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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) { graphaudio_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) { graphaudio_halt_input(cookie); return error; } } return 0; } int graphaudio_halt_output(void *cookie) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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 graphaudio_halt_input(void *cookie) { struct graphaudio_softc *sc = cookie; struct dai_device *dai; 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; }