1c15c9315SChristos Margiolis /*- 2c15c9315SChristos Margiolis * SPDX-License-Identifier: BSD-2-Clause 3c15c9315SChristos Margiolis * 4c15c9315SChristos Margiolis * Copyright (c) 2024 The FreeBSD Foundation 5c15c9315SChristos Margiolis * 6c15c9315SChristos Margiolis * This software was developed by Christos Margiolis <christos@FreeBSD.org> 7c15c9315SChristos Margiolis * under sponsorship from the FreeBSD Foundation. 8c15c9315SChristos Margiolis * 9c15c9315SChristos Margiolis * Redistribution and use in source and binary forms, with or without 10c15c9315SChristos Margiolis * modification, are permitted provided that the following conditions 11c15c9315SChristos Margiolis * are met: 12c15c9315SChristos Margiolis * 1. Redistributions of source code must retain the above copyright 13c15c9315SChristos Margiolis * notice, this list of conditions and the following disclaimer. 14c15c9315SChristos Margiolis * 2. Redistributions in binary form must reproduce the above copyright 15c15c9315SChristos Margiolis * notice, this list of conditions and the following disclaimer in the 16c15c9315SChristos Margiolis * documentation and/or other materials provided with the distribution. 17c15c9315SChristos Margiolis * 18c15c9315SChristos Margiolis * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19c15c9315SChristos Margiolis * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20c15c9315SChristos Margiolis * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21c15c9315SChristos Margiolis * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22c15c9315SChristos Margiolis * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23c15c9315SChristos Margiolis * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24c15c9315SChristos Margiolis * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25c15c9315SChristos Margiolis * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26c15c9315SChristos Margiolis * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27c15c9315SChristos Margiolis * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28c15c9315SChristos Margiolis * SUCH DAMAGE. 29c15c9315SChristos Margiolis */ 30c15c9315SChristos Margiolis 31c15c9315SChristos Margiolis #include <sys/cdefs.h> 32c15c9315SChristos Margiolis 33c15c9315SChristos Margiolis #include <sys/param.h> 34c15c9315SChristos Margiolis #include <sys/systm.h> 35c15c9315SChristos Margiolis #include <sys/kernel.h> 36c15c9315SChristos Margiolis #include <sys/bus.h> 37c15c9315SChristos Margiolis 38c15c9315SChristos Margiolis #ifdef HAVE_KERNEL_OPTION_HEADERS 39c15c9315SChristos Margiolis #include "opt_snd.h" 40c15c9315SChristos Margiolis #endif 41c15c9315SChristos Margiolis 42c15c9315SChristos Margiolis #include <dev/sound/pcm/sound.h> 43c15c9315SChristos Margiolis #include <mixer_if.h> 44c15c9315SChristos Margiolis 45c15c9315SChristos Margiolis #define DUMMY_NPCHAN 1 46c15c9315SChristos Margiolis #define DUMMY_NRCHAN 1 47c15c9315SChristos Margiolis #define DUMMY_NCHAN (DUMMY_NPCHAN + DUMMY_NRCHAN) 48c15c9315SChristos Margiolis 49c15c9315SChristos Margiolis struct dummy_chan { 50c15c9315SChristos Margiolis struct dummy_softc *sc; 51c15c9315SChristos Margiolis struct pcm_channel *chan; 52c15c9315SChristos Margiolis struct snd_dbuf *buf; 53c15c9315SChristos Margiolis struct pcmchan_caps *caps; 54c15c9315SChristos Margiolis uint32_t ptr; 55c15c9315SChristos Margiolis int dir; 56c15c9315SChristos Margiolis int run; 57c15c9315SChristos Margiolis }; 58c15c9315SChristos Margiolis 59c15c9315SChristos Margiolis struct dummy_softc { 60c15c9315SChristos Margiolis struct snddev_info info; 61c15c9315SChristos Margiolis device_t dev; 62c15c9315SChristos Margiolis uint32_t cap_fmts[4]; 63c15c9315SChristos Margiolis struct pcmchan_caps caps; 64c15c9315SChristos Margiolis int chnum; 65c15c9315SChristos Margiolis struct dummy_chan chans[DUMMY_NCHAN]; 66c15c9315SChristos Margiolis struct callout callout; 67c15c9315SChristos Margiolis struct mtx *lock; 68c15c9315SChristos Margiolis }; 69c15c9315SChristos Margiolis 70c15c9315SChristos Margiolis static void 71c15c9315SChristos Margiolis dummy_chan_io(void *arg) 72c15c9315SChristos Margiolis { 73c15c9315SChristos Margiolis struct dummy_softc *sc = arg; 74c15c9315SChristos Margiolis struct dummy_chan *ch; 75c15c9315SChristos Margiolis int i = 0; 76c15c9315SChristos Margiolis 77c15c9315SChristos Margiolis snd_mtxlock(sc->lock); 78c15c9315SChristos Margiolis 79c15c9315SChristos Margiolis for (i = 0; i < sc->chnum; i++) { 80c15c9315SChristos Margiolis ch = &sc->chans[i]; 81c15c9315SChristos Margiolis if (!ch->run) 82c15c9315SChristos Margiolis continue; 83c15c9315SChristos Margiolis if (ch->dir == PCMDIR_PLAY) 84c15c9315SChristos Margiolis ch->ptr += sndbuf_getblksz(ch->buf); 85c15c9315SChristos Margiolis else 86c15c9315SChristos Margiolis sndbuf_fillsilence(ch->buf); 87c15c9315SChristos Margiolis snd_mtxunlock(sc->lock); 88c15c9315SChristos Margiolis chn_intr(ch->chan); 89c15c9315SChristos Margiolis snd_mtxlock(sc->lock); 90c15c9315SChristos Margiolis } 91c15c9315SChristos Margiolis callout_schedule(&sc->callout, 1); 92c15c9315SChristos Margiolis 93c15c9315SChristos Margiolis snd_mtxunlock(sc->lock); 94c15c9315SChristos Margiolis } 95c15c9315SChristos Margiolis 96c15c9315SChristos Margiolis static int 97c15c9315SChristos Margiolis dummy_chan_free(kobj_t obj, void *data) 98c15c9315SChristos Margiolis { 99c15c9315SChristos Margiolis struct dummy_chan *ch =data; 100c15c9315SChristos Margiolis uint8_t *buf; 101c15c9315SChristos Margiolis 102c15c9315SChristos Margiolis buf = sndbuf_getbuf(ch->buf); 103c15c9315SChristos Margiolis if (buf != NULL) 104c15c9315SChristos Margiolis free(buf, M_DEVBUF); 105c15c9315SChristos Margiolis 106c15c9315SChristos Margiolis return (0); 107c15c9315SChristos Margiolis } 108c15c9315SChristos Margiolis 109c15c9315SChristos Margiolis static void * 110c15c9315SChristos Margiolis dummy_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 111c15c9315SChristos Margiolis struct pcm_channel *c, int dir) 112c15c9315SChristos Margiolis { 113c15c9315SChristos Margiolis struct dummy_softc *sc; 114c15c9315SChristos Margiolis struct dummy_chan *ch; 115c15c9315SChristos Margiolis uint8_t *buf; 116c15c9315SChristos Margiolis size_t bufsz; 117c15c9315SChristos Margiolis 118c15c9315SChristos Margiolis sc = devinfo; 119c15c9315SChristos Margiolis 120c15c9315SChristos Margiolis snd_mtxlock(sc->lock); 121c15c9315SChristos Margiolis 122c15c9315SChristos Margiolis ch = &sc->chans[sc->chnum++]; 123c15c9315SChristos Margiolis ch->sc = sc; 124c15c9315SChristos Margiolis ch->dir = dir; 125c15c9315SChristos Margiolis ch->chan = c; 126c15c9315SChristos Margiolis ch->buf = b; 127c15c9315SChristos Margiolis ch->caps = &sc->caps; 128c15c9315SChristos Margiolis 129c15c9315SChristos Margiolis snd_mtxunlock(sc->lock); 130c15c9315SChristos Margiolis 131c15c9315SChristos Margiolis bufsz = pcm_getbuffersize(sc->dev, 2048, 2048, 65536); 132c15c9315SChristos Margiolis buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO); 133c15c9315SChristos Margiolis if (sndbuf_setup(ch->buf, buf, bufsz) != 0) { 134c15c9315SChristos Margiolis dummy_chan_free(obj, ch); 135c15c9315SChristos Margiolis return (NULL); 136c15c9315SChristos Margiolis } 137c15c9315SChristos Margiolis 138c15c9315SChristos Margiolis return (ch); 139c15c9315SChristos Margiolis } 140c15c9315SChristos Margiolis 141c15c9315SChristos Margiolis static int 142c15c9315SChristos Margiolis dummy_chan_setformat(kobj_t obj, void *data, uint32_t format) 143c15c9315SChristos Margiolis { 144c15c9315SChristos Margiolis struct dummy_chan *ch = data; 145c15c9315SChristos Margiolis int i; 146c15c9315SChristos Margiolis 147c15c9315SChristos Margiolis for (i = 0; ch->caps->fmtlist[i]; i++) 148c15c9315SChristos Margiolis if (format == ch->caps->fmtlist[i]) 149c15c9315SChristos Margiolis return (0); 150c15c9315SChristos Margiolis 151c15c9315SChristos Margiolis return (EINVAL); 152c15c9315SChristos Margiolis } 153c15c9315SChristos Margiolis 154c15c9315SChristos Margiolis static uint32_t 155c15c9315SChristos Margiolis dummy_chan_setspeed(kobj_t obj, void *data, uint32_t speed) 156c15c9315SChristos Margiolis { 157c15c9315SChristos Margiolis struct dummy_chan *ch = data; 158c15c9315SChristos Margiolis 159c15c9315SChristos Margiolis RANGE(speed, ch->caps->minspeed, ch->caps->maxspeed); 160c15c9315SChristos Margiolis 161c15c9315SChristos Margiolis return (speed); 162c15c9315SChristos Margiolis } 163c15c9315SChristos Margiolis 164c15c9315SChristos Margiolis static uint32_t 165c15c9315SChristos Margiolis dummy_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) 166c15c9315SChristos Margiolis { 167c15c9315SChristos Margiolis struct dummy_chan *ch = data; 168c15c9315SChristos Margiolis 169c15c9315SChristos Margiolis return (sndbuf_getblksz(ch->buf)); 170c15c9315SChristos Margiolis } 171c15c9315SChristos Margiolis 172c15c9315SChristos Margiolis static int 173c15c9315SChristos Margiolis dummy_chan_trigger(kobj_t obj, void *data, int go) 174c15c9315SChristos Margiolis { 175c15c9315SChristos Margiolis struct dummy_chan *ch = data; 176c15c9315SChristos Margiolis struct dummy_softc *sc = ch->sc; 177c15c9315SChristos Margiolis 178c15c9315SChristos Margiolis snd_mtxlock(sc->lock); 179c15c9315SChristos Margiolis 180c15c9315SChristos Margiolis switch (go) { 181c15c9315SChristos Margiolis case PCMTRIG_START: 182c15c9315SChristos Margiolis if (!callout_active(&sc->callout)) 183c15c9315SChristos Margiolis callout_reset(&sc->callout, 1, dummy_chan_io, sc); 184c15c9315SChristos Margiolis ch->ptr = 0; 185c15c9315SChristos Margiolis ch->run = 1; 186c15c9315SChristos Margiolis break; 187c15c9315SChristos Margiolis case PCMTRIG_STOP: 188c15c9315SChristos Margiolis case PCMTRIG_ABORT: 189c15c9315SChristos Margiolis ch->run = 0; 190c15c9315SChristos Margiolis if (callout_active(&sc->callout)) 191c15c9315SChristos Margiolis callout_stop(&sc->callout); 192c15c9315SChristos Margiolis default: 193c15c9315SChristos Margiolis break; 194c15c9315SChristos Margiolis } 195c15c9315SChristos Margiolis 196c15c9315SChristos Margiolis snd_mtxunlock(sc->lock); 197c15c9315SChristos Margiolis 198c15c9315SChristos Margiolis return (0); 199c15c9315SChristos Margiolis } 200c15c9315SChristos Margiolis 201c15c9315SChristos Margiolis static uint32_t 202c15c9315SChristos Margiolis dummy_chan_getptr(kobj_t obj, void *data) 203c15c9315SChristos Margiolis { 204c15c9315SChristos Margiolis struct dummy_chan *ch = data; 205c15c9315SChristos Margiolis 206c15c9315SChristos Margiolis return (ch->run ? ch->ptr : 0); 207c15c9315SChristos Margiolis } 208c15c9315SChristos Margiolis 209c15c9315SChristos Margiolis static struct pcmchan_caps * 210c15c9315SChristos Margiolis dummy_chan_getcaps(kobj_t obj, void *data) 211c15c9315SChristos Margiolis { 212c15c9315SChristos Margiolis struct dummy_chan *ch = data; 213c15c9315SChristos Margiolis 214c15c9315SChristos Margiolis return (ch->caps); 215c15c9315SChristos Margiolis } 216c15c9315SChristos Margiolis 217c15c9315SChristos Margiolis static kobj_method_t dummy_chan_methods[] = { 218c15c9315SChristos Margiolis KOBJMETHOD(channel_init, dummy_chan_init), 219c15c9315SChristos Margiolis KOBJMETHOD(channel_free, dummy_chan_free), 220c15c9315SChristos Margiolis KOBJMETHOD(channel_setformat, dummy_chan_setformat), 221c15c9315SChristos Margiolis KOBJMETHOD(channel_setspeed, dummy_chan_setspeed), 222c15c9315SChristos Margiolis KOBJMETHOD(channel_setblocksize,dummy_chan_setblocksize), 223c15c9315SChristos Margiolis KOBJMETHOD(channel_trigger, dummy_chan_trigger), 224c15c9315SChristos Margiolis KOBJMETHOD(channel_getptr, dummy_chan_getptr), 225c15c9315SChristos Margiolis KOBJMETHOD(channel_getcaps, dummy_chan_getcaps), 226c15c9315SChristos Margiolis KOBJMETHOD_END 227c15c9315SChristos Margiolis }; 228c15c9315SChristos Margiolis 229c15c9315SChristos Margiolis CHANNEL_DECLARE(dummy_chan); 230c15c9315SChristos Margiolis 231c15c9315SChristos Margiolis static int 232c15c9315SChristos Margiolis dummy_mixer_init(struct snd_mixer *m) 233c15c9315SChristos Margiolis { 234c15c9315SChristos Margiolis struct dummy_softc *sc; 235c15c9315SChristos Margiolis 236c15c9315SChristos Margiolis sc = mix_getdevinfo(m); 237c15c9315SChristos Margiolis if (sc == NULL) 238c15c9315SChristos Margiolis return (-1); 239c15c9315SChristos Margiolis 240c15c9315SChristos Margiolis pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); 241c15c9315SChristos Margiolis mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV); 242c15c9315SChristos Margiolis mix_setrecdevs(m, SOUND_MASK_RECLEV); 243c15c9315SChristos Margiolis 244c15c9315SChristos Margiolis return (0); 245c15c9315SChristos Margiolis } 246c15c9315SChristos Margiolis 247c15c9315SChristos Margiolis static int 248c15c9315SChristos Margiolis dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) 249c15c9315SChristos Margiolis { 250c15c9315SChristos Margiolis return (0); 251c15c9315SChristos Margiolis } 252c15c9315SChristos Margiolis 253c15c9315SChristos Margiolis static uint32_t 254c15c9315SChristos Margiolis dummy_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) 255c15c9315SChristos Margiolis { 256c15c9315SChristos Margiolis return (src == SOUND_MASK_RECLEV ? src : 0); 257c15c9315SChristos Margiolis } 258c15c9315SChristos Margiolis 259c15c9315SChristos Margiolis static kobj_method_t dummy_mixer_methods[] = { 260c15c9315SChristos Margiolis KOBJMETHOD(mixer_init, dummy_mixer_init), 261c15c9315SChristos Margiolis KOBJMETHOD(mixer_set, dummy_mixer_set), 262c15c9315SChristos Margiolis KOBJMETHOD(mixer_setrecsrc, dummy_mixer_setrecsrc), 263c15c9315SChristos Margiolis KOBJMETHOD_END 264c15c9315SChristos Margiolis }; 265c15c9315SChristos Margiolis 266c15c9315SChristos Margiolis MIXER_DECLARE(dummy_mixer); 267c15c9315SChristos Margiolis 268c15c9315SChristos Margiolis static void 269c15c9315SChristos Margiolis dummy_identify(driver_t *driver, device_t parent) 270c15c9315SChristos Margiolis { 271c15c9315SChristos Margiolis if (device_find_child(parent, driver->name, -1) != NULL) 272c15c9315SChristos Margiolis return; 273c15c9315SChristos Margiolis if (BUS_ADD_CHILD(parent, 0, driver->name, -1) == NULL) 274c15c9315SChristos Margiolis device_printf(parent, "add child failed\n"); 275c15c9315SChristos Margiolis } 276c15c9315SChristos Margiolis 277c15c9315SChristos Margiolis static int 278c15c9315SChristos Margiolis dummy_probe(device_t dev) 279c15c9315SChristos Margiolis { 280c15c9315SChristos Margiolis device_set_desc(dev, "Dummy Audio Device"); 281c15c9315SChristos Margiolis 282c15c9315SChristos Margiolis return (0); 283c15c9315SChristos Margiolis } 284c15c9315SChristos Margiolis 285c15c9315SChristos Margiolis static int 286c15c9315SChristos Margiolis dummy_attach(device_t dev) 287c15c9315SChristos Margiolis { 288c15c9315SChristos Margiolis struct dummy_softc *sc; 289c15c9315SChristos Margiolis char status[SND_STATUSLEN]; 290c15c9315SChristos Margiolis int i = 0; 291c15c9315SChristos Margiolis 292c15c9315SChristos Margiolis sc = device_get_softc(dev); 293c15c9315SChristos Margiolis sc->dev = dev; 294c15c9315SChristos Margiolis sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_dummy softc"); 295c15c9315SChristos Margiolis 296c15c9315SChristos Margiolis sc->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, 2, 0); 297c15c9315SChristos Margiolis sc->cap_fmts[1] = SND_FORMAT(AFMT_S24_LE, 2, 0); 298c15c9315SChristos Margiolis sc->cap_fmts[2] = SND_FORMAT(AFMT_S16_LE, 2, 0); 299c15c9315SChristos Margiolis sc->cap_fmts[3] = 0; 300c15c9315SChristos Margiolis sc->caps = (struct pcmchan_caps){ 301c15c9315SChristos Margiolis 8000, /* minspeed */ 302c15c9315SChristos Margiolis 96000, /* maxspeed */ 303c15c9315SChristos Margiolis sc->cap_fmts, /* fmtlist */ 304c15c9315SChristos Margiolis 0, /* caps */ 305c15c9315SChristos Margiolis }; 306c15c9315SChristos Margiolis 307c15c9315SChristos Margiolis pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); 308*516a9c02SChristos Margiolis pcm_init(dev, sc); 309c15c9315SChristos Margiolis for (i = 0; i < DUMMY_NPCHAN; i++) 310c15c9315SChristos Margiolis pcm_addchan(dev, PCMDIR_PLAY, &dummy_chan_class, sc); 311c15c9315SChristos Margiolis for (i = 0; i < DUMMY_NRCHAN; i++) 312c15c9315SChristos Margiolis pcm_addchan(dev, PCMDIR_REC, &dummy_chan_class, sc); 313c15c9315SChristos Margiolis 314c15c9315SChristos Margiolis snprintf(status, SND_STATUSLEN, "on %s", 315c15c9315SChristos Margiolis device_get_nameunit(device_get_parent(dev))); 316*516a9c02SChristos Margiolis if (pcm_register(dev, status)) 317*516a9c02SChristos Margiolis return (ENXIO); 318c15c9315SChristos Margiolis mixer_init(dev, &dummy_mixer_class, sc); 319c15c9315SChristos Margiolis callout_init(&sc->callout, 1); 320c15c9315SChristos Margiolis 321c15c9315SChristos Margiolis return (0); 322c15c9315SChristos Margiolis } 323c15c9315SChristos Margiolis 324c15c9315SChristos Margiolis static int 325c15c9315SChristos Margiolis dummy_detach(device_t dev) 326c15c9315SChristos Margiolis { 327c15c9315SChristos Margiolis struct dummy_softc *sc = device_get_softc(dev); 328c15c9315SChristos Margiolis int err; 329c15c9315SChristos Margiolis 330e42c8267SChristos Margiolis callout_drain(&sc->callout); 331c15c9315SChristos Margiolis err = pcm_unregister(dev); 332c15c9315SChristos Margiolis snd_mtxfree(sc->lock); 333c15c9315SChristos Margiolis 334c15c9315SChristos Margiolis return (err); 335c15c9315SChristos Margiolis } 336c15c9315SChristos Margiolis 337c15c9315SChristos Margiolis static device_method_t dummy_methods[] = { 338c15c9315SChristos Margiolis /* Device interface */ 339c15c9315SChristos Margiolis DEVMETHOD(device_identify, dummy_identify), 340c15c9315SChristos Margiolis DEVMETHOD(device_probe, dummy_probe), 341c15c9315SChristos Margiolis DEVMETHOD(device_attach, dummy_attach), 342c15c9315SChristos Margiolis DEVMETHOD(device_detach, dummy_detach), 343c15c9315SChristos Margiolis DEVMETHOD_END 344c15c9315SChristos Margiolis }; 345c15c9315SChristos Margiolis 346c15c9315SChristos Margiolis static driver_t dummy_driver = { 347c15c9315SChristos Margiolis "pcm", 348c15c9315SChristos Margiolis dummy_methods, 349c15c9315SChristos Margiolis sizeof(struct dummy_softc), 350c15c9315SChristos Margiolis }; 351c15c9315SChristos Margiolis 352c15c9315SChristos Margiolis DRIVER_MODULE(snd_dummy, nexus, dummy_driver, 0, 0); 353c15c9315SChristos Margiolis MODULE_DEPEND(snd_dummy, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); 354c15c9315SChristos Margiolis MODULE_VERSION(snd_dummy, 1); 355