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