1/*-
2 * Copyright 2008 by Marco Trillo. All rights reserved.
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 *	Apple Onboard Audio (AOA).
30 */
31
32#include <sys/cdefs.h>
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/kernel.h>
37#include <sys/bus.h>
38#include <sys/malloc.h>
39#include <sys/lock.h>
40#include <sys/mutex.h>
41#include <machine/dbdma.h>
42#include <machine/resource.h>
43#include <machine/bus.h>
44#include <sys/rman.h>
45#include <dev/ofw/ofw_bus.h>
46
47#ifdef HAVE_KERNEL_OPTION_HEADERS
48#include "opt_snd.h"
49#endif
50
51#include <dev/sound/pcm/sound.h>
52#include <dev/sound/macio/aoa.h>
53
54#include "mixer_if.h"
55
56struct aoa_dma {
57	struct mtx 		 mutex;
58	struct resource 	*reg; 		/* DBDMA registers */
59	dbdma_channel_t 	*channel; 	/* DBDMA channel */
60	bus_dma_tag_t 		 tag; 		/* bus_dma tag */
61	struct pcm_channel 	*pcm;		/* PCM channel */
62	struct snd_dbuf		*buf; 		/* PCM buffer */
63	u_int 			 slots; 	/* # of slots */
64	u_int 			 slot;		/* current slot */
65	u_int 			 bufsz; 	/* buffer size */
66	u_int 			 blksz; 	/* block size */
67	int 			 running;
68};
69
70static void
71aoa_dma_set_program(struct aoa_dma *dma)
72{
73	u_int32_t 		 addr;
74	int 			 i;
75
76	addr = (u_int32_t) sndbuf_getbufaddr(dma->buf);
77	KASSERT(dma->bufsz == sndbuf_getsize(dma->buf), ("bad size"));
78
79	dma->slots = dma->bufsz / dma->blksz;
80
81	for (i = 0; i < dma->slots; ++i) {
82		dbdma_insert_command(dma->channel,
83		    i, /* slot */
84		    DBDMA_OUTPUT_MORE, /* command */
85		    0, /* stream */
86		    addr, /* data */
87		    dma->blksz, /* count */
88		    DBDMA_ALWAYS, /* interrupt */
89		    DBDMA_COND_TRUE, /* branch */
90		    DBDMA_NEVER, /* wait */
91		    dma->slots + 1 /* branch_slot */
92		);
93
94		addr += dma->blksz;
95	}
96
97	/* Branch back to beginning. */
98	dbdma_insert_branch(dma->channel, dma->slots, 0);
99
100	/* STOP command to branch when S0 is asserted. */
101	dbdma_insert_stop(dma->channel, dma->slots + 1);
102
103	/* Set S0 as the condition to branch to STOP. */
104	dbdma_set_branch_selector(dma->channel, 1 << 0, 1 << 0);
105	dbdma_set_device_status(dma->channel, 1 << 0, 0);
106
107	dbdma_sync_commands(dma->channel, BUS_DMASYNC_PREWRITE);
108}
109
110#define AOA_BUFFER_SIZE		65536
111
112static struct aoa_dma *
113aoa_dma_create(struct aoa_softc *sc)
114{
115	struct aoa_dma *dma;
116	bus_dma_tag_t 	tag;
117	int 		err;
118	device_t	self;
119
120	self = sc->sc_dev;
121	err = bus_dma_tag_create(bus_get_dma_tag(self),
122	    4, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
123	    AOA_BUFFER_SIZE, 1, AOA_BUFFER_SIZE, 0, NULL, NULL, &tag);
124	if (err != 0)
125		return (NULL);
126
127	dma = malloc(sizeof(*dma), M_DEVBUF, M_WAITOK | M_ZERO);
128	dma->tag = tag;
129	dma->bufsz = AOA_BUFFER_SIZE;
130	dma->blksz = PAGE_SIZE; /* initial blocksize */
131
132	mtx_init(&dma->mutex, "AOA", NULL, MTX_DEF);
133
134	sc->sc_intrp = dma;
135
136	return (dma);
137}
138
139static void
140aoa_dma_delete(struct aoa_dma *dma)
141{
142	bus_dma_tag_destroy(dma->tag);
143	mtx_destroy(&dma->mutex);
144	free(dma, M_DEVBUF);
145}
146
147static u_int32_t
148aoa_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksz)
149{
150	struct aoa_dma 		*dma = data;
151	int 			 err, lz;
152
153	DPRINTF(("aoa_chan_setblocksize: blocksz = %u, dma->blksz = %u\n",
154		blocksz, dma->blksz));
155	KASSERT(!dma->running, ("dma is running"));
156	KASSERT(blocksz > 0, ("bad blocksz"));
157
158	/* Round blocksz down to a power of two... */
159	__asm volatile ("cntlzw %0,%1" : "=r"(lz) : "r"(blocksz));
160	blocksz = 1 << (31 - lz);
161	DPRINTF(("blocksz = %u\n", blocksz));
162
163	/* ...but no more than the buffer. */
164	if (blocksz > dma->bufsz)
165		blocksz = dma->bufsz;
166
167	err = sndbuf_resize(dma->buf, dma->bufsz / blocksz, blocksz);
168	if (err != 0) {
169		DPRINTF(("sndbuf_resize returned %d\n", err));
170		return (0);
171	}
172
173	if (blocksz == dma->blksz)
174		return (dma->blksz);
175
176	/* One slot per block plus branch to 0 plus STOP. */
177	err = dbdma_resize_channel(dma->channel, 2 + dma->bufsz / blocksz);
178	if (err != 0) {
179		DPRINTF(("dbdma_resize_channel returned %d\n", err));
180		return (0);
181	}
182
183	/* Set the new blocksize. */
184	dma->blksz = blocksz;
185	aoa_dma_set_program(dma);
186
187	return (dma->blksz);
188}
189
190static int
191aoa_chan_setformat(kobj_t obj, void *data, u_int32_t format)
192{
193	DPRINTF(("aoa_chan_setformat: format = %u\n", format));
194
195	if (format != SND_FORMAT(AFMT_S16_BE, 2, 0))
196		return (EINVAL);
197
198	return (0);
199}
200
201static u_int32_t
202aoa_chan_setspeed(kobj_t obj, void *data, u_int32_t speed)
203{
204	DPRINTF(("aoa_chan_setspeed: speed = %u\n", speed));
205
206	return (44100);
207}
208
209static u_int32_t
210aoa_chan_getptr(kobj_t obj, void *data)
211{
212	struct aoa_dma 	 *dma = data;
213
214	if (!dma->running)
215		return (0);
216
217	return (dma->slot * dma->blksz);
218}
219
220static void *
221aoa_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
222	struct pcm_channel *c, int dir)
223{
224	struct aoa_softc 	*sc = devinfo;
225	struct aoa_dma		*dma;
226	int 	 		 max_slots, err;
227
228	KASSERT(dir == PCMDIR_PLAY, ("bad dir"));
229
230	dma = aoa_dma_create(sc);
231	if (!dma)
232		return (NULL);
233	dma->pcm = c;
234	dma->buf = b;
235	dma->reg = sc->sc_odma;
236
237	/* One slot per block, plus branch to 0 plus STOP. */
238	max_slots = 2 + dma->bufsz / dma->blksz;
239	err = dbdma_allocate_channel(dma->reg, 0, bus_get_dma_tag(sc->sc_dev),
240	    max_slots, &dma->channel );
241	if (err != 0) {
242		aoa_dma_delete(dma);
243		return (NULL);
244	}
245
246	if (sndbuf_alloc(dma->buf, dma->tag, 0, dma->bufsz) != 0) {
247		dbdma_free_channel(dma->channel);
248		aoa_dma_delete(dma);
249		return (NULL);
250	}
251
252	aoa_dma_set_program(dma);
253
254	return (dma);
255}
256
257static int
258aoa_chan_trigger(kobj_t obj, void *data, int go)
259{
260	struct aoa_dma 	*dma = data;
261	int 		 i;
262
263	switch (go) {
264	case PCMTRIG_START:
265
266		/* Start the DMA. */
267		dma->running = 1;
268
269		dma->slot = 0;
270		dbdma_set_current_cmd(dma->channel, dma->slot);
271
272		dbdma_run(dma->channel);
273
274		return (0);
275
276	case PCMTRIG_STOP:
277	case PCMTRIG_ABORT:
278
279		mtx_lock(&dma->mutex);
280
281		dma->running = 0;
282
283		/* Make it branch to the STOP command. */
284		dbdma_set_device_status(dma->channel, 1 << 0, 1 << 0);
285
286		/* XXX should wait for DBDMA_ACTIVE to clear. */
287		DELAY(40000);
288
289		/* Reset the DMA. */
290		dbdma_stop(dma->channel);
291		dbdma_set_device_status(dma->channel, 1 << 0, 0);
292
293		for (i = 0; i < dma->slots; ++i)
294			dbdma_clear_cmd_status(dma->channel, i);
295
296		mtx_unlock(&dma->mutex);
297
298		return (0);
299	}
300
301	return (0);
302}
303
304static int
305aoa_chan_free(kobj_t obj, void *data)
306{
307	struct aoa_dma 	*dma = data;
308
309	sndbuf_free(dma->buf);
310	dbdma_free_channel(dma->channel);
311	aoa_dma_delete(dma);
312
313	return (0);
314}
315
316void
317aoa_interrupt(void *xsc)
318{
319	struct aoa_softc	*sc = xsc;
320	struct aoa_dma		*dma;
321
322	if (!(dma = sc->sc_intrp) || !dma->running)
323		return;
324
325	mtx_lock(&dma->mutex);
326
327	while (dbdma_get_cmd_status(dma->channel, dma->slot)) {
328
329		dbdma_clear_cmd_status(dma->channel, dma->slot);
330		dma->slot = (dma->slot + 1) % dma->slots;
331
332		mtx_unlock(&dma->mutex);
333		chn_intr(dma->pcm);
334		mtx_lock(&dma->mutex);
335	}
336
337	mtx_unlock(&dma->mutex);
338}
339
340static u_int32_t sc_fmt[] = {
341	SND_FORMAT(AFMT_S16_BE, 2, 0),
342	0
343};
344static struct pcmchan_caps aoa_caps = {44100, 44100, sc_fmt, 0};
345
346static struct pcmchan_caps *
347aoa_chan_getcaps(kobj_t obj, void *data)
348{
349	return (&aoa_caps);
350}
351
352static kobj_method_t aoa_chan_methods[] = {
353	KOBJMETHOD(channel_init, 	aoa_chan_init),
354	KOBJMETHOD(channel_free, 	aoa_chan_free),
355	KOBJMETHOD(channel_setformat, 	aoa_chan_setformat),
356	KOBJMETHOD(channel_setspeed, 	aoa_chan_setspeed),
357	KOBJMETHOD(channel_setblocksize,aoa_chan_setblocksize),
358	KOBJMETHOD(channel_trigger,	aoa_chan_trigger),
359	KOBJMETHOD(channel_getptr,	aoa_chan_getptr),
360	KOBJMETHOD(channel_getcaps,	aoa_chan_getcaps),
361	KOBJMETHOD_END
362};
363CHANNEL_DECLARE(aoa_chan);
364
365int
366aoa_attach(void *xsc)
367{
368	char status[SND_STATUSLEN];
369	struct aoa_softc *sc;
370	device_t self;
371	int err;
372
373	sc = xsc;
374	self = sc->sc_dev;
375
376	if (pcm_register(self, sc, 1, 0))
377		return (ENXIO);
378
379	err = pcm_getbuffersize(self, AOA_BUFFER_SIZE, AOA_BUFFER_SIZE,
380	    AOA_BUFFER_SIZE);
381	DPRINTF(("pcm_getbuffersize returned %d\n", err));
382
383	pcm_addchan(self, PCMDIR_PLAY, &aoa_chan_class, sc);
384
385	snprintf(status, sizeof(status), "at %s", ofw_bus_get_name(self));
386	pcm_setstatus(self, status);
387
388	return (0);
389}
390
391