xref: /freebsd/sys/dev/sound/fdt/audio_soc.c (revision 516a9c0212b003e1da0c6f4476dbe4f3f431606c)
12cfe870aSOleksandr Tymoshenko /*-
22cfe870aSOleksandr Tymoshenko  * Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
32cfe870aSOleksandr Tymoshenko  *
42cfe870aSOleksandr Tymoshenko  * Redistribution and use in source and binary forms, with or without
52cfe870aSOleksandr Tymoshenko  * modification, are permitted provided that the following conditions
62cfe870aSOleksandr Tymoshenko  * are met:
72cfe870aSOleksandr Tymoshenko  * 1. Redistributions of source code must retain the above copyright
82cfe870aSOleksandr Tymoshenko  *    notice, this list of conditions and the following disclaimer.
92cfe870aSOleksandr Tymoshenko  * 2. Redistributions in binary form must reproduce the above copyright
102cfe870aSOleksandr Tymoshenko  *    notice, this list of conditions and the following disclaimer in the
112cfe870aSOleksandr Tymoshenko  *    documentation and/or other materials provided with the distribution.
122cfe870aSOleksandr Tymoshenko  *
132cfe870aSOleksandr Tymoshenko  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
142cfe870aSOleksandr Tymoshenko  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
152cfe870aSOleksandr Tymoshenko  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
162cfe870aSOleksandr Tymoshenko  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
172cfe870aSOleksandr Tymoshenko  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
182cfe870aSOleksandr Tymoshenko  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
192cfe870aSOleksandr Tymoshenko  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
202cfe870aSOleksandr Tymoshenko  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
212cfe870aSOleksandr Tymoshenko  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
222cfe870aSOleksandr Tymoshenko  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
232cfe870aSOleksandr Tymoshenko  *
242cfe870aSOleksandr Tymoshenko  */
252cfe870aSOleksandr Tymoshenko 
262cfe870aSOleksandr Tymoshenko #include <sys/cdefs.h>
272cfe870aSOleksandr Tymoshenko #include "opt_platform.h"
282cfe870aSOleksandr Tymoshenko 
292cfe870aSOleksandr Tymoshenko #include <sys/param.h>
302cfe870aSOleksandr Tymoshenko #include <sys/systm.h>
312cfe870aSOleksandr Tymoshenko #include <sys/bus.h>
322cfe870aSOleksandr Tymoshenko #include <sys/clock.h>
332cfe870aSOleksandr Tymoshenko #include <sys/kernel.h>
342cfe870aSOleksandr Tymoshenko #include <sys/lock.h>
352cfe870aSOleksandr Tymoshenko #include <sys/module.h>
362cfe870aSOleksandr Tymoshenko #include <sys/endian.h>
372cfe870aSOleksandr Tymoshenko 
382cfe870aSOleksandr Tymoshenko #include <dev/ofw/ofw_bus.h>
392cfe870aSOleksandr Tymoshenko #include <dev/ofw/ofw_bus_subr.h>
402cfe870aSOleksandr Tymoshenko 
412cfe870aSOleksandr Tymoshenko #include <dev/sound/fdt/audio_dai.h>
422cfe870aSOleksandr Tymoshenko #include <dev/sound/pcm/sound.h>
432cfe870aSOleksandr Tymoshenko #include "audio_dai_if.h"
442cfe870aSOleksandr Tymoshenko 
452cfe870aSOleksandr Tymoshenko #define	AUDIO_BUFFER_SIZE	48000 * 4
462cfe870aSOleksandr Tymoshenko 
472cfe870aSOleksandr Tymoshenko struct audio_soc_aux_node {
482cfe870aSOleksandr Tymoshenko 	SLIST_ENTRY(audio_soc_aux_node)	link;
492cfe870aSOleksandr Tymoshenko 	device_t			dev;
502cfe870aSOleksandr Tymoshenko };
512cfe870aSOleksandr Tymoshenko 
522cfe870aSOleksandr Tymoshenko struct audio_soc_channel {
532cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc	*sc;	/* parent device's softc */
542cfe870aSOleksandr Tymoshenko 	struct pcm_channel 	*pcm;	/* PCM channel */
552cfe870aSOleksandr Tymoshenko 	struct snd_dbuf		*buf;	/* PCM buffer */
562cfe870aSOleksandr Tymoshenko 	int			dir;	/* direction */
572cfe870aSOleksandr Tymoshenko };
582cfe870aSOleksandr Tymoshenko 
592cfe870aSOleksandr Tymoshenko struct audio_soc_softc {
602cfe870aSOleksandr Tymoshenko 	/*
612cfe870aSOleksandr Tymoshenko 	 * pcm_register assumes that sc is snddev_info,
6204389c85SGordon Bergling 	 * so this has to be first structure member for "compatibility"
632cfe870aSOleksandr Tymoshenko 	 */
642cfe870aSOleksandr Tymoshenko 	struct snddev_info	info;
652cfe870aSOleksandr Tymoshenko 	device_t		dev;
662cfe870aSOleksandr Tymoshenko 	char			*name;
672cfe870aSOleksandr Tymoshenko 	struct intr_config_hook init_hook;
682cfe870aSOleksandr Tymoshenko 	device_t		cpu_dev;
692cfe870aSOleksandr Tymoshenko 	device_t		codec_dev;
702cfe870aSOleksandr Tymoshenko 	SLIST_HEAD(, audio_soc_aux_node)	aux_devs;
712cfe870aSOleksandr Tymoshenko 	unsigned int		mclk_fs;
722cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel 	play_channel;
732cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel 	rec_channel;
742cfe870aSOleksandr Tymoshenko 	/*
752cfe870aSOleksandr Tymoshenko 	 * The format is from the CPU node, for CODEC node clock roles
762cfe870aSOleksandr Tymoshenko 	 * need to be reversed.
772cfe870aSOleksandr Tymoshenko 	 */
782cfe870aSOleksandr Tymoshenko 	uint32_t		format;
792cfe870aSOleksandr Tymoshenko 	uint32_t		link_mclk_fs;
802cfe870aSOleksandr Tymoshenko };
812cfe870aSOleksandr Tymoshenko 
822cfe870aSOleksandr Tymoshenko static struct ofw_compat_data compat_data[] = {
832cfe870aSOleksandr Tymoshenko 	{"simple-audio-card",	1},
842cfe870aSOleksandr Tymoshenko 	{NULL,			0},
852cfe870aSOleksandr Tymoshenko };
862cfe870aSOleksandr Tymoshenko 
872cfe870aSOleksandr Tymoshenko static struct {
882cfe870aSOleksandr Tymoshenko 	const char *name;
892cfe870aSOleksandr Tymoshenko 	unsigned int fmt;
902cfe870aSOleksandr Tymoshenko } ausoc_dai_formats[] = {
912cfe870aSOleksandr Tymoshenko 	{ "i2s",	AUDIO_DAI_FORMAT_I2S },
922cfe870aSOleksandr Tymoshenko 	{ "right_j",	AUDIO_DAI_FORMAT_RJ },
932cfe870aSOleksandr Tymoshenko 	{ "left_j",	AUDIO_DAI_FORMAT_LJ },
942cfe870aSOleksandr Tymoshenko 	{ "dsp_a",	AUDIO_DAI_FORMAT_DSPA },
952cfe870aSOleksandr Tymoshenko 	{ "dsp_b",	AUDIO_DAI_FORMAT_DSPB },
962cfe870aSOleksandr Tymoshenko 	{ "ac97",	AUDIO_DAI_FORMAT_AC97 },
972cfe870aSOleksandr Tymoshenko 	{ "pdm",	AUDIO_DAI_FORMAT_PDM },
982cfe870aSOleksandr Tymoshenko };
992cfe870aSOleksandr Tymoshenko 
1002cfe870aSOleksandr Tymoshenko static int	audio_soc_probe(device_t dev);
1012cfe870aSOleksandr Tymoshenko static int	audio_soc_attach(device_t dev);
1022cfe870aSOleksandr Tymoshenko static int	audio_soc_detach(device_t dev);
1032cfe870aSOleksandr Tymoshenko 
1042cfe870aSOleksandr Tymoshenko /*
1052cfe870aSOleksandr Tymoshenko  * Invert master/slave roles for CODEC side of the node
1062cfe870aSOleksandr Tymoshenko  */
1072cfe870aSOleksandr Tymoshenko static uint32_t
audio_soc_reverse_clocks(uint32_t format)1082cfe870aSOleksandr Tymoshenko audio_soc_reverse_clocks(uint32_t format)
1092cfe870aSOleksandr Tymoshenko {
1102cfe870aSOleksandr Tymoshenko 	int fmt, pol, clk;
1112cfe870aSOleksandr Tymoshenko 
1122cfe870aSOleksandr Tymoshenko 	fmt = AUDIO_DAI_FORMAT_FORMAT(format);
1132cfe870aSOleksandr Tymoshenko 	pol = AUDIO_DAI_FORMAT_POLARITY(format);
1142cfe870aSOleksandr Tymoshenko 	clk = AUDIO_DAI_FORMAT_CLOCK(format);
1152cfe870aSOleksandr Tymoshenko 
1162cfe870aSOleksandr Tymoshenko 	switch (clk) {
1172cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBM_CFM:
1182cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBS_CFS;
1192cfe870aSOleksandr Tymoshenko 		break;
1202cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBS_CFM:
1212cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBM_CFS;
1222cfe870aSOleksandr Tymoshenko 		break;
1232cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBM_CFS:
1242cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBS_CFM;
1252cfe870aSOleksandr Tymoshenko 		break;
1262cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBS_CFS:
1272cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBM_CFM;
1282cfe870aSOleksandr Tymoshenko 		break;
1292cfe870aSOleksandr Tymoshenko 	}
1302cfe870aSOleksandr Tymoshenko 
1312cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_FORMAT(fmt, pol, clk);
1322cfe870aSOleksandr Tymoshenko }
1332cfe870aSOleksandr Tymoshenko 
1342cfe870aSOleksandr Tymoshenko static uint32_t
audio_soc_chan_setblocksize(kobj_t obj,void * data,uint32_t blocksz)1352cfe870aSOleksandr Tymoshenko audio_soc_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksz)
1362cfe870aSOleksandr Tymoshenko {
1372cfe870aSOleksandr Tymoshenko 
1382cfe870aSOleksandr Tymoshenko 	return (blocksz);
1392cfe870aSOleksandr Tymoshenko }
1402cfe870aSOleksandr Tymoshenko 
1412cfe870aSOleksandr Tymoshenko static int
audio_soc_chan_setformat(kobj_t obj,void * data,uint32_t format)1422cfe870aSOleksandr Tymoshenko audio_soc_chan_setformat(kobj_t obj, void *data, uint32_t format)
1432cfe870aSOleksandr Tymoshenko {
1442cfe870aSOleksandr Tymoshenko 
1452cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
1462cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
1472cfe870aSOleksandr Tymoshenko 
1482cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
1492cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
1502cfe870aSOleksandr Tymoshenko 
1512cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_SET_CHANFORMAT(sc->cpu_dev, format);
1522cfe870aSOleksandr Tymoshenko }
1532cfe870aSOleksandr Tymoshenko 
1542cfe870aSOleksandr Tymoshenko static uint32_t
audio_soc_chan_setspeed(kobj_t obj,void * data,uint32_t speed)1552cfe870aSOleksandr Tymoshenko audio_soc_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
1562cfe870aSOleksandr Tymoshenko {
1572cfe870aSOleksandr Tymoshenko 
1582cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
1592cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
1602cfe870aSOleksandr Tymoshenko 	uint32_t rate;
1612cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux_node;
1622cfe870aSOleksandr Tymoshenko 
1632cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
1642cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
1652cfe870aSOleksandr Tymoshenko 
1662cfe870aSOleksandr Tymoshenko 	if (sc->link_mclk_fs) {
1672cfe870aSOleksandr Tymoshenko 		rate = speed * sc->link_mclk_fs;
1682cfe870aSOleksandr Tymoshenko 		if (AUDIO_DAI_SET_SYSCLK(sc->cpu_dev, rate, AUDIO_DAI_CLOCK_IN))
1692cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to set sysclk for CPU node\n");
1702cfe870aSOleksandr Tymoshenko 
1712cfe870aSOleksandr Tymoshenko 		if (AUDIO_DAI_SET_SYSCLK(sc->codec_dev, rate, AUDIO_DAI_CLOCK_OUT))
1722cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to set sysclk for codec node\n");
1732cfe870aSOleksandr Tymoshenko 
1742cfe870aSOleksandr Tymoshenko 		SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
1752cfe870aSOleksandr Tymoshenko 			if (AUDIO_DAI_SET_SYSCLK(aux_node->dev, rate, AUDIO_DAI_CLOCK_OUT))
1762cfe870aSOleksandr Tymoshenko 				device_printf(sc->dev, "failed to set sysclk for aux node\n");
1772cfe870aSOleksandr Tymoshenko 		}
1782cfe870aSOleksandr Tymoshenko 	}
1792cfe870aSOleksandr Tymoshenko 
1802cfe870aSOleksandr Tymoshenko 	/*
1812cfe870aSOleksandr Tymoshenko 	 * Let CPU node determine speed
1822cfe870aSOleksandr Tymoshenko 	 */
1832cfe870aSOleksandr Tymoshenko 	speed = AUDIO_DAI_SET_CHANSPEED(sc->cpu_dev, speed);
1842cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_SET_CHANSPEED(sc->codec_dev, speed);
1852cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
1862cfe870aSOleksandr Tymoshenko 		AUDIO_DAI_SET_CHANSPEED(aux_node->dev, speed);
1872cfe870aSOleksandr Tymoshenko 	}
1882cfe870aSOleksandr Tymoshenko 
1892cfe870aSOleksandr Tymoshenko 	return (speed);
1902cfe870aSOleksandr Tymoshenko }
1912cfe870aSOleksandr Tymoshenko 
1922cfe870aSOleksandr Tymoshenko static uint32_t
audio_soc_chan_getptr(kobj_t obj,void * data)1932cfe870aSOleksandr Tymoshenko audio_soc_chan_getptr(kobj_t obj, void *data)
1942cfe870aSOleksandr Tymoshenko {
1952cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
1962cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
1972cfe870aSOleksandr Tymoshenko 
1982cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
1992cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
2002cfe870aSOleksandr Tymoshenko 
2012cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_GET_PTR(sc->cpu_dev, ausoc_chan->dir);
2022cfe870aSOleksandr Tymoshenko }
2032cfe870aSOleksandr Tymoshenko 
2042cfe870aSOleksandr Tymoshenko static void *
audio_soc_chan_init(kobj_t obj,void * devinfo,struct snd_dbuf * b,struct pcm_channel * c,int dir)2052cfe870aSOleksandr Tymoshenko audio_soc_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
2062cfe870aSOleksandr Tymoshenko 	struct pcm_channel *c, int dir)
2072cfe870aSOleksandr Tymoshenko {
2082cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
2092cfe870aSOleksandr Tymoshenko 	void *buffer;
2102cfe870aSOleksandr Tymoshenko 
2112cfe870aSOleksandr Tymoshenko 	ausoc_chan = devinfo;
2122cfe870aSOleksandr Tymoshenko 	buffer = malloc(AUDIO_BUFFER_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
2132cfe870aSOleksandr Tymoshenko 
2142cfe870aSOleksandr Tymoshenko 	if (sndbuf_setup(b, buffer, AUDIO_BUFFER_SIZE) != 0) {
2152cfe870aSOleksandr Tymoshenko 		free(buffer, M_DEVBUF);
2162cfe870aSOleksandr Tymoshenko 		return NULL;
2172cfe870aSOleksandr Tymoshenko 	}
2182cfe870aSOleksandr Tymoshenko 
2192cfe870aSOleksandr Tymoshenko 	ausoc_chan->dir = dir;
2202cfe870aSOleksandr Tymoshenko 	ausoc_chan->buf = b;
2212cfe870aSOleksandr Tymoshenko 	ausoc_chan->pcm = c;
2222cfe870aSOleksandr Tymoshenko 
2232cfe870aSOleksandr Tymoshenko 	return (devinfo);
2242cfe870aSOleksandr Tymoshenko }
2252cfe870aSOleksandr Tymoshenko 
2262cfe870aSOleksandr Tymoshenko static int
audio_soc_chan_trigger(kobj_t obj,void * data,int go)2272cfe870aSOleksandr Tymoshenko audio_soc_chan_trigger(kobj_t obj, void *data, int go)
2282cfe870aSOleksandr Tymoshenko {
2292cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
2302cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
2312cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux_node;
2322cfe870aSOleksandr Tymoshenko 
2332cfe870aSOleksandr Tymoshenko 	ausoc_chan = (struct audio_soc_channel *)data;
2342cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
2352cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_TRIGGER(sc->codec_dev, go, ausoc_chan->dir);
2362cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
2372cfe870aSOleksandr Tymoshenko 		AUDIO_DAI_TRIGGER(aux_node->dev, go, ausoc_chan->dir);
2382cfe870aSOleksandr Tymoshenko 	}
2392cfe870aSOleksandr Tymoshenko 
2402cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_TRIGGER(sc->cpu_dev, go, ausoc_chan->dir);
2412cfe870aSOleksandr Tymoshenko }
2422cfe870aSOleksandr Tymoshenko 
2432cfe870aSOleksandr Tymoshenko static int
audio_soc_chan_free(kobj_t obj,void * data)2442cfe870aSOleksandr Tymoshenko audio_soc_chan_free(kobj_t obj, void *data)
2452cfe870aSOleksandr Tymoshenko {
2462cfe870aSOleksandr Tymoshenko 
2472cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
2482cfe870aSOleksandr Tymoshenko 	void *buffer;
2492cfe870aSOleksandr Tymoshenko 
2502cfe870aSOleksandr Tymoshenko 	ausoc_chan = (struct audio_soc_channel *)data;
2512cfe870aSOleksandr Tymoshenko 
2522cfe870aSOleksandr Tymoshenko 	buffer = sndbuf_getbuf(ausoc_chan->buf);
2532cfe870aSOleksandr Tymoshenko 	if (buffer)
2542cfe870aSOleksandr Tymoshenko 		free(buffer, M_DEVBUF);
2552cfe870aSOleksandr Tymoshenko 
2562cfe870aSOleksandr Tymoshenko 	return (0);
2572cfe870aSOleksandr Tymoshenko }
2582cfe870aSOleksandr Tymoshenko 
2592cfe870aSOleksandr Tymoshenko static struct pcmchan_caps *
audio_soc_chan_getcaps(kobj_t obj,void * data)2602cfe870aSOleksandr Tymoshenko audio_soc_chan_getcaps(kobj_t obj, void *data)
2612cfe870aSOleksandr Tymoshenko {
2622cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
2632cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
2642cfe870aSOleksandr Tymoshenko 
2652cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
2662cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
2672cfe870aSOleksandr Tymoshenko 
2682cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_GET_CAPS(sc->cpu_dev);
2692cfe870aSOleksandr Tymoshenko }
2702cfe870aSOleksandr Tymoshenko 
2712cfe870aSOleksandr Tymoshenko static kobj_method_t audio_soc_chan_methods[] = {
2722cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_init, 	audio_soc_chan_init),
2732cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_free, 	audio_soc_chan_free),
2742cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_setformat, 	audio_soc_chan_setformat),
2752cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_setspeed, 	audio_soc_chan_setspeed),
2762cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_setblocksize,audio_soc_chan_setblocksize),
2772cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_trigger,	audio_soc_chan_trigger),
2782cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_getptr,	audio_soc_chan_getptr),
2792cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_getcaps,	audio_soc_chan_getcaps),
2802cfe870aSOleksandr Tymoshenko 	KOBJMETHOD_END
2812cfe870aSOleksandr Tymoshenko };
2822cfe870aSOleksandr Tymoshenko CHANNEL_DECLARE(audio_soc_chan);
2832cfe870aSOleksandr Tymoshenko 
2842cfe870aSOleksandr Tymoshenko static void
audio_soc_intr(void * arg)2852cfe870aSOleksandr Tymoshenko audio_soc_intr(void *arg)
2862cfe870aSOleksandr Tymoshenko {
2872cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
2882cfe870aSOleksandr Tymoshenko 	int channel_intr_required;
2892cfe870aSOleksandr Tymoshenko 
2902cfe870aSOleksandr Tymoshenko 	sc = (struct audio_soc_softc *)arg;
2912cfe870aSOleksandr Tymoshenko 	channel_intr_required = AUDIO_DAI_INTR(sc->cpu_dev, sc->play_channel.buf, sc->rec_channel.buf);
2922cfe870aSOleksandr Tymoshenko 	if (channel_intr_required & AUDIO_DAI_PLAY_INTR)
2932cfe870aSOleksandr Tymoshenko 		chn_intr(sc->play_channel.pcm);
2942cfe870aSOleksandr Tymoshenko 	if (channel_intr_required & AUDIO_DAI_REC_INTR)
2952cfe870aSOleksandr Tymoshenko 		chn_intr(sc->rec_channel.pcm);
2962cfe870aSOleksandr Tymoshenko }
2972cfe870aSOleksandr Tymoshenko 
2982cfe870aSOleksandr Tymoshenko static int
audio_soc_probe(device_t dev)2992cfe870aSOleksandr Tymoshenko audio_soc_probe(device_t dev)
3002cfe870aSOleksandr Tymoshenko {
3012cfe870aSOleksandr Tymoshenko 
3022cfe870aSOleksandr Tymoshenko 	if (!ofw_bus_status_okay(dev))
3032cfe870aSOleksandr Tymoshenko 		return (ENXIO);
3042cfe870aSOleksandr Tymoshenko 
3052cfe870aSOleksandr Tymoshenko 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) {
3062cfe870aSOleksandr Tymoshenko 		device_set_desc(dev, "simple-audio-card");
3072cfe870aSOleksandr Tymoshenko 		return (BUS_PROBE_DEFAULT);
3082cfe870aSOleksandr Tymoshenko 	}
3092cfe870aSOleksandr Tymoshenko 
3102cfe870aSOleksandr Tymoshenko 	return (ENXIO);
3112cfe870aSOleksandr Tymoshenko }
3122cfe870aSOleksandr Tymoshenko 
3132cfe870aSOleksandr Tymoshenko static void
audio_soc_init(void * arg)3142cfe870aSOleksandr Tymoshenko audio_soc_init(void *arg)
3152cfe870aSOleksandr Tymoshenko {
3162cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
3172cfe870aSOleksandr Tymoshenko 	phandle_t node, child;
3182cfe870aSOleksandr Tymoshenko 	device_t daidev, auxdev;
3192cfe870aSOleksandr Tymoshenko 	uint32_t xref;
3202cfe870aSOleksandr Tymoshenko 	uint32_t *aux_devs;
3212cfe870aSOleksandr Tymoshenko 	int ncells, i;
3222cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux_node;
3232cfe870aSOleksandr Tymoshenko 
3242cfe870aSOleksandr Tymoshenko 	sc = (struct audio_soc_softc *)arg;
3252cfe870aSOleksandr Tymoshenko 	config_intrhook_disestablish(&sc->init_hook);
3262cfe870aSOleksandr Tymoshenko 
3272cfe870aSOleksandr Tymoshenko 	node = ofw_bus_get_node(sc->dev);
3282cfe870aSOleksandr Tymoshenko 	/* TODO: handle multi-link nodes */
3292cfe870aSOleksandr Tymoshenko 	child = ofw_bus_find_child(node, "simple-audio-card,cpu");
3302cfe870aSOleksandr Tymoshenko 	if (child == 0) {
3312cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "cpu node is missing\n");
3322cfe870aSOleksandr Tymoshenko 		return;
3332cfe870aSOleksandr Tymoshenko 	}
3342cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
3352cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "missing sound-dai property in cpu node\n");
3362cfe870aSOleksandr Tymoshenko 		return;
3372cfe870aSOleksandr Tymoshenko 	}
3382cfe870aSOleksandr Tymoshenko 	daidev = OF_device_from_xref(xref);
3392cfe870aSOleksandr Tymoshenko 	if (daidev == NULL) {
3402cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "no driver attached to cpu node\n");
3412cfe870aSOleksandr Tymoshenko 		return;
3422cfe870aSOleksandr Tymoshenko 	}
3432cfe870aSOleksandr Tymoshenko 	sc->cpu_dev = daidev;
3442cfe870aSOleksandr Tymoshenko 
3452cfe870aSOleksandr Tymoshenko 	child = ofw_bus_find_child(node, "simple-audio-card,codec");
3462cfe870aSOleksandr Tymoshenko 	if (child == 0) {
3472cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "codec node is missing\n");
3482cfe870aSOleksandr Tymoshenko 		return;
3492cfe870aSOleksandr Tymoshenko 	}
3502cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
3512cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "missing sound-dai property in codec node\n");
3522cfe870aSOleksandr Tymoshenko 		return;
3532cfe870aSOleksandr Tymoshenko 	}
3542cfe870aSOleksandr Tymoshenko 	daidev = OF_device_from_xref(xref);
3552cfe870aSOleksandr Tymoshenko 	if (daidev == NULL) {
3562cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "no driver attached to codec node\n");
3572cfe870aSOleksandr Tymoshenko 		return;
3582cfe870aSOleksandr Tymoshenko 	}
3592cfe870aSOleksandr Tymoshenko 	sc->codec_dev = daidev;
3602cfe870aSOleksandr Tymoshenko 
3612cfe870aSOleksandr Tymoshenko 	/* Add AUX devices */
3622cfe870aSOleksandr Tymoshenko 	aux_devs = NULL;
3632cfe870aSOleksandr Tymoshenko 	ncells = OF_getencprop_alloc_multi(node, "simple-audio-card,aux-devs", sizeof(*aux_devs),
3642cfe870aSOleksandr Tymoshenko 	    (void **)&aux_devs);
3652cfe870aSOleksandr Tymoshenko 
3662cfe870aSOleksandr Tymoshenko 	for (i = 0; i < ncells; i++) {
3672cfe870aSOleksandr Tymoshenko 		auxdev = OF_device_from_xref(aux_devs[i]);
3682cfe870aSOleksandr Tymoshenko 		if (auxdev == NULL)
3692cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "warning: no driver attached to aux node\n");
3703cc01caaSChristos Margiolis 		aux_node = malloc(sizeof(*aux_node), M_DEVBUF, M_NOWAIT);
3712cfe870aSOleksandr Tymoshenko 		if (aux_node == NULL) {
3722cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to allocate aux node struct\n");
3732cfe870aSOleksandr Tymoshenko 			return;
3742cfe870aSOleksandr Tymoshenko 		}
3752cfe870aSOleksandr Tymoshenko 		aux_node->dev = auxdev;
3762cfe870aSOleksandr Tymoshenko 		SLIST_INSERT_HEAD(&sc->aux_devs, aux_node, link);
3772cfe870aSOleksandr Tymoshenko 	}
3782cfe870aSOleksandr Tymoshenko 
3792cfe870aSOleksandr Tymoshenko 	if (aux_devs)
3802cfe870aSOleksandr Tymoshenko 		OF_prop_free(aux_devs);
3812cfe870aSOleksandr Tymoshenko 
3822cfe870aSOleksandr Tymoshenko 	if (AUDIO_DAI_INIT(sc->cpu_dev, sc->format)) {
3832cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "failed to initialize cpu node\n");
3842cfe870aSOleksandr Tymoshenko 		return;
3852cfe870aSOleksandr Tymoshenko 	}
3862cfe870aSOleksandr Tymoshenko 
3872cfe870aSOleksandr Tymoshenko 	/* Reverse clock roles for CODEC */
3882cfe870aSOleksandr Tymoshenko 	if (AUDIO_DAI_INIT(sc->codec_dev, audio_soc_reverse_clocks(sc->format))) {
3892cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "failed to initialize codec node\n");
3902cfe870aSOleksandr Tymoshenko 		return;
3912cfe870aSOleksandr Tymoshenko 	}
3922cfe870aSOleksandr Tymoshenko 
3932cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
3942cfe870aSOleksandr Tymoshenko 		if (AUDIO_DAI_INIT(aux_node->dev, audio_soc_reverse_clocks(sc->format))) {
3952cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to initialize aux node\n");
3962cfe870aSOleksandr Tymoshenko 			return;
3972cfe870aSOleksandr Tymoshenko 		}
3982cfe870aSOleksandr Tymoshenko 	}
3992cfe870aSOleksandr Tymoshenko 
400*516a9c02SChristos Margiolis 	pcm_init(sc->dev, sc);
4012cfe870aSOleksandr Tymoshenko 
4022cfe870aSOleksandr Tymoshenko 	sc->play_channel.sc = sc;
4032cfe870aSOleksandr Tymoshenko 	sc->rec_channel.sc = sc;
4042cfe870aSOleksandr Tymoshenko 
4052cfe870aSOleksandr Tymoshenko 	pcm_addchan(sc->dev, PCMDIR_PLAY, &audio_soc_chan_class, &sc->play_channel);
4062cfe870aSOleksandr Tymoshenko 	pcm_addchan(sc->dev, PCMDIR_REC, &audio_soc_chan_class, &sc->rec_channel);
4072cfe870aSOleksandr Tymoshenko 
408*516a9c02SChristos Margiolis 	if (pcm_register(sc->dev, "at simplebus")) {
409*516a9c02SChristos Margiolis 		device_printf(sc->dev, "failed to register PCM\n");
410*516a9c02SChristos Margiolis 		return;
411*516a9c02SChristos Margiolis 	}
4122cfe870aSOleksandr Tymoshenko 
4132cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_SETUP_INTR(sc->cpu_dev, audio_soc_intr, sc);
4142cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_SETUP_MIXER(sc->codec_dev, sc->dev);
4152cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
4162cfe870aSOleksandr Tymoshenko 		AUDIO_DAI_SETUP_MIXER(aux_node->dev, sc->dev);
4172cfe870aSOleksandr Tymoshenko 	}
4182cfe870aSOleksandr Tymoshenko }
4192cfe870aSOleksandr Tymoshenko 
4202cfe870aSOleksandr Tymoshenko static int
audio_soc_attach(device_t dev)4212cfe870aSOleksandr Tymoshenko audio_soc_attach(device_t dev)
4222cfe870aSOleksandr Tymoshenko {
4232cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
4242cfe870aSOleksandr Tymoshenko 	char *name;
4252cfe870aSOleksandr Tymoshenko 	phandle_t node, cpu_child;
4262cfe870aSOleksandr Tymoshenko 	uint32_t xref;
4272cfe870aSOleksandr Tymoshenko 	int i, ret;
4282cfe870aSOleksandr Tymoshenko 	char tmp[32];
4292cfe870aSOleksandr Tymoshenko 	unsigned int fmt, pol, clk;
4302cfe870aSOleksandr Tymoshenko 	bool frame_master, bitclock_master;
4312cfe870aSOleksandr Tymoshenko 
4322cfe870aSOleksandr Tymoshenko 	sc = device_get_softc(dev);
4332cfe870aSOleksandr Tymoshenko 	sc->dev = dev;
4342cfe870aSOleksandr Tymoshenko 	node = ofw_bus_get_node(dev);
4352cfe870aSOleksandr Tymoshenko 
4362cfe870aSOleksandr Tymoshenko 	ret = OF_getprop_alloc(node, "name", (void **)&name);
4372cfe870aSOleksandr Tymoshenko 	if (ret == -1)
4382cfe870aSOleksandr Tymoshenko 		name = "SoC audio";
4392cfe870aSOleksandr Tymoshenko 
4402cfe870aSOleksandr Tymoshenko 	sc->name = strdup(name, M_DEVBUF);
4412cfe870aSOleksandr Tymoshenko 	device_set_desc(dev, sc->name);
4422cfe870aSOleksandr Tymoshenko 
4432cfe870aSOleksandr Tymoshenko 	if (ret != -1)
4442cfe870aSOleksandr Tymoshenko 		OF_prop_free(name);
4452cfe870aSOleksandr Tymoshenko 
4462cfe870aSOleksandr Tymoshenko 	SLIST_INIT(&sc->aux_devs);
4472cfe870aSOleksandr Tymoshenko 
4482cfe870aSOleksandr Tymoshenko 	ret = OF_getprop(node, "simple-audio-card,format", tmp, sizeof(tmp));
4492cfe870aSOleksandr Tymoshenko 	if (ret == 0) {
4502cfe870aSOleksandr Tymoshenko 		for (i = 0; i < nitems(ausoc_dai_formats); i++) {
4512cfe870aSOleksandr Tymoshenko 			if (strcmp(tmp, ausoc_dai_formats[i].name) == 0) {
4522cfe870aSOleksandr Tymoshenko 				fmt = ausoc_dai_formats[i].fmt;
4532cfe870aSOleksandr Tymoshenko 				break;
4542cfe870aSOleksandr Tymoshenko 			}
4552cfe870aSOleksandr Tymoshenko 		}
4562cfe870aSOleksandr Tymoshenko 		if (i == nitems(ausoc_dai_formats))
4572cfe870aSOleksandr Tymoshenko 			return (EINVAL);
4582cfe870aSOleksandr Tymoshenko 	} else
4592cfe870aSOleksandr Tymoshenko 		fmt = AUDIO_DAI_FORMAT_I2S;
4602cfe870aSOleksandr Tymoshenko 
4612cfe870aSOleksandr Tymoshenko 	if (OF_getencprop(node, "simple-audio-card,mclk-fs",
4622cfe870aSOleksandr Tymoshenko 	    &sc->link_mclk_fs, sizeof(sc->link_mclk_fs)) <= 0)
4632cfe870aSOleksandr Tymoshenko 		sc->link_mclk_fs = 0;
4642cfe870aSOleksandr Tymoshenko 
4652cfe870aSOleksandr Tymoshenko 	/* Unless specified otherwise, CPU node is the master */
4662cfe870aSOleksandr Tymoshenko 	frame_master = bitclock_master = true;
4672cfe870aSOleksandr Tymoshenko 
4682cfe870aSOleksandr Tymoshenko 	cpu_child = ofw_bus_find_child(node, "simple-audio-card,cpu");
4692cfe870aSOleksandr Tymoshenko 
4702cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(node, "simple-audio-card,frame-master", &xref, sizeof(xref))) > 0)
4712cfe870aSOleksandr Tymoshenko 		frame_master = cpu_child == OF_node_from_xref(xref);
4722cfe870aSOleksandr Tymoshenko 
4732cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(node, "simple-audio-card,bitclock-master", &xref, sizeof(xref))) > 0)
4742cfe870aSOleksandr Tymoshenko 		bitclock_master = cpu_child == OF_node_from_xref(xref);
4752cfe870aSOleksandr Tymoshenko 
4762cfe870aSOleksandr Tymoshenko 	if (frame_master) {
4772cfe870aSOleksandr Tymoshenko 		clk = bitclock_master ?
4782cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_CLOCK_CBM_CFM : AUDIO_DAI_CLOCK_CBS_CFM;
4792cfe870aSOleksandr Tymoshenko 	} else {
4802cfe870aSOleksandr Tymoshenko 		clk = bitclock_master ?
4812cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_CLOCK_CBM_CFS : AUDIO_DAI_CLOCK_CBS_CFS;
4822cfe870aSOleksandr Tymoshenko 	}
4832cfe870aSOleksandr Tymoshenko 
4842cfe870aSOleksandr Tymoshenko 	bool bitclock_inversion = OF_hasprop(node, "simple-audio-card,bitclock-inversion");
4852cfe870aSOleksandr Tymoshenko 	bool frame_inversion = OF_hasprop(node, "simple-audio-card,frame-inversion");
4862cfe870aSOleksandr Tymoshenko 	if (bitclock_inversion) {
4872cfe870aSOleksandr Tymoshenko 		pol = frame_inversion ?
4882cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_POLARITY_IB_IF : AUDIO_DAI_POLARITY_IB_NF;
4892cfe870aSOleksandr Tymoshenko 	} else {
4902cfe870aSOleksandr Tymoshenko 		pol = frame_inversion ?
4912cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_POLARITY_NB_IF : AUDIO_DAI_POLARITY_NB_NF;
4922cfe870aSOleksandr Tymoshenko 	}
4932cfe870aSOleksandr Tymoshenko 
4942cfe870aSOleksandr Tymoshenko 	sc->format = AUDIO_DAI_FORMAT(fmt, pol, clk);
4952cfe870aSOleksandr Tymoshenko 
4962cfe870aSOleksandr Tymoshenko 	sc->init_hook.ich_func = audio_soc_init;
4972cfe870aSOleksandr Tymoshenko 	sc->init_hook.ich_arg = sc;
4982cfe870aSOleksandr Tymoshenko 	if (config_intrhook_establish(&sc->init_hook) != 0)
4992cfe870aSOleksandr Tymoshenko 		return (ENOMEM);
5002cfe870aSOleksandr Tymoshenko 
5012cfe870aSOleksandr Tymoshenko 	return (0);
5022cfe870aSOleksandr Tymoshenko }
5032cfe870aSOleksandr Tymoshenko 
5042cfe870aSOleksandr Tymoshenko static int
audio_soc_detach(device_t dev)5052cfe870aSOleksandr Tymoshenko audio_soc_detach(device_t dev)
5062cfe870aSOleksandr Tymoshenko {
5072cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
5082cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux;
5092cfe870aSOleksandr Tymoshenko 
5102cfe870aSOleksandr Tymoshenko 	sc = device_get_softc(dev);
5112cfe870aSOleksandr Tymoshenko 	if (sc->name)
5122cfe870aSOleksandr Tymoshenko 		free(sc->name, M_DEVBUF);
5132cfe870aSOleksandr Tymoshenko 
5142cfe870aSOleksandr Tymoshenko 	while ((aux = SLIST_FIRST(&sc->aux_devs)) != NULL) {
5152cfe870aSOleksandr Tymoshenko 		SLIST_REMOVE_HEAD(&sc->aux_devs, link);
5162cfe870aSOleksandr Tymoshenko 		free(aux, M_DEVBUF);
5172cfe870aSOleksandr Tymoshenko 	}
5182cfe870aSOleksandr Tymoshenko 
5192cfe870aSOleksandr Tymoshenko 	return (0);
5202cfe870aSOleksandr Tymoshenko }
5212cfe870aSOleksandr Tymoshenko 
5222cfe870aSOleksandr Tymoshenko static device_method_t audio_soc_methods[] = {
5232cfe870aSOleksandr Tymoshenko         /* device_if methods */
5242cfe870aSOleksandr Tymoshenko 	DEVMETHOD(device_probe,		audio_soc_probe),
5252cfe870aSOleksandr Tymoshenko 	DEVMETHOD(device_attach,	audio_soc_attach),
5262cfe870aSOleksandr Tymoshenko 	DEVMETHOD(device_detach,	audio_soc_detach),
5272cfe870aSOleksandr Tymoshenko 
5282cfe870aSOleksandr Tymoshenko 	DEVMETHOD_END,
5292cfe870aSOleksandr Tymoshenko };
5302cfe870aSOleksandr Tymoshenko 
5312cfe870aSOleksandr Tymoshenko static driver_t audio_soc_driver = {
5322cfe870aSOleksandr Tymoshenko 	"pcm",
5332cfe870aSOleksandr Tymoshenko 	audio_soc_methods,
5342cfe870aSOleksandr Tymoshenko 	sizeof(struct audio_soc_softc),
5352cfe870aSOleksandr Tymoshenko };
5362cfe870aSOleksandr Tymoshenko 
5372287364eSJohn Baldwin DRIVER_MODULE(audio_soc, simplebus, audio_soc_driver, NULL, NULL);
5382cfe870aSOleksandr Tymoshenko MODULE_VERSION(audio_soc, 1);
539