xref: /freebsd-13-stable/sys/arm/allwinner/a10_hdmiaudio.c (revision 3bc80996974a61a4223eae4c1ccd47b6ee32a48a)
1 /*-
2  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
18  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
20  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 /*
27  * Allwinner A10/A20 HDMI Audio
28  */
29 
30 #include <sys/cdefs.h>
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/rman.h>
35 #include <sys/condvar.h>
36 #include <sys/kernel.h>
37 #include <sys/module.h>
38 
39 #include <dev/sound/pcm/sound.h>
40 #include <dev/sound/chip.h>
41 
42 #include <dev/ofw/ofw_bus.h>
43 #include <dev/ofw/ofw_bus_subr.h>
44 
45 #include "sunxi_dma_if.h"
46 #include "mixer_if.h"
47 
48 #define	DRQTYPE_HDMIAUDIO	24
49 #define	DRQTYPE_SDRAM		1
50 
51 #define	DMA_WIDTH		32
52 #define	DMA_BURST_LEN		8
53 #define	DDMA_BLKSIZE		32
54 #define	DDMA_WAIT_CYC		8
55 
56 #define	DMABUF_MIN		4096
57 #define	DMABUF_DEFAULT		65536
58 #define	DMABUF_MAX		131072
59 
60 #define	HDMI_SAMPLERATE		48000
61 
62 #define	TX_FIFO			0x01c16400
63 
64 static uint32_t a10hdmiaudio_fmt[] = {
65 	SND_FORMAT(AFMT_S16_LE, 2, 0),
66 	0
67 };
68 
69 static struct pcmchan_caps a10hdmiaudio_pcaps = {
70     HDMI_SAMPLERATE, HDMI_SAMPLERATE, a10hdmiaudio_fmt, 0
71 };
72 
73 struct a10hdmiaudio_info;
74 
75 struct a10hdmiaudio_chinfo {
76 	struct snd_dbuf		*buffer;
77 	struct pcm_channel	*channel;
78 	struct a10hdmiaudio_info	*parent;
79 	bus_dmamap_t		dmamap;
80 	void			*dmaaddr;
81 	bus_addr_t		physaddr;
82 	device_t		dmac;
83 	void			*dmachan;
84 
85 	int			run;
86 	uint32_t		pos;
87 	uint32_t		blocksize;
88 };
89 
90 struct a10hdmiaudio_info {
91 	device_t		dev;
92 	struct mtx		*lock;
93 	bus_dma_tag_t		dmat;
94 	unsigned		dmasize;
95 
96 	struct a10hdmiaudio_chinfo	play;
97 };
98 
99 /*
100  * Mixer interface
101  */
102 
103 static int
a10hdmiaudio_mixer_init(struct snd_mixer * m)104 a10hdmiaudio_mixer_init(struct snd_mixer *m)
105 {
106 	mix_setdevs(m, SOUND_MASK_PCM);
107 
108 	return (0);
109 }
110 
111 static int
a10hdmiaudio_mixer_set(struct snd_mixer * m,unsigned dev,unsigned left,unsigned right)112 a10hdmiaudio_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left,
113     unsigned right)
114 {
115 	return (-1);
116 }
117 
118 static kobj_method_t a10hdmiaudio_mixer_methods[] = {
119 	KOBJMETHOD(mixer_init,		a10hdmiaudio_mixer_init),
120 	KOBJMETHOD(mixer_set,		a10hdmiaudio_mixer_set),
121 	KOBJMETHOD_END
122 };
123 MIXER_DECLARE(a10hdmiaudio_mixer);
124 
125 /*
126  * Channel interface
127  */
128 
129 static void
a10hdmiaudio_dmamap_cb(void * arg,bus_dma_segment_t * segs,int nseg,int error)130 a10hdmiaudio_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
131 {
132 	struct a10hdmiaudio_chinfo *ch = arg;
133 
134 	if (error != 0)
135 		return;
136 
137 	ch->physaddr = segs[0].ds_addr;
138 }
139 
140 static void
a10hdmiaudio_transfer(struct a10hdmiaudio_chinfo * ch)141 a10hdmiaudio_transfer(struct a10hdmiaudio_chinfo *ch)
142 {
143 	int error;
144 
145 	error = SUNXI_DMA_TRANSFER(ch->dmac, ch->dmachan,
146 	    ch->physaddr + ch->pos, TX_FIFO, ch->blocksize);
147 	if (error) {
148 		ch->run = 0;
149 		device_printf(ch->parent->dev, "DMA transfer failed: %d\n",
150 		    error);
151 	}
152 }
153 
154 static void
a10hdmiaudio_dmaconfig(struct a10hdmiaudio_chinfo * ch)155 a10hdmiaudio_dmaconfig(struct a10hdmiaudio_chinfo *ch)
156 {
157 	struct sunxi_dma_config conf;
158 
159 	memset(&conf, 0, sizeof(conf));
160 	conf.src_width = conf.dst_width = DMA_WIDTH;
161 	conf.src_burst_len = conf.dst_burst_len = DMA_BURST_LEN;
162 	conf.src_blksize = conf.dst_blksize = DDMA_BLKSIZE;
163 	conf.src_wait_cyc = conf.dst_wait_cyc = DDMA_WAIT_CYC;
164 	conf.src_drqtype = DRQTYPE_SDRAM;
165 	conf.dst_drqtype = DRQTYPE_HDMIAUDIO;
166 	conf.dst_noincr = true;
167 
168 	SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf);
169 }
170 
171 static void
a10hdmiaudio_dmaintr(void * priv)172 a10hdmiaudio_dmaintr(void *priv)
173 {
174 	struct a10hdmiaudio_chinfo *ch = priv;
175 	unsigned bufsize;
176 
177 	bufsize = sndbuf_getsize(ch->buffer);
178 
179 	ch->pos += ch->blocksize;
180 	if (ch->pos >= bufsize)
181 		ch->pos -= bufsize;
182 
183 	if (ch->run) {
184 		chn_intr(ch->channel);
185 		a10hdmiaudio_transfer(ch);
186 	}
187 }
188 
189 static void
a10hdmiaudio_start(struct a10hdmiaudio_chinfo * ch)190 a10hdmiaudio_start(struct a10hdmiaudio_chinfo *ch)
191 {
192 	ch->pos = 0;
193 
194 	/* Configure DMA channel */
195 	a10hdmiaudio_dmaconfig(ch);
196 
197 	/* Start DMA transfer */
198 	a10hdmiaudio_transfer(ch);
199 }
200 
201 static void
a10hdmiaudio_stop(struct a10hdmiaudio_chinfo * ch)202 a10hdmiaudio_stop(struct a10hdmiaudio_chinfo *ch)
203 {
204 	/* Disable DMA channel */
205 	SUNXI_DMA_HALT(ch->dmac, ch->dmachan);
206 }
207 
208 static void *
a10hdmiaudio_chan_init(kobj_t obj,void * devinfo,struct snd_dbuf * b,struct pcm_channel * c,int dir)209 a10hdmiaudio_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
210     struct pcm_channel *c, int dir)
211 {
212 	struct a10hdmiaudio_info *sc = devinfo;
213 	struct a10hdmiaudio_chinfo *ch = &sc->play;
214 	int error;
215 
216 	ch->parent = sc;
217 	ch->channel = c;
218 	ch->buffer = b;
219 
220 	ch->dmac = devclass_get_device(devclass_find("a10dmac"), 0);
221 	if (ch->dmac == NULL) {
222 		device_printf(sc->dev, "cannot find DMA controller\n");
223 		return (NULL);
224 	}
225 	ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, true, a10hdmiaudio_dmaintr, ch);
226 	if (ch->dmachan == NULL) {
227 		device_printf(sc->dev, "cannot allocate DMA channel\n");
228 		return (NULL);
229 	}
230 
231 	error = bus_dmamem_alloc(sc->dmat, &ch->dmaaddr,
232 	    BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &ch->dmamap);
233 	if (error != 0) {
234 		device_printf(sc->dev, "cannot allocate channel buffer\n");
235 		return (NULL);
236 	}
237 	error = bus_dmamap_load(sc->dmat, ch->dmamap, ch->dmaaddr,
238 	    sc->dmasize, a10hdmiaudio_dmamap_cb, ch, BUS_DMA_NOWAIT);
239 	if (error != 0) {
240 		device_printf(sc->dev, "cannot load DMA map\n");
241 		return (NULL);
242 	}
243 	memset(ch->dmaaddr, 0, sc->dmasize);
244 
245 	if (sndbuf_setup(ch->buffer, ch->dmaaddr, sc->dmasize) != 0) {
246 		device_printf(sc->dev, "cannot setup sndbuf\n");
247 		return (NULL);
248 	}
249 
250 	return (ch);
251 }
252 
253 static int
a10hdmiaudio_chan_free(kobj_t obj,void * data)254 a10hdmiaudio_chan_free(kobj_t obj, void *data)
255 {
256 	struct a10hdmiaudio_chinfo *ch = data;
257 	struct a10hdmiaudio_info *sc = ch->parent;
258 
259 	SUNXI_DMA_FREE(ch->dmac, ch->dmachan);
260 	bus_dmamap_unload(sc->dmat, ch->dmamap);
261 	bus_dmamem_free(sc->dmat, ch->dmaaddr, ch->dmamap);
262 
263 	return (0);
264 }
265 
266 static int
a10hdmiaudio_chan_setformat(kobj_t obj,void * data,uint32_t format)267 a10hdmiaudio_chan_setformat(kobj_t obj, void *data, uint32_t format)
268 {
269 	return (0);
270 }
271 
272 static uint32_t
a10hdmiaudio_chan_setspeed(kobj_t obj,void * data,uint32_t speed)273 a10hdmiaudio_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
274 {
275 	return (HDMI_SAMPLERATE);
276 }
277 
278 static uint32_t
a10hdmiaudio_chan_setblocksize(kobj_t obj,void * data,uint32_t blocksize)279 a10hdmiaudio_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
280 {
281 	struct a10hdmiaudio_chinfo *ch = data;
282 
283 	ch->blocksize = blocksize & ~3;
284 
285 	return (ch->blocksize);
286 }
287 
288 static int
a10hdmiaudio_chan_trigger(kobj_t obj,void * data,int go)289 a10hdmiaudio_chan_trigger(kobj_t obj, void *data, int go)
290 {
291 	struct a10hdmiaudio_chinfo *ch = data;
292 	struct a10hdmiaudio_info *sc = ch->parent;
293 
294 	if (!PCMTRIG_COMMON(go))
295 		return (0);
296 
297 	snd_mtxlock(sc->lock);
298 	switch (go) {
299 	case PCMTRIG_START:
300 		ch->run = 1;
301 		a10hdmiaudio_start(ch);
302 		break;
303 	case PCMTRIG_STOP:
304 	case PCMTRIG_ABORT:
305 		ch->run = 0;
306 		a10hdmiaudio_stop(ch);
307 		break;
308 	default:
309 		break;
310 	}
311 	snd_mtxunlock(sc->lock);
312 
313 	return (0);
314 }
315 
316 static uint32_t
a10hdmiaudio_chan_getptr(kobj_t obj,void * data)317 a10hdmiaudio_chan_getptr(kobj_t obj, void *data)
318 {
319 	struct a10hdmiaudio_chinfo *ch = data;
320 
321 	return (ch->pos);
322 }
323 
324 static struct pcmchan_caps *
a10hdmiaudio_chan_getcaps(kobj_t obj,void * data)325 a10hdmiaudio_chan_getcaps(kobj_t obj, void *data)
326 {
327 	return (&a10hdmiaudio_pcaps);
328 }
329 
330 static kobj_method_t a10hdmiaudio_chan_methods[] = {
331 	KOBJMETHOD(channel_init,		a10hdmiaudio_chan_init),
332 	KOBJMETHOD(channel_free,		a10hdmiaudio_chan_free),
333 	KOBJMETHOD(channel_setformat,		a10hdmiaudio_chan_setformat),
334 	KOBJMETHOD(channel_setspeed,		a10hdmiaudio_chan_setspeed),
335 	KOBJMETHOD(channel_setblocksize,	a10hdmiaudio_chan_setblocksize),
336 	KOBJMETHOD(channel_trigger,		a10hdmiaudio_chan_trigger),
337 	KOBJMETHOD(channel_getptr,		a10hdmiaudio_chan_getptr),
338 	KOBJMETHOD(channel_getcaps,		a10hdmiaudio_chan_getcaps),
339 	KOBJMETHOD_END
340 };
341 CHANNEL_DECLARE(a10hdmiaudio_chan);
342 
343 /*
344  * Device interface
345  */
346 
347 static int
a10hdmiaudio_probe(device_t dev)348 a10hdmiaudio_probe(device_t dev)
349 {
350 	if (!ofw_bus_status_okay(dev))
351 		return (ENXIO);
352 
353 	if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmiaudio"))
354 		return (ENXIO);
355 
356 	device_set_desc(dev, "Allwinner HDMI Audio");
357 	return (BUS_PROBE_DEFAULT);
358 }
359 
360 static int
a10hdmiaudio_attach(device_t dev)361 a10hdmiaudio_attach(device_t dev)
362 {
363 	struct a10hdmiaudio_info *sc;
364 	char status[SND_STATUSLEN];
365 	int error;
366 
367 	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
368 	sc->dev = dev;
369 	sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10hdmiaudio softc");
370 
371 	sc->dmasize = pcm_getbuffersize(dev, DMABUF_MIN,
372 	    DMABUF_DEFAULT, DMABUF_MAX);
373 	error = bus_dma_tag_create(
374 	    bus_get_dma_tag(dev),
375 	    4, sc->dmasize,		/* alignment, boundary */
376 	    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
377 	    BUS_SPACE_MAXADDR,		/* highaddr */
378 	    NULL, NULL,			/* filter, filterarg */
379 	    sc->dmasize, 1,		/* maxsize, nsegs */
380 	    sc->dmasize, 0,		/* maxsegsize, flags */
381 	    NULL, NULL,			/* lockfunc, lockarg */
382 	    &sc->dmat);
383 	if (error != 0) {
384 		device_printf(dev, "cannot create DMA tag\n");
385 		goto fail;
386 	}
387 
388 	if (mixer_init(dev, &a10hdmiaudio_mixer_class, sc)) {
389 		device_printf(dev, "mixer_init failed\n");
390 		goto fail;
391 	}
392 
393 	pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
394 	pcm_setflags(dev, pcm_getflags(dev) | SD_F_SOFTPCMVOL);
395 
396 	if (pcm_register(dev, sc, 1, 0)) {
397 		device_printf(dev, "pcm_register failed\n");
398 		goto fail;
399 	}
400 
401 	pcm_addchan(dev, PCMDIR_PLAY, &a10hdmiaudio_chan_class, sc);
402 
403 	snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev));
404 	pcm_setstatus(dev, status);
405 
406 	return (0);
407 
408 fail:
409 	snd_mtxfree(sc->lock);
410 	free(sc, M_DEVBUF);
411 
412 	return (error);
413 }
414 
415 static device_method_t a10hdmiaudio_pcm_methods[] = {
416 	/* Device interface */
417 	DEVMETHOD(device_probe,		a10hdmiaudio_probe),
418 	DEVMETHOD(device_attach,	a10hdmiaudio_attach),
419 
420 	DEVMETHOD_END
421 };
422 
423 static driver_t a10hdmiaudio_pcm_driver = {
424 	"pcm",
425 	a10hdmiaudio_pcm_methods,
426 	PCM_SOFTC_SIZE,
427 };
428 
429 DRIVER_MODULE(a10hdmiaudio, simplebus, a10hdmiaudio_pcm_driver, pcm_devclass, 0, 0);
430 MODULE_DEPEND(a10hdmiaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
431 MODULE_VERSION(a10hdmiaudio, 1);
432