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