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