xref: /freebsd/sys/dev/sound/fdt/audio_soc.c (revision 2cfe870acdecd35b621932f2b0cb702c48ce087a)
1*2cfe870aSOleksandr Tymoshenko /*-
2*2cfe870aSOleksandr Tymoshenko  * Copyright (c) 2019 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
3*2cfe870aSOleksandr Tymoshenko  *
4*2cfe870aSOleksandr Tymoshenko  * Redistribution and use in source and binary forms, with or without
5*2cfe870aSOleksandr Tymoshenko  * modification, are permitted provided that the following conditions
6*2cfe870aSOleksandr Tymoshenko  * are met:
7*2cfe870aSOleksandr Tymoshenko  * 1. Redistributions of source code must retain the above copyright
8*2cfe870aSOleksandr Tymoshenko  *    notice, this list of conditions and the following disclaimer.
9*2cfe870aSOleksandr Tymoshenko  * 2. Redistributions in binary form must reproduce the above copyright
10*2cfe870aSOleksandr Tymoshenko  *    notice, this list of conditions and the following disclaimer in the
11*2cfe870aSOleksandr Tymoshenko  *    documentation and/or other materials provided with the distribution.
12*2cfe870aSOleksandr Tymoshenko  *
13*2cfe870aSOleksandr Tymoshenko  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14*2cfe870aSOleksandr Tymoshenko  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15*2cfe870aSOleksandr Tymoshenko  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16*2cfe870aSOleksandr Tymoshenko  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17*2cfe870aSOleksandr Tymoshenko  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18*2cfe870aSOleksandr Tymoshenko  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19*2cfe870aSOleksandr Tymoshenko  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20*2cfe870aSOleksandr Tymoshenko  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21*2cfe870aSOleksandr Tymoshenko  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22*2cfe870aSOleksandr Tymoshenko  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23*2cfe870aSOleksandr Tymoshenko  *
24*2cfe870aSOleksandr Tymoshenko  */
25*2cfe870aSOleksandr Tymoshenko 
26*2cfe870aSOleksandr Tymoshenko #include <sys/cdefs.h>
27*2cfe870aSOleksandr Tymoshenko __FBSDID("$FreeBSD$");
28*2cfe870aSOleksandr Tymoshenko 
29*2cfe870aSOleksandr Tymoshenko #include "opt_platform.h"
30*2cfe870aSOleksandr Tymoshenko 
31*2cfe870aSOleksandr Tymoshenko #include <sys/param.h>
32*2cfe870aSOleksandr Tymoshenko #include <sys/systm.h>
33*2cfe870aSOleksandr Tymoshenko #include <sys/bus.h>
34*2cfe870aSOleksandr Tymoshenko #include <sys/clock.h>
35*2cfe870aSOleksandr Tymoshenko #include <sys/kernel.h>
36*2cfe870aSOleksandr Tymoshenko #include <sys/lock.h>
37*2cfe870aSOleksandr Tymoshenko #include <sys/module.h>
38*2cfe870aSOleksandr Tymoshenko #include <sys/endian.h>
39*2cfe870aSOleksandr Tymoshenko 
40*2cfe870aSOleksandr Tymoshenko #include <dev/ofw/ofw_bus.h>
41*2cfe870aSOleksandr Tymoshenko #include <dev/ofw/ofw_bus_subr.h>
42*2cfe870aSOleksandr Tymoshenko 
43*2cfe870aSOleksandr Tymoshenko #include <dev/sound/fdt/audio_dai.h>
44*2cfe870aSOleksandr Tymoshenko #include <dev/sound/pcm/sound.h>
45*2cfe870aSOleksandr Tymoshenko #include "audio_dai_if.h"
46*2cfe870aSOleksandr Tymoshenko 
47*2cfe870aSOleksandr Tymoshenko #define	AUDIO_BUFFER_SIZE	48000 * 4
48*2cfe870aSOleksandr Tymoshenko 
49*2cfe870aSOleksandr Tymoshenko struct audio_soc_aux_node {
50*2cfe870aSOleksandr Tymoshenko 	SLIST_ENTRY(audio_soc_aux_node)	link;
51*2cfe870aSOleksandr Tymoshenko 	device_t			dev;
52*2cfe870aSOleksandr Tymoshenko };
53*2cfe870aSOleksandr Tymoshenko 
54*2cfe870aSOleksandr Tymoshenko struct audio_soc_channel {
55*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc	*sc;	/* parent device's softc */
56*2cfe870aSOleksandr Tymoshenko 	struct pcm_channel 	*pcm;	/* PCM channel */
57*2cfe870aSOleksandr Tymoshenko 	struct snd_dbuf		*buf;	/* PCM buffer */
58*2cfe870aSOleksandr Tymoshenko 	int			dir;	/* direction */
59*2cfe870aSOleksandr Tymoshenko };
60*2cfe870aSOleksandr Tymoshenko 
61*2cfe870aSOleksandr Tymoshenko struct audio_soc_softc {
62*2cfe870aSOleksandr Tymoshenko 	/*
63*2cfe870aSOleksandr Tymoshenko 	 * pcm_register assumes that sc is snddev_info,
64*2cfe870aSOleksandr Tymoshenko 	 * so this has to be first structure member for "compatiblity"
65*2cfe870aSOleksandr Tymoshenko 	 */
66*2cfe870aSOleksandr Tymoshenko 	struct snddev_info	info;
67*2cfe870aSOleksandr Tymoshenko 	device_t		dev;
68*2cfe870aSOleksandr Tymoshenko 	char			*name;
69*2cfe870aSOleksandr Tymoshenko 	struct intr_config_hook init_hook;
70*2cfe870aSOleksandr Tymoshenko 	device_t		cpu_dev;
71*2cfe870aSOleksandr Tymoshenko 	device_t		codec_dev;
72*2cfe870aSOleksandr Tymoshenko 	SLIST_HEAD(, audio_soc_aux_node)	aux_devs;
73*2cfe870aSOleksandr Tymoshenko 	unsigned int		mclk_fs;
74*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel 	play_channel;
75*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel 	rec_channel;
76*2cfe870aSOleksandr Tymoshenko 	/*
77*2cfe870aSOleksandr Tymoshenko 	 * The format is from the CPU node, for CODEC node clock roles
78*2cfe870aSOleksandr Tymoshenko 	 * need to be reversed.
79*2cfe870aSOleksandr Tymoshenko 	 */
80*2cfe870aSOleksandr Tymoshenko 	uint32_t		format;
81*2cfe870aSOleksandr Tymoshenko 	uint32_t		link_mclk_fs;
82*2cfe870aSOleksandr Tymoshenko };
83*2cfe870aSOleksandr Tymoshenko 
84*2cfe870aSOleksandr Tymoshenko static struct ofw_compat_data compat_data[] = {
85*2cfe870aSOleksandr Tymoshenko 	{"simple-audio-card",	1},
86*2cfe870aSOleksandr Tymoshenko 	{NULL,			0},
87*2cfe870aSOleksandr Tymoshenko };
88*2cfe870aSOleksandr Tymoshenko 
89*2cfe870aSOleksandr Tymoshenko static struct {
90*2cfe870aSOleksandr Tymoshenko 	const char *name;
91*2cfe870aSOleksandr Tymoshenko 	unsigned int fmt;
92*2cfe870aSOleksandr Tymoshenko } ausoc_dai_formats[] = {
93*2cfe870aSOleksandr Tymoshenko 	{ "i2s",	AUDIO_DAI_FORMAT_I2S },
94*2cfe870aSOleksandr Tymoshenko 	{ "right_j",	AUDIO_DAI_FORMAT_RJ },
95*2cfe870aSOleksandr Tymoshenko 	{ "left_j",	AUDIO_DAI_FORMAT_LJ },
96*2cfe870aSOleksandr Tymoshenko 	{ "dsp_a",	AUDIO_DAI_FORMAT_DSPA },
97*2cfe870aSOleksandr Tymoshenko 	{ "dsp_b",	AUDIO_DAI_FORMAT_DSPB },
98*2cfe870aSOleksandr Tymoshenko 	{ "ac97",	AUDIO_DAI_FORMAT_AC97 },
99*2cfe870aSOleksandr Tymoshenko 	{ "pdm",	AUDIO_DAI_FORMAT_PDM },
100*2cfe870aSOleksandr Tymoshenko };
101*2cfe870aSOleksandr Tymoshenko 
102*2cfe870aSOleksandr Tymoshenko static int	audio_soc_probe(device_t dev);
103*2cfe870aSOleksandr Tymoshenko static int	audio_soc_attach(device_t dev);
104*2cfe870aSOleksandr Tymoshenko static int	audio_soc_detach(device_t dev);
105*2cfe870aSOleksandr Tymoshenko 
106*2cfe870aSOleksandr Tymoshenko /*
107*2cfe870aSOleksandr Tymoshenko  * Invert master/slave roles for CODEC side of the node
108*2cfe870aSOleksandr Tymoshenko  */
109*2cfe870aSOleksandr Tymoshenko static uint32_t
110*2cfe870aSOleksandr Tymoshenko audio_soc_reverse_clocks(uint32_t format)
111*2cfe870aSOleksandr Tymoshenko {
112*2cfe870aSOleksandr Tymoshenko 	int fmt, pol, clk;
113*2cfe870aSOleksandr Tymoshenko 
114*2cfe870aSOleksandr Tymoshenko 	fmt = AUDIO_DAI_FORMAT_FORMAT(format);
115*2cfe870aSOleksandr Tymoshenko 	pol = AUDIO_DAI_FORMAT_POLARITY(format);
116*2cfe870aSOleksandr Tymoshenko 	clk = AUDIO_DAI_FORMAT_CLOCK(format);
117*2cfe870aSOleksandr Tymoshenko 
118*2cfe870aSOleksandr Tymoshenko 	switch (clk) {
119*2cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBM_CFM:
120*2cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBS_CFS;
121*2cfe870aSOleksandr Tymoshenko 		break;
122*2cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBS_CFM:
123*2cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBM_CFS;
124*2cfe870aSOleksandr Tymoshenko 		break;
125*2cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBM_CFS:
126*2cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBS_CFM;
127*2cfe870aSOleksandr Tymoshenko 		break;
128*2cfe870aSOleksandr Tymoshenko 	case AUDIO_DAI_CLOCK_CBS_CFS:
129*2cfe870aSOleksandr Tymoshenko 		clk = AUDIO_DAI_CLOCK_CBM_CFM;
130*2cfe870aSOleksandr Tymoshenko 		break;
131*2cfe870aSOleksandr Tymoshenko 	}
132*2cfe870aSOleksandr Tymoshenko 
133*2cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_FORMAT(fmt, pol, clk);
134*2cfe870aSOleksandr Tymoshenko }
135*2cfe870aSOleksandr Tymoshenko 
136*2cfe870aSOleksandr Tymoshenko static uint32_t
137*2cfe870aSOleksandr Tymoshenko audio_soc_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksz)
138*2cfe870aSOleksandr Tymoshenko {
139*2cfe870aSOleksandr Tymoshenko 
140*2cfe870aSOleksandr Tymoshenko 	return (blocksz);
141*2cfe870aSOleksandr Tymoshenko }
142*2cfe870aSOleksandr Tymoshenko 
143*2cfe870aSOleksandr Tymoshenko static int
144*2cfe870aSOleksandr Tymoshenko audio_soc_chan_setformat(kobj_t obj, void *data, uint32_t format)
145*2cfe870aSOleksandr Tymoshenko {
146*2cfe870aSOleksandr Tymoshenko 
147*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
148*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
149*2cfe870aSOleksandr Tymoshenko 
150*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
151*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
152*2cfe870aSOleksandr Tymoshenko 
153*2cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_SET_CHANFORMAT(sc->cpu_dev, format);
154*2cfe870aSOleksandr Tymoshenko }
155*2cfe870aSOleksandr Tymoshenko 
156*2cfe870aSOleksandr Tymoshenko static uint32_t
157*2cfe870aSOleksandr Tymoshenko audio_soc_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
158*2cfe870aSOleksandr Tymoshenko {
159*2cfe870aSOleksandr Tymoshenko 
160*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
161*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
162*2cfe870aSOleksandr Tymoshenko 	uint32_t rate;
163*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux_node;
164*2cfe870aSOleksandr Tymoshenko 
165*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
166*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
167*2cfe870aSOleksandr Tymoshenko 
168*2cfe870aSOleksandr Tymoshenko 	if (sc->link_mclk_fs) {
169*2cfe870aSOleksandr Tymoshenko 		rate = speed * sc->link_mclk_fs;
170*2cfe870aSOleksandr Tymoshenko 		if (AUDIO_DAI_SET_SYSCLK(sc->cpu_dev, rate, AUDIO_DAI_CLOCK_IN))
171*2cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to set sysclk for CPU node\n");
172*2cfe870aSOleksandr Tymoshenko 
173*2cfe870aSOleksandr Tymoshenko 		if (AUDIO_DAI_SET_SYSCLK(sc->codec_dev, rate, AUDIO_DAI_CLOCK_OUT))
174*2cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to set sysclk for codec node\n");
175*2cfe870aSOleksandr Tymoshenko 
176*2cfe870aSOleksandr Tymoshenko 		SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
177*2cfe870aSOleksandr Tymoshenko 			if (AUDIO_DAI_SET_SYSCLK(aux_node->dev, rate, AUDIO_DAI_CLOCK_OUT))
178*2cfe870aSOleksandr Tymoshenko 				device_printf(sc->dev, "failed to set sysclk for aux node\n");
179*2cfe870aSOleksandr Tymoshenko 		}
180*2cfe870aSOleksandr Tymoshenko 	}
181*2cfe870aSOleksandr Tymoshenko 
182*2cfe870aSOleksandr Tymoshenko 	/*
183*2cfe870aSOleksandr Tymoshenko 	 * Let CPU node determine speed
184*2cfe870aSOleksandr Tymoshenko 	 */
185*2cfe870aSOleksandr Tymoshenko 	speed = AUDIO_DAI_SET_CHANSPEED(sc->cpu_dev, speed);
186*2cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_SET_CHANSPEED(sc->codec_dev, speed);
187*2cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
188*2cfe870aSOleksandr Tymoshenko 		AUDIO_DAI_SET_CHANSPEED(aux_node->dev, speed);
189*2cfe870aSOleksandr Tymoshenko 	}
190*2cfe870aSOleksandr Tymoshenko 
191*2cfe870aSOleksandr Tymoshenko 	return (speed);
192*2cfe870aSOleksandr Tymoshenko }
193*2cfe870aSOleksandr Tymoshenko 
194*2cfe870aSOleksandr Tymoshenko static uint32_t
195*2cfe870aSOleksandr Tymoshenko audio_soc_chan_getptr(kobj_t obj, void *data)
196*2cfe870aSOleksandr Tymoshenko {
197*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
198*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
199*2cfe870aSOleksandr Tymoshenko 
200*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
201*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
202*2cfe870aSOleksandr Tymoshenko 
203*2cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_GET_PTR(sc->cpu_dev, ausoc_chan->dir);
204*2cfe870aSOleksandr Tymoshenko }
205*2cfe870aSOleksandr Tymoshenko 
206*2cfe870aSOleksandr Tymoshenko static void *
207*2cfe870aSOleksandr Tymoshenko audio_soc_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
208*2cfe870aSOleksandr Tymoshenko 	struct pcm_channel *c, int dir)
209*2cfe870aSOleksandr Tymoshenko {
210*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
211*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
212*2cfe870aSOleksandr Tymoshenko 	void *buffer;
213*2cfe870aSOleksandr Tymoshenko 
214*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = devinfo;
215*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
216*2cfe870aSOleksandr Tymoshenko 	buffer = malloc(AUDIO_BUFFER_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
217*2cfe870aSOleksandr Tymoshenko 
218*2cfe870aSOleksandr Tymoshenko 	if (sndbuf_setup(b, buffer, AUDIO_BUFFER_SIZE) != 0) {
219*2cfe870aSOleksandr Tymoshenko 		free(buffer, M_DEVBUF);
220*2cfe870aSOleksandr Tymoshenko 		return NULL;
221*2cfe870aSOleksandr Tymoshenko 	}
222*2cfe870aSOleksandr Tymoshenko 
223*2cfe870aSOleksandr Tymoshenko 	ausoc_chan->dir = dir;
224*2cfe870aSOleksandr Tymoshenko 	ausoc_chan->buf = b;
225*2cfe870aSOleksandr Tymoshenko 	ausoc_chan->pcm = c;
226*2cfe870aSOleksandr Tymoshenko 
227*2cfe870aSOleksandr Tymoshenko 	return (devinfo);
228*2cfe870aSOleksandr Tymoshenko }
229*2cfe870aSOleksandr Tymoshenko 
230*2cfe870aSOleksandr Tymoshenko static int
231*2cfe870aSOleksandr Tymoshenko audio_soc_chan_trigger(kobj_t obj, void *data, int go)
232*2cfe870aSOleksandr Tymoshenko {
233*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
234*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
235*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux_node;
236*2cfe870aSOleksandr Tymoshenko 
237*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = (struct audio_soc_channel *)data;
238*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
239*2cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_TRIGGER(sc->codec_dev, go, ausoc_chan->dir);
240*2cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
241*2cfe870aSOleksandr Tymoshenko 		AUDIO_DAI_TRIGGER(aux_node->dev, go, ausoc_chan->dir);
242*2cfe870aSOleksandr Tymoshenko 	}
243*2cfe870aSOleksandr Tymoshenko 
244*2cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_TRIGGER(sc->cpu_dev, go, ausoc_chan->dir);
245*2cfe870aSOleksandr Tymoshenko }
246*2cfe870aSOleksandr Tymoshenko 
247*2cfe870aSOleksandr Tymoshenko static int
248*2cfe870aSOleksandr Tymoshenko audio_soc_chan_free(kobj_t obj, void *data)
249*2cfe870aSOleksandr Tymoshenko {
250*2cfe870aSOleksandr Tymoshenko 
251*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
252*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
253*2cfe870aSOleksandr Tymoshenko 	void *buffer;
254*2cfe870aSOleksandr Tymoshenko 
255*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = (struct audio_soc_channel *)data;
256*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
257*2cfe870aSOleksandr Tymoshenko 
258*2cfe870aSOleksandr Tymoshenko 	buffer = sndbuf_getbuf(ausoc_chan->buf);
259*2cfe870aSOleksandr Tymoshenko 	if (buffer)
260*2cfe870aSOleksandr Tymoshenko 		free(buffer, M_DEVBUF);
261*2cfe870aSOleksandr Tymoshenko 
262*2cfe870aSOleksandr Tymoshenko 	return (0);
263*2cfe870aSOleksandr Tymoshenko }
264*2cfe870aSOleksandr Tymoshenko 
265*2cfe870aSOleksandr Tymoshenko static struct pcmchan_caps *
266*2cfe870aSOleksandr Tymoshenko audio_soc_chan_getcaps(kobj_t obj, void *data)
267*2cfe870aSOleksandr Tymoshenko {
268*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
269*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_channel *ausoc_chan;
270*2cfe870aSOleksandr Tymoshenko 
271*2cfe870aSOleksandr Tymoshenko 	ausoc_chan = data;
272*2cfe870aSOleksandr Tymoshenko 	sc = ausoc_chan->sc;
273*2cfe870aSOleksandr Tymoshenko 
274*2cfe870aSOleksandr Tymoshenko 	return AUDIO_DAI_GET_CAPS(sc->cpu_dev);
275*2cfe870aSOleksandr Tymoshenko }
276*2cfe870aSOleksandr Tymoshenko 
277*2cfe870aSOleksandr Tymoshenko static kobj_method_t audio_soc_chan_methods[] = {
278*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_init, 	audio_soc_chan_init),
279*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_free, 	audio_soc_chan_free),
280*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_setformat, 	audio_soc_chan_setformat),
281*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_setspeed, 	audio_soc_chan_setspeed),
282*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_setblocksize,audio_soc_chan_setblocksize),
283*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_trigger,	audio_soc_chan_trigger),
284*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_getptr,	audio_soc_chan_getptr),
285*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD(channel_getcaps,	audio_soc_chan_getcaps),
286*2cfe870aSOleksandr Tymoshenko 	KOBJMETHOD_END
287*2cfe870aSOleksandr Tymoshenko };
288*2cfe870aSOleksandr Tymoshenko CHANNEL_DECLARE(audio_soc_chan);
289*2cfe870aSOleksandr Tymoshenko 
290*2cfe870aSOleksandr Tymoshenko static void
291*2cfe870aSOleksandr Tymoshenko audio_soc_intr(void *arg)
292*2cfe870aSOleksandr Tymoshenko {
293*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
294*2cfe870aSOleksandr Tymoshenko 	int channel_intr_required;
295*2cfe870aSOleksandr Tymoshenko 
296*2cfe870aSOleksandr Tymoshenko 	sc = (struct audio_soc_softc *)arg;
297*2cfe870aSOleksandr Tymoshenko 	channel_intr_required = AUDIO_DAI_INTR(sc->cpu_dev, sc->play_channel.buf, sc->rec_channel.buf);
298*2cfe870aSOleksandr Tymoshenko 	if (channel_intr_required & AUDIO_DAI_PLAY_INTR)
299*2cfe870aSOleksandr Tymoshenko 		chn_intr(sc->play_channel.pcm);
300*2cfe870aSOleksandr Tymoshenko 	if (channel_intr_required & AUDIO_DAI_REC_INTR)
301*2cfe870aSOleksandr Tymoshenko 		chn_intr(sc->rec_channel.pcm);
302*2cfe870aSOleksandr Tymoshenko }
303*2cfe870aSOleksandr Tymoshenko 
304*2cfe870aSOleksandr Tymoshenko static int
305*2cfe870aSOleksandr Tymoshenko audio_soc_probe(device_t dev)
306*2cfe870aSOleksandr Tymoshenko {
307*2cfe870aSOleksandr Tymoshenko 
308*2cfe870aSOleksandr Tymoshenko 	if (!ofw_bus_status_okay(dev))
309*2cfe870aSOleksandr Tymoshenko 		return (ENXIO);
310*2cfe870aSOleksandr Tymoshenko 
311*2cfe870aSOleksandr Tymoshenko 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) {
312*2cfe870aSOleksandr Tymoshenko 		device_set_desc(dev, "simple-audio-card");
313*2cfe870aSOleksandr Tymoshenko 		return (BUS_PROBE_DEFAULT);
314*2cfe870aSOleksandr Tymoshenko 	}
315*2cfe870aSOleksandr Tymoshenko 
316*2cfe870aSOleksandr Tymoshenko 	return (ENXIO);
317*2cfe870aSOleksandr Tymoshenko }
318*2cfe870aSOleksandr Tymoshenko 
319*2cfe870aSOleksandr Tymoshenko static void
320*2cfe870aSOleksandr Tymoshenko audio_soc_init(void *arg)
321*2cfe870aSOleksandr Tymoshenko {
322*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
323*2cfe870aSOleksandr Tymoshenko 	phandle_t node, child;
324*2cfe870aSOleksandr Tymoshenko 	device_t daidev, auxdev;
325*2cfe870aSOleksandr Tymoshenko 	uint32_t xref;
326*2cfe870aSOleksandr Tymoshenko 	uint32_t *aux_devs;
327*2cfe870aSOleksandr Tymoshenko 	int ncells, i;
328*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux_node;
329*2cfe870aSOleksandr Tymoshenko 
330*2cfe870aSOleksandr Tymoshenko 	sc = (struct audio_soc_softc *)arg;
331*2cfe870aSOleksandr Tymoshenko 	config_intrhook_disestablish(&sc->init_hook);
332*2cfe870aSOleksandr Tymoshenko 
333*2cfe870aSOleksandr Tymoshenko 	node = ofw_bus_get_node(sc->dev);
334*2cfe870aSOleksandr Tymoshenko 	/* TODO: handle multi-link nodes */
335*2cfe870aSOleksandr Tymoshenko 	child = ofw_bus_find_child(node, "simple-audio-card,cpu");
336*2cfe870aSOleksandr Tymoshenko 	if (child == 0) {
337*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "cpu node is missing\n");
338*2cfe870aSOleksandr Tymoshenko 		return;
339*2cfe870aSOleksandr Tymoshenko 	}
340*2cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
341*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "missing sound-dai property in cpu node\n");
342*2cfe870aSOleksandr Tymoshenko 		return;
343*2cfe870aSOleksandr Tymoshenko 	}
344*2cfe870aSOleksandr Tymoshenko 	daidev = OF_device_from_xref(xref);
345*2cfe870aSOleksandr Tymoshenko 	if (daidev == NULL) {
346*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "no driver attached to cpu node\n");
347*2cfe870aSOleksandr Tymoshenko 		return;
348*2cfe870aSOleksandr Tymoshenko 	}
349*2cfe870aSOleksandr Tymoshenko 	sc->cpu_dev = daidev;
350*2cfe870aSOleksandr Tymoshenko 
351*2cfe870aSOleksandr Tymoshenko 	child = ofw_bus_find_child(node, "simple-audio-card,codec");
352*2cfe870aSOleksandr Tymoshenko 	if (child == 0) {
353*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "codec node is missing\n");
354*2cfe870aSOleksandr Tymoshenko 		return;
355*2cfe870aSOleksandr Tymoshenko 	}
356*2cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) {
357*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "missing sound-dai property in codec node\n");
358*2cfe870aSOleksandr Tymoshenko 		return;
359*2cfe870aSOleksandr Tymoshenko 	}
360*2cfe870aSOleksandr Tymoshenko 	daidev = OF_device_from_xref(xref);
361*2cfe870aSOleksandr Tymoshenko 	if (daidev == NULL) {
362*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "no driver attached to codec node\n");
363*2cfe870aSOleksandr Tymoshenko 		return;
364*2cfe870aSOleksandr Tymoshenko 	}
365*2cfe870aSOleksandr Tymoshenko 	sc->codec_dev = daidev;
366*2cfe870aSOleksandr Tymoshenko 
367*2cfe870aSOleksandr Tymoshenko 	/* Add AUX devices */
368*2cfe870aSOleksandr Tymoshenko 	aux_devs = NULL;
369*2cfe870aSOleksandr Tymoshenko 	ncells = OF_getencprop_alloc_multi(node, "simple-audio-card,aux-devs", sizeof(*aux_devs),
370*2cfe870aSOleksandr Tymoshenko 	    (void **)&aux_devs);
371*2cfe870aSOleksandr Tymoshenko 
372*2cfe870aSOleksandr Tymoshenko 	for (i = 0; i < ncells; i++) {
373*2cfe870aSOleksandr Tymoshenko 		auxdev = OF_device_from_xref(aux_devs[i]);
374*2cfe870aSOleksandr Tymoshenko 		if (auxdev == NULL)
375*2cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "warning: no driver attached to aux node\n");
376*2cfe870aSOleksandr Tymoshenko 		aux_node = (struct audio_soc_aux_node *)malloc(sizeof(*aux_node), M_DEVBUF, M_NOWAIT);
377*2cfe870aSOleksandr Tymoshenko 		if (aux_node == NULL) {
378*2cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to allocate aux node struct\n");
379*2cfe870aSOleksandr Tymoshenko 			return;
380*2cfe870aSOleksandr Tymoshenko 		}
381*2cfe870aSOleksandr Tymoshenko 		aux_node->dev = auxdev;
382*2cfe870aSOleksandr Tymoshenko 		SLIST_INSERT_HEAD(&sc->aux_devs, aux_node, link);
383*2cfe870aSOleksandr Tymoshenko 	}
384*2cfe870aSOleksandr Tymoshenko 
385*2cfe870aSOleksandr Tymoshenko 	if (aux_devs)
386*2cfe870aSOleksandr Tymoshenko 		OF_prop_free(aux_devs);
387*2cfe870aSOleksandr Tymoshenko 
388*2cfe870aSOleksandr Tymoshenko 	if (AUDIO_DAI_INIT(sc->cpu_dev, sc->format)) {
389*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "failed to initialize cpu node\n");
390*2cfe870aSOleksandr Tymoshenko 		return;
391*2cfe870aSOleksandr Tymoshenko 	}
392*2cfe870aSOleksandr Tymoshenko 
393*2cfe870aSOleksandr Tymoshenko 	/* Reverse clock roles for CODEC */
394*2cfe870aSOleksandr Tymoshenko 	if (AUDIO_DAI_INIT(sc->codec_dev, audio_soc_reverse_clocks(sc->format))) {
395*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "failed to initialize codec node\n");
396*2cfe870aSOleksandr Tymoshenko 		return;
397*2cfe870aSOleksandr Tymoshenko 	}
398*2cfe870aSOleksandr Tymoshenko 
399*2cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
400*2cfe870aSOleksandr Tymoshenko 		if (AUDIO_DAI_INIT(aux_node->dev, audio_soc_reverse_clocks(sc->format))) {
401*2cfe870aSOleksandr Tymoshenko 			device_printf(sc->dev, "failed to initialize aux node\n");
402*2cfe870aSOleksandr Tymoshenko 			return;
403*2cfe870aSOleksandr Tymoshenko 		}
404*2cfe870aSOleksandr Tymoshenko 	}
405*2cfe870aSOleksandr Tymoshenko 
406*2cfe870aSOleksandr Tymoshenko 	if (pcm_register(sc->dev, sc, 1, 1)) {
407*2cfe870aSOleksandr Tymoshenko 		device_printf(sc->dev, "failed to register PCM\n");
408*2cfe870aSOleksandr Tymoshenko 		return;
409*2cfe870aSOleksandr Tymoshenko 	}
410*2cfe870aSOleksandr Tymoshenko 
411*2cfe870aSOleksandr Tymoshenko 	pcm_getbuffersize(sc->dev, AUDIO_BUFFER_SIZE, AUDIO_BUFFER_SIZE,
412*2cfe870aSOleksandr Tymoshenko 	    AUDIO_BUFFER_SIZE);
413*2cfe870aSOleksandr Tymoshenko 	sc->play_channel.sc = sc;
414*2cfe870aSOleksandr Tymoshenko 	sc->rec_channel.sc = sc;
415*2cfe870aSOleksandr Tymoshenko 
416*2cfe870aSOleksandr Tymoshenko 	pcm_addchan(sc->dev, PCMDIR_PLAY, &audio_soc_chan_class, &sc->play_channel);
417*2cfe870aSOleksandr Tymoshenko 	pcm_addchan(sc->dev, PCMDIR_REC, &audio_soc_chan_class, &sc->rec_channel);
418*2cfe870aSOleksandr Tymoshenko 
419*2cfe870aSOleksandr Tymoshenko 	pcm_setstatus(sc->dev, "at EXPERIMENT");
420*2cfe870aSOleksandr Tymoshenko 
421*2cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_SETUP_INTR(sc->cpu_dev, audio_soc_intr, sc);
422*2cfe870aSOleksandr Tymoshenko 	AUDIO_DAI_SETUP_MIXER(sc->codec_dev, sc->dev);
423*2cfe870aSOleksandr Tymoshenko 	SLIST_FOREACH(aux_node, &sc->aux_devs, link) {
424*2cfe870aSOleksandr Tymoshenko 		AUDIO_DAI_SETUP_MIXER(aux_node->dev, sc->dev);
425*2cfe870aSOleksandr Tymoshenko 	}
426*2cfe870aSOleksandr Tymoshenko }
427*2cfe870aSOleksandr Tymoshenko 
428*2cfe870aSOleksandr Tymoshenko static int
429*2cfe870aSOleksandr Tymoshenko audio_soc_attach(device_t dev)
430*2cfe870aSOleksandr Tymoshenko {
431*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
432*2cfe870aSOleksandr Tymoshenko 	char *name;
433*2cfe870aSOleksandr Tymoshenko 	phandle_t node, cpu_child;
434*2cfe870aSOleksandr Tymoshenko 	uint32_t xref;
435*2cfe870aSOleksandr Tymoshenko 	int i, ret;
436*2cfe870aSOleksandr Tymoshenko 	char tmp[32];
437*2cfe870aSOleksandr Tymoshenko 	unsigned int fmt, pol, clk;
438*2cfe870aSOleksandr Tymoshenko 	bool frame_master, bitclock_master;
439*2cfe870aSOleksandr Tymoshenko 
440*2cfe870aSOleksandr Tymoshenko 	sc = device_get_softc(dev);
441*2cfe870aSOleksandr Tymoshenko 	sc->dev = dev;
442*2cfe870aSOleksandr Tymoshenko 	node = ofw_bus_get_node(dev);
443*2cfe870aSOleksandr Tymoshenko 
444*2cfe870aSOleksandr Tymoshenko 	ret = OF_getprop_alloc(node, "name", (void **)&name);
445*2cfe870aSOleksandr Tymoshenko 	if (ret == -1)
446*2cfe870aSOleksandr Tymoshenko 		name = "SoC audio";
447*2cfe870aSOleksandr Tymoshenko 
448*2cfe870aSOleksandr Tymoshenko 	sc->name = strdup(name, M_DEVBUF);
449*2cfe870aSOleksandr Tymoshenko 	device_set_desc(dev, sc->name);
450*2cfe870aSOleksandr Tymoshenko 
451*2cfe870aSOleksandr Tymoshenko 	if (ret != -1)
452*2cfe870aSOleksandr Tymoshenko 		OF_prop_free(name);
453*2cfe870aSOleksandr Tymoshenko 
454*2cfe870aSOleksandr Tymoshenko 	SLIST_INIT(&sc->aux_devs);
455*2cfe870aSOleksandr Tymoshenko 
456*2cfe870aSOleksandr Tymoshenko 	ret = OF_getprop(node, "simple-audio-card,format", tmp, sizeof(tmp));
457*2cfe870aSOleksandr Tymoshenko 	if (ret == 0) {
458*2cfe870aSOleksandr Tymoshenko 		for (i = 0; i < nitems(ausoc_dai_formats); i++) {
459*2cfe870aSOleksandr Tymoshenko 			if (strcmp(tmp, ausoc_dai_formats[i].name) == 0) {
460*2cfe870aSOleksandr Tymoshenko 				fmt = ausoc_dai_formats[i].fmt;
461*2cfe870aSOleksandr Tymoshenko 				break;
462*2cfe870aSOleksandr Tymoshenko 			}
463*2cfe870aSOleksandr Tymoshenko 		}
464*2cfe870aSOleksandr Tymoshenko 		if (i == nitems(ausoc_dai_formats))
465*2cfe870aSOleksandr Tymoshenko 			return (EINVAL);
466*2cfe870aSOleksandr Tymoshenko 	} else
467*2cfe870aSOleksandr Tymoshenko 		fmt = AUDIO_DAI_FORMAT_I2S;
468*2cfe870aSOleksandr Tymoshenko 
469*2cfe870aSOleksandr Tymoshenko 	if (OF_getencprop(node, "simple-audio-card,mclk-fs",
470*2cfe870aSOleksandr Tymoshenko 	    &sc->link_mclk_fs, sizeof(sc->link_mclk_fs)) <= 0)
471*2cfe870aSOleksandr Tymoshenko 		sc->link_mclk_fs = 0;
472*2cfe870aSOleksandr Tymoshenko 
473*2cfe870aSOleksandr Tymoshenko 	/* Unless specified otherwise, CPU node is the master */
474*2cfe870aSOleksandr Tymoshenko 	frame_master = bitclock_master = true;
475*2cfe870aSOleksandr Tymoshenko 
476*2cfe870aSOleksandr Tymoshenko 	cpu_child = ofw_bus_find_child(node, "simple-audio-card,cpu");
477*2cfe870aSOleksandr Tymoshenko 
478*2cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(node, "simple-audio-card,frame-master", &xref, sizeof(xref))) > 0)
479*2cfe870aSOleksandr Tymoshenko 		frame_master = cpu_child == OF_node_from_xref(xref);
480*2cfe870aSOleksandr Tymoshenko 
481*2cfe870aSOleksandr Tymoshenko 	if ((OF_getencprop(node, "simple-audio-card,bitclock-master", &xref, sizeof(xref))) > 0)
482*2cfe870aSOleksandr Tymoshenko 		bitclock_master = cpu_child == OF_node_from_xref(xref);
483*2cfe870aSOleksandr Tymoshenko 
484*2cfe870aSOleksandr Tymoshenko 	if (frame_master) {
485*2cfe870aSOleksandr Tymoshenko 		clk = bitclock_master ?
486*2cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_CLOCK_CBM_CFM : AUDIO_DAI_CLOCK_CBS_CFM;
487*2cfe870aSOleksandr Tymoshenko 	} else {
488*2cfe870aSOleksandr Tymoshenko 		clk = bitclock_master ?
489*2cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_CLOCK_CBM_CFS : AUDIO_DAI_CLOCK_CBS_CFS;
490*2cfe870aSOleksandr Tymoshenko 	}
491*2cfe870aSOleksandr Tymoshenko 
492*2cfe870aSOleksandr Tymoshenko 	bool bitclock_inversion = OF_hasprop(node, "simple-audio-card,bitclock-inversion");
493*2cfe870aSOleksandr Tymoshenko 	bool frame_inversion = OF_hasprop(node, "simple-audio-card,frame-inversion");
494*2cfe870aSOleksandr Tymoshenko 	if (bitclock_inversion) {
495*2cfe870aSOleksandr Tymoshenko 		pol = frame_inversion ?
496*2cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_POLARITY_IB_IF : AUDIO_DAI_POLARITY_IB_NF;
497*2cfe870aSOleksandr Tymoshenko 	} else {
498*2cfe870aSOleksandr Tymoshenko 		pol = frame_inversion ?
499*2cfe870aSOleksandr Tymoshenko 		    AUDIO_DAI_POLARITY_NB_IF : AUDIO_DAI_POLARITY_NB_NF;
500*2cfe870aSOleksandr Tymoshenko 	}
501*2cfe870aSOleksandr Tymoshenko 
502*2cfe870aSOleksandr Tymoshenko 	sc->format = AUDIO_DAI_FORMAT(fmt, pol, clk);
503*2cfe870aSOleksandr Tymoshenko 
504*2cfe870aSOleksandr Tymoshenko 	sc->init_hook.ich_func = audio_soc_init;
505*2cfe870aSOleksandr Tymoshenko 	sc->init_hook.ich_arg = sc;
506*2cfe870aSOleksandr Tymoshenko 	if (config_intrhook_establish(&sc->init_hook) != 0)
507*2cfe870aSOleksandr Tymoshenko 		return (ENOMEM);
508*2cfe870aSOleksandr Tymoshenko 
509*2cfe870aSOleksandr Tymoshenko 	return (0);
510*2cfe870aSOleksandr Tymoshenko }
511*2cfe870aSOleksandr Tymoshenko 
512*2cfe870aSOleksandr Tymoshenko static int
513*2cfe870aSOleksandr Tymoshenko audio_soc_detach(device_t dev)
514*2cfe870aSOleksandr Tymoshenko {
515*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_softc *sc;
516*2cfe870aSOleksandr Tymoshenko 	struct audio_soc_aux_node *aux;
517*2cfe870aSOleksandr Tymoshenko 
518*2cfe870aSOleksandr Tymoshenko 	sc = device_get_softc(dev);
519*2cfe870aSOleksandr Tymoshenko 	if (sc->name)
520*2cfe870aSOleksandr Tymoshenko 		free(sc->name, M_DEVBUF);
521*2cfe870aSOleksandr Tymoshenko 
522*2cfe870aSOleksandr Tymoshenko 	while ((aux = SLIST_FIRST(&sc->aux_devs)) != NULL) {
523*2cfe870aSOleksandr Tymoshenko 		SLIST_REMOVE_HEAD(&sc->aux_devs, link);
524*2cfe870aSOleksandr Tymoshenko 		free(aux, M_DEVBUF);
525*2cfe870aSOleksandr Tymoshenko 	}
526*2cfe870aSOleksandr Tymoshenko 
527*2cfe870aSOleksandr Tymoshenko 	return (0);
528*2cfe870aSOleksandr Tymoshenko }
529*2cfe870aSOleksandr Tymoshenko 
530*2cfe870aSOleksandr Tymoshenko static device_method_t audio_soc_methods[] = {
531*2cfe870aSOleksandr Tymoshenko         /* device_if methods */
532*2cfe870aSOleksandr Tymoshenko 	DEVMETHOD(device_probe,		audio_soc_probe),
533*2cfe870aSOleksandr Tymoshenko 	DEVMETHOD(device_attach,	audio_soc_attach),
534*2cfe870aSOleksandr Tymoshenko 	DEVMETHOD(device_detach,	audio_soc_detach),
535*2cfe870aSOleksandr Tymoshenko 
536*2cfe870aSOleksandr Tymoshenko 	DEVMETHOD_END,
537*2cfe870aSOleksandr Tymoshenko };
538*2cfe870aSOleksandr Tymoshenko 
539*2cfe870aSOleksandr Tymoshenko static driver_t audio_soc_driver = {
540*2cfe870aSOleksandr Tymoshenko 	"pcm",
541*2cfe870aSOleksandr Tymoshenko 	audio_soc_methods,
542*2cfe870aSOleksandr Tymoshenko 	sizeof(struct audio_soc_softc),
543*2cfe870aSOleksandr Tymoshenko };
544*2cfe870aSOleksandr Tymoshenko 
545*2cfe870aSOleksandr Tymoshenko DRIVER_MODULE(audio_soc, simplebus, audio_soc_driver, pcm_devclass, NULL, NULL);
546*2cfe870aSOleksandr Tymoshenko MODULE_VERSION(audio_soc, 1);
547