1/* $NetBSD: bwdsp.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2024 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: bwdsp.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $");
31
32#include <sys/param.h>
33#include <sys/bus.h>
34#include <sys/cpu.h>
35#include <sys/device.h>
36#include <sys/kmem.h>
37
38#include <sys/audioio.h>
39#include <dev/audio/audio_if.h>
40#include <dev/audio/audio_dai.h>
41
42#include <machine/wiiu.h>
43#include <machine/pio.h>
44
45#include "mainbus.h"
46#include "bwai.h"
47
48#define	BWDSP_MAP_FLAGS		BUS_DMA_NOCACHE
49
50#define DSP_DMA_START_ADDR_H	0x30
51#define	DSP_DMA_START_ADDR_L	0x32
52#define DSP_DMA_CONTROL_LENGTH	0x36
53#define  DSP_DMA_CONTROL_LENGTH_CTRL	__BIT(15)
54#define	 DSP_DMA_CONTROL_LENGTH_NUM_CLS	__BITS(14,0)
55
56#define	DSP_DMA_ALIGN		32
57#define	DSP_DMA_MAX_BUFSIZE	(DSP_DMA_CONTROL_LENGTH_NUM_CLS * 32)
58
59extern struct powerpc_bus_dma_tag wii_mem2_bus_dma_tag;
60
61struct bwdsp_dma {
62	LIST_ENTRY(bwdsp_dma)	dma_list;
63	bus_dmamap_t		dma_map;
64	void			*dma_addr;
65	size_t			dma_size;
66	bus_dma_segment_t	dma_segs[1];
67	int			dma_nsegs;
68};
69
70struct bwdsp_softc {
71	device_t		sc_dev;
72	bus_space_tag_t		sc_bst;
73	bus_space_handle_t	sc_bsh;
74	bus_dma_tag_t		sc_dmat;
75
76	LIST_HEAD(, bwdsp_dma) sc_dmalist;
77
78	kmutex_t		sc_lock;
79	kmutex_t		sc_intr_lock;
80
81	struct audio_format	sc_format;
82
83	audio_dai_tag_t		sc_dai;
84
85	/* Register offsets */
86	uint32_t		sc_dma_start_addr_h;
87	uint32_t		sc_dma_start_addr_l;
88	uint32_t		sc_dma_control_length;
89};
90
91#define	WR2(sc, reg, val)		\
92	bus_space_write_2((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
93
94static int
95bwdsp_allocdma(struct bwdsp_softc *sc, size_t size,
96    size_t align, struct bwdsp_dma *dma)
97{
98	int error;
99
100	dma->dma_size = size;
101	error = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, align, 0,
102	    dma->dma_segs, 1, &dma->dma_nsegs, BUS_DMA_WAITOK);
103	if (error)
104		return error;
105
106	error = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs,
107	    dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | BWDSP_MAP_FLAGS);
108	if (error)
109		goto free;
110
111	error = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs,
112	    dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map);
113	if (error)
114		goto unmap;
115
116	error = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr,
117	    dma->dma_size, NULL, BUS_DMA_WAITOK);
118	if (error)
119		goto destroy;
120
121	return 0;
122
123destroy:
124	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
125unmap:
126	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
127free:
128	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
129
130	return error;
131}
132
133static void
134bwdsp_freedma(struct bwdsp_softc *sc, struct bwdsp_dma *dma)
135{
136	bus_dmamap_unload(sc->sc_dmat, dma->dma_map);
137	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
138	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
139	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
140}
141
142static int
143bwdsp_query_format(void *priv, audio_format_query_t *afp)
144{
145	struct bwdsp_softc * const sc = priv;
146
147	return audio_query_format(&sc->sc_format, 1, afp);
148}
149
150static int
151bwdsp_set_format(void *priv, int setmode,
152    const audio_params_t *play, const audio_params_t *rec,
153    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
154{
155	struct bwdsp_softc * const sc = priv;
156
157	return audio_dai_mi_set_format(sc->sc_dai, setmode, play, rec,
158	    pfil, rfil);
159}
160
161static int
162bwdsp_set_port(void *priv, mixer_ctrl_t *mc)
163{
164	struct bwdsp_softc * const sc = priv;
165
166	return audio_dai_set_port(sc->sc_dai, mc);
167}
168
169static int
170bwdsp_get_port(void *priv, mixer_ctrl_t *mc)
171{
172	struct bwdsp_softc * const sc = priv;
173
174	return audio_dai_get_port(sc->sc_dai, mc);
175}
176
177static int
178bwdsp_query_devinfo(void *priv, mixer_devinfo_t *di)
179{
180	struct bwdsp_softc * const sc = priv;
181
182	return audio_dai_query_devinfo(sc->sc_dai, di);
183}
184
185static void *
186bwdsp_allocm(void *priv, int dir, size_t size)
187{
188	struct bwdsp_softc * const sc = priv;
189	struct bwdsp_dma *dma;
190	int error;
191
192	dma = kmem_alloc(sizeof(*dma), KM_SLEEP);
193
194	error = bwdsp_allocdma(sc, size, DSP_DMA_ALIGN, dma);
195	if (error) {
196		kmem_free(dma, sizeof(*dma));
197		device_printf(sc->sc_dev, "couldn't allocate DMA memory (%d)\n",
198		    error);
199		return NULL;
200	}
201
202	LIST_INSERT_HEAD(&sc->sc_dmalist, dma, dma_list);
203
204	return dma->dma_addr;
205}
206
207static void
208bwdsp_freem(void *priv, void *addr, size_t size)
209{
210	struct bwdsp_softc * const sc = priv;
211	struct bwdsp_dma *dma;
212
213	LIST_FOREACH(dma, &sc->sc_dmalist, dma_list)
214		if (dma->dma_addr == addr) {
215			bwdsp_freedma(sc, dma);
216			LIST_REMOVE(dma, dma_list);
217			kmem_free(dma, sizeof(*dma));
218			break;
219		}
220}
221
222static int
223bwdsp_getdev(void *priv, struct audio_device *adev)
224{
225	if (wiiu_plat) {
226		snprintf(adev->name, sizeof(adev->name), "Latte DSP");
227	} else {
228		snprintf(adev->name, sizeof(adev->name), "Hollywood DSP");
229	}
230	snprintf(adev->version, sizeof(adev->version), "");
231	snprintf(adev->config, sizeof(adev->config), "bwdsp");
232
233	return 0;
234}
235
236static int
237bwdsp_get_props(void *priv)
238{
239	return AUDIO_PROP_PLAYBACK;
240}
241
242static int
243bwdsp_round_blocksize(void *priv, int bs, int mode,
244    const audio_params_t *params)
245{
246	bs = roundup(bs, DSP_DMA_ALIGN);
247	if (bs > DSP_DMA_MAX_BUFSIZE) {
248		bs = DSP_DMA_MAX_BUFSIZE;
249	}
250	return bs;
251}
252
253static size_t
254bwdsp_round_buffersize(void *priv, int dir, size_t bufsize)
255{
256	if (bufsize > DSP_DMA_MAX_BUFSIZE) {
257		bufsize = DSP_DMA_MAX_BUFSIZE;
258	}
259	return bufsize;
260}
261
262static void
263bwdsp_transfer(struct bwdsp_softc *sc, uint32_t phys_addr, size_t bufsize)
264{
265	if (bufsize != 0) {
266		WR2(sc, sc->sc_dma_start_addr_h, phys_addr >> 16);
267		WR2(sc, sc->sc_dma_start_addr_l, phys_addr & 0xffff);
268		WR2(sc, sc->sc_dma_control_length,
269		    DSP_DMA_CONTROL_LENGTH_CTRL | (bufsize / 32));
270	} else {
271		WR2(sc, sc->sc_dma_control_length, 0);
272	}
273}
274
275static int
276bwdsp_trigger_output(void *priv, void *start, void *end, int blksize,
277    void (*intr)(void *), void *intrarg, const audio_params_t *params)
278{
279	struct bwdsp_softc * const sc = priv;
280	struct bwdsp_dma *dma;
281	bus_addr_t pstart;
282	bus_size_t psize;
283	int error;
284
285	pstart = 0;
286	psize = (uintptr_t)end - (uintptr_t)start;
287
288	LIST_FOREACH(dma, &sc->sc_dmalist, dma_list)
289		if (dma->dma_addr == start) {
290			pstart = dma->dma_map->dm_segs[0].ds_addr;
291			break;
292		}
293	if (pstart == 0) {
294		device_printf(sc->sc_dev, "bad addr %p\n", start);
295		return EINVAL;
296	}
297
298	error = audio_dai_trigger(sc->sc_dai, start, end, blksize,
299	    intr, intrarg, params, AUMODE_PLAY);
300	if (error != 0) {
301		return error;
302	}
303
304	/* Start DMA transfer */
305	bwdsp_transfer(sc, pstart, psize);
306
307	return 0;
308}
309
310static int
311bwdsp_halt_output(void *priv)
312{
313	struct bwdsp_softc * const sc = priv;
314
315	/* Stop DMA transfer */
316	bwdsp_transfer(sc, 0, 0);
317
318	return audio_dai_halt(sc->sc_dai, AUMODE_PLAY);
319}
320
321static void
322bwdsp_get_locks(void *priv, kmutex_t **intr, kmutex_t **thread)
323{
324	struct bwdsp_softc * const sc = priv;
325
326	*intr = &sc->sc_intr_lock;
327	*thread = &sc->sc_lock;
328}
329
330static const struct audio_hw_if bwdsp_hw_if = {
331	.query_format = bwdsp_query_format,
332	.set_format = bwdsp_set_format,
333	.allocm = bwdsp_allocm,
334	.freem = bwdsp_freem,
335	.getdev = bwdsp_getdev,
336	.set_port = bwdsp_set_port,
337	.get_port = bwdsp_get_port,
338	.query_devinfo = bwdsp_query_devinfo,
339	.get_props = bwdsp_get_props,
340	.round_blocksize = bwdsp_round_blocksize,
341	.round_buffersize = bwdsp_round_buffersize,
342	.trigger_output = bwdsp_trigger_output,
343	.halt_output = bwdsp_halt_output,
344	.get_locks = bwdsp_get_locks,
345};
346
347static void
348bwdsp_late_attach(device_t dev)
349{
350	struct bwdsp_softc * const sc = device_private(dev);
351
352	sc->sc_dai = bwai_dsp_init(&sc->sc_intr_lock);
353	if (sc->sc_dai == NULL) {
354		aprint_error_dev(dev, "can't find bwai device\n");
355		return;
356	}
357
358	audio_attach_mi(&bwdsp_hw_if, sc, dev);
359}
360
361static int
362bwdsp_match(device_t parent, cfdata_t cf, void *aux)
363{
364	struct mainbus_attach_args * const maa = aux;
365
366	return strcmp(maa->maa_name, "bwdsp") == 0;
367}
368
369static void
370bwdsp_attach(device_t parent, device_t self, void *aux)
371{
372	struct bwdsp_softc * const sc = device_private(self);
373	struct mainbus_attach_args * const maa = aux;
374	bus_addr_t addr = maa->maa_addr;
375	bus_size_t size = 0x200;
376	const uint32_t dma_reg_off = wiiu_native ? 0x10 : 0;
377
378	sc->sc_dev = self;
379	sc->sc_bst = maa->maa_bst;
380	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
381		aprint_error(": couldn't map registers\n");
382		return;
383	}
384	sc->sc_dmat = &wii_mem2_bus_dma_tag;
385	LIST_INIT(&sc->sc_dmalist);
386	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
387	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
388
389	sc->sc_dma_start_addr_h = DSP_DMA_START_ADDR_H + dma_reg_off;
390	sc->sc_dma_start_addr_l = DSP_DMA_START_ADDR_L + dma_reg_off;
391	sc->sc_dma_control_length = DSP_DMA_CONTROL_LENGTH + dma_reg_off;
392
393	aprint_naive("\n");
394	aprint_normal(": DSP\n");
395
396	out32(HW_RESETS, in32(HW_RESETS) | RSTB_DSP);
397
398	sc->sc_format.mode = AUMODE_PLAY;
399	sc->sc_format.encoding = AUDIO_ENCODING_SLINEAR_BE;
400	sc->sc_format.validbits = 16;
401	sc->sc_format.precision = 16;
402	sc->sc_format.channels = 2;
403	sc->sc_format.channel_mask = AUFMT_STEREO;
404	sc->sc_format.frequency_type = 1;
405	sc->sc_format.frequency[0] = 48000;
406
407	config_defer(self, bwdsp_late_attach);
408}
409
410CFATTACH_DECL_NEW(bwdsp, sizeof(struct bwdsp_softc),
411    bwdsp_match, bwdsp_attach, NULL, NULL);
412