1/* $NetBSD: bwai.c,v 1.3 2024/01/23 21:56:07 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: bwai.c,v 1.3 2024/01/23 21:56:07 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#include <sys/mutex.h> 38#include <sys/bitops.h> 39 40#include <dev/audio/audio_dai.h> 41 42#include <machine/wii.h> 43 44#include "mainbus.h" 45#include "avenc.h" 46#include "bwai.h" 47 48#define AI_CONTROL 0x00 49#define AI_CONTROL_RATE __BIT(6) 50#define AI_CONTROL_SCRESET __BIT(5) 51#define AI_CONTROL_AIINTVLD __BIT(4) 52#define AI_CONTROL_AIINT __BIT(3) 53#define AI_CONTROL_AIINTMSK __BIT(2) 54#define AI_CONTROL_AFR __BIT(1) 55#define AI_CONTROL_PSTAT __BIT(0) 56#define AI_AIIT 0x0c 57 58struct bwai_softc { 59 device_t sc_dev; 60 bus_space_tag_t sc_bst; 61 bus_space_handle_t sc_bsh; 62 int sc_irq; 63 64 struct audio_dai_device sc_dai; 65 66 void (*sc_intr)(void *); 67 void *sc_intrarg; 68 69 kmutex_t *sc_intr_lock; 70}; 71 72enum bwai_mixer_ctrl { 73 BWAI_OUTPUT_CLASS, 74 BWAI_INPUT_CLASS, 75 76 BWAI_OUTPUT_MASTER_VOLUME, 77 BWAI_INPUT_DAC_VOLUME, 78 79 BWAI_MIXER_CTRL_LAST 80}; 81 82#define RD4(sc, reg) \ 83 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 84#define WR4(sc, reg, val) \ 85 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 86 87static int 88bwai_intr(void *priv) 89{ 90 struct bwai_softc * const sc = priv; 91 uint32_t val; 92 93 val = RD4(sc, AI_CONTROL); 94 if ((val & AI_CONTROL_AIINT) != 0) { 95 WR4(sc, AI_CONTROL, val | AI_CONTROL_SCRESET); 96 97 mutex_enter(sc->sc_intr_lock); 98 if (sc->sc_intr) { 99 sc->sc_intr(sc->sc_intrarg); 100 } 101 mutex_exit(sc->sc_intr_lock); 102 } 103 104 return 1; 105} 106 107audio_dai_tag_t 108bwai_dsp_init(kmutex_t *intr_lock) 109{ 110 struct bwai_softc *sc; 111 device_t dev; 112 113 dev = device_find_by_driver_unit("bwai", 0); 114 if (dev == NULL) { 115 return NULL; 116 } 117 sc = device_private(dev); 118 119 sc->sc_intr_lock = intr_lock; 120 121 intr_establish_xname(sc->sc_irq, IST_LEVEL, IPL_AUDIO, bwai_intr, sc, 122 device_xname(dev)); 123 124 return &sc->sc_dai; 125} 126 127static int 128bwai_set_port(void *priv, mixer_ctrl_t *mc) 129{ 130 if (mc->dev != BWAI_OUTPUT_MASTER_VOLUME && 131 mc->dev != BWAI_INPUT_DAC_VOLUME) { 132 return ENXIO; 133 } 134 135 avenc_set_volume(mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT], 136 mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT]); 137 138 return 0; 139} 140 141static int 142bwai_get_port(void *priv, mixer_ctrl_t *mc) 143{ 144 if (mc->dev != BWAI_OUTPUT_MASTER_VOLUME && 145 mc->dev != BWAI_INPUT_DAC_VOLUME) { 146 return ENXIO; 147 } 148 149 avenc_get_volume(&mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT], 150 &mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT]); 151 152 return 0; 153} 154 155static int 156bwai_query_devinfo(void *priv, mixer_devinfo_t *di) 157{ 158 switch (di->index) { 159 case BWAI_OUTPUT_CLASS: 160 di->mixer_class = di->index; 161 strcpy(di->label.name, AudioCoutputs); 162 di->type = AUDIO_MIXER_CLASS; 163 di->next = di->prev = AUDIO_MIXER_LAST; 164 return 0; 165 166 case BWAI_INPUT_CLASS: 167 di->mixer_class = di->index; 168 strcpy(di->label.name, AudioCinputs); 169 di->type = AUDIO_MIXER_CLASS; 170 di->next = di->prev = AUDIO_MIXER_LAST; 171 return 0; 172 173 case BWAI_OUTPUT_MASTER_VOLUME: 174 di->mixer_class = BWAI_OUTPUT_CLASS; 175 strcpy(di->label.name, AudioNmaster); 176 di->un.v.delta = 1; 177 di->type = AUDIO_MIXER_VALUE; 178 di->next = di->prev = AUDIO_MIXER_LAST; 179 di->un.v.num_channels = 2; 180 strcpy(di->un.v.units.name, AudioNvolume); 181 return 0; 182 183 case BWAI_INPUT_DAC_VOLUME: 184 di->mixer_class = BWAI_INPUT_CLASS; 185 strcpy(di->label.name, AudioNdac); 186 di->un.v.delta = 1; 187 di->type = AUDIO_MIXER_VALUE; 188 di->next = di->prev = AUDIO_MIXER_LAST; 189 di->un.v.num_channels = 2; 190 strcpy(di->un.v.units.name, AudioNvolume); 191 return 0; 192 } 193 194 return ENXIO; 195} 196 197static int 198bwai_set_format(void *priv, int setmode, 199 const audio_params_t *play, const audio_params_t *rec, 200 audio_filter_reg_t *pfil, audio_filter_reg_t *rfil) 201{ 202 return 0; 203} 204 205static int 206bwai_trigger_output(void *priv, void *start, void *end, int blksize, 207 void (*intr)(void *), void *intrarg, const audio_params_t *params) 208{ 209 struct bwai_softc * const sc = priv; 210 uint32_t val; 211 212 sc->sc_intr = intr; 213 sc->sc_intrarg = intrarg; 214 215 val = RD4(sc, AI_CONTROL); 216 if ((val & AI_CONTROL_PSTAT) != 0) { 217 WR4(sc, AI_CONTROL, 0); 218 } 219 220 WR4(sc, AI_AIIT, blksize / 4); 221 222 val = AI_CONTROL_SCRESET | 223 AI_CONTROL_AIINT | 224 AI_CONTROL_AIINTMSK | 225 AI_CONTROL_AFR; 226 WR4(sc, AI_CONTROL, val); 227 228 val |= AI_CONTROL_PSTAT; 229 WR4(sc, AI_CONTROL, val); 230 231 return 0; 232} 233 234static int 235bwai_halt_output(void *priv) 236{ 237 struct bwai_softc * const sc = priv; 238 239 WR4(sc, AI_CONTROL, 0); 240 241 sc->sc_intr = NULL; 242 sc->sc_intrarg = NULL; 243 244 return 0; 245} 246 247static const struct audio_hw_if bwai_hw_if = { 248 .set_port = bwai_set_port, 249 .get_port = bwai_get_port, 250 .query_devinfo = bwai_query_devinfo, 251 .set_format = bwai_set_format, 252 .trigger_output = bwai_trigger_output, 253 .halt_output = bwai_halt_output, 254}; 255 256static int 257bwai_match(device_t parent, cfdata_t cf, void *aux) 258{ 259 struct mainbus_attach_args * const maa = aux; 260 261 return strcmp(maa->maa_name, "bwai") == 0; 262} 263 264static void 265bwai_attach(device_t parent, device_t self, void *aux) 266{ 267 struct bwai_softc * const sc = device_private(self); 268 struct mainbus_attach_args * const maa = aux; 269 270 sc->sc_dev = self; 271 sc->sc_bst = maa->maa_bst; 272 if (bus_space_map(sc->sc_bst, maa->maa_addr, AI_SIZE, 0, 273 &sc->sc_bsh) != 0) { 274 aprint_error(": couldn't map registers\n"); 275 return; 276 } 277 sc->sc_irq = maa->maa_irq; 278 279 aprint_naive("\n"); 280 aprint_normal(": Audio Interface\n"); 281 282 sc->sc_dai.dai_hw_if = &bwai_hw_if; 283 sc->sc_dai.dai_dev = self; 284 sc->sc_dai.dai_priv = sc; 285} 286 287CFATTACH_DECL_NEW(bwai, sizeof(struct bwai_softc), 288 bwai_match, bwai_attach, NULL, NULL); 289