/* $NetBSD: emuxki.c,v 1.78 2023/12/20 05:08:34 thorpej Exp $ */ /*- * Copyright (c) 2001, 2007 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Yannick Montulet, and by Andrew Doran. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * EMU10K1 single voice driver * o. only 1 voice playback, 1 recording * o. only s16le 2ch 48k * This makes it simple to control buffers and interrupts * while satisfying playback and recording quality. */ #include __KERNEL_RCSID(0, "$NetBSD: emuxki.c,v 1.78 2023/12/20 05:08:34 thorpej Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #define EMUXKI_DEBUG 1 */ #ifdef EMUXKI_DEBUG #define emudebug EMUXKI_DEBUG # define DPRINTF(fmt...) do { if (emudebug) printf(fmt); } while (0) # define DPRINTFN(n,fmt...) do { if (emudebug>=(n)) printf(fmt); } while (0) #else # define DPRINTF(fmt...) __nothing # define DPRINTFN(n,fmt...) __nothing #endif /* * PCI * Note: emuxki's page table entry uses only 31bit addressing. * (Maybe, later chip has 32bit mode, but it isn't used now.) */ #define EMU_PCI_CBIO (0x10) /* blackmagic */ #define X1(x) ((sc->sc_type & EMUXKI_AUDIGY) ? EMU_A_##x : EMU_##x) #define X2(x, y) ((sc->sc_type & EMUXKI_AUDIGY) \ ? EMU_A_##x(EMU_A_##y) : EMU_##x(EMU_##y)) #define EMU_A_DSP_FX EMU_DSP_FX #define EMU_A_DSP_IN_AC97 EMU_DSP_IN_AC97 /* prototypes */ static struct dmamem *dmamem_alloc(struct emuxki_softc *, size_t); static void dmamem_free(struct dmamem *); static void dmamem_sync(struct dmamem *, int); static uint8_t emuxki_readio_1(struct emuxki_softc *, int) __unused; static uint16_t emuxki_readio_2(struct emuxki_softc *, int); static uint32_t emuxki_readio_4(struct emuxki_softc *, int); static void emuxki_writeio_1(struct emuxki_softc *, int, uint8_t); static void emuxki_writeio_2(struct emuxki_softc *, int, uint16_t); static void emuxki_writeio_4(struct emuxki_softc *, int, uint32_t); static uint32_t emuxki_readptr(struct emuxki_softc *, int, int, int); static void emuxki_writeptr(struct emuxki_softc *, int, int, int, uint32_t); static uint32_t emuxki_read(struct emuxki_softc *, int, int); static void emuxki_write(struct emuxki_softc *, int, int, uint32_t); static int emuxki_match(device_t, cfdata_t, void *); static void emuxki_attach(device_t, device_t, void *); static int emuxki_detach(device_t, int); static int emuxki_init(struct emuxki_softc *); static void emuxki_dsp_addop(struct emuxki_softc *, uint16_t *, uint8_t, uint16_t, uint16_t, uint16_t, uint16_t); static void emuxki_initfx(struct emuxki_softc *); static void emuxki_play_start(struct emuxki_softc *, int, uint32_t, uint32_t); static void emuxki_play_stop(struct emuxki_softc *, int); static int emuxki_query_format(void *, audio_format_query_t *); static int emuxki_set_format(void *, int, const audio_params_t *, const audio_params_t *, audio_filter_reg_t *, audio_filter_reg_t *); static int emuxki_halt_output(void *); static int emuxki_halt_input(void *); static int emuxki_intr(void *); static int emuxki_getdev(void *, struct audio_device *); static int emuxki_set_port(void *, mixer_ctrl_t *); static int emuxki_get_port(void *, mixer_ctrl_t *); static int emuxki_query_devinfo(void *, mixer_devinfo_t *); static void *emuxki_allocm(void *, int, size_t); static void emuxki_freem(void *, void *, size_t); static int emuxki_round_blocksize(void *, int, int, const audio_params_t *); static size_t emuxki_round_buffersize(void *, int, size_t); static int emuxki_get_props(void *); static int emuxki_trigger_output(void *, void *, void *, int, void (*)(void *), void *, const audio_params_t *); static int emuxki_trigger_input(void *, void *, void *, int, void (*)(void *), void *, const audio_params_t *); static void emuxki_get_locks(void *, kmutex_t **, kmutex_t **); static int emuxki_ac97_init(struct emuxki_softc *); static int emuxki_ac97_attach(void *, struct ac97_codec_if *); static int emuxki_ac97_read(void *, uint8_t, uint16_t *); static int emuxki_ac97_write(void *, uint8_t, uint16_t); static int emuxki_ac97_reset(void *); static enum ac97_host_flags emuxki_ac97_flags(void *); CFATTACH_DECL_NEW(emuxki, sizeof(struct emuxki_softc), emuxki_match, emuxki_attach, emuxki_detach, NULL); static const struct audio_hw_if emuxki_hw_if = { .query_format = emuxki_query_format, .set_format = emuxki_set_format, .round_blocksize = emuxki_round_blocksize, .halt_output = emuxki_halt_output, .halt_input = emuxki_halt_input, .getdev = emuxki_getdev, .set_port = emuxki_set_port, .get_port = emuxki_get_port, .query_devinfo = emuxki_query_devinfo, .allocm = emuxki_allocm, .freem = emuxki_freem, .round_buffersize = emuxki_round_buffersize, .get_props = emuxki_get_props, .trigger_output = emuxki_trigger_output, .trigger_input = emuxki_trigger_input, .get_locks = emuxki_get_locks, }; static const struct audio_format emuxki_formats[] = { { .mode = AUMODE_PLAY | AUMODE_RECORD, .encoding = AUDIO_ENCODING_SLINEAR_LE, .validbits = 16, .precision = 16, .channels = 2, .channel_mask = AUFMT_STEREO, .frequency_type = 1, .frequency = { 48000 }, } }; #define EMUXKI_NFORMATS __arraycount(emuxki_formats) /* * dma memory */ static struct dmamem * dmamem_alloc(struct emuxki_softc *sc, size_t size) { struct dmamem *mem; KASSERT(!mutex_owned(&sc->sc_intr_lock)); /* Allocate memory for structure */ mem = kmem_alloc(sizeof(*mem), KM_SLEEP); mem->dmat = sc->sc_dmat; mem->size = size; mem->align = EMU_DMA_ALIGN; mem->nsegs = EMU_DMA_NSEGS; mem->bound = 0; mem->segs = kmem_alloc(mem->nsegs * sizeof(*(mem->segs)), KM_SLEEP); if (bus_dmamem_alloc(mem->dmat, mem->size, mem->align, mem->bound, mem->segs, mem->nsegs, &mem->rsegs, BUS_DMA_WAITOK)) { device_printf(sc->sc_dev, "%s bus_dmamem_alloc failed\n", __func__); goto memfree; } if (bus_dmamem_map(mem->dmat, mem->segs, mem->nsegs, mem->size, &mem->kaddr, BUS_DMA_WAITOK | BUS_DMA_COHERENT)) { device_printf(sc->sc_dev, "%s bus_dmamem_map failed\n", __func__); goto free; } if (bus_dmamap_create(mem->dmat, mem->size, mem->nsegs, mem->size, mem->bound, BUS_DMA_WAITOK, &mem->map)) { device_printf(sc->sc_dev, "%s bus_dmamap_create failed\n", __func__); goto unmap; } if (bus_dmamap_load(mem->dmat, mem->map, mem->kaddr, mem->size, NULL, BUS_DMA_WAITOK)) { device_printf(sc->sc_dev, "%s bus_dmamap_load failed\n", __func__); goto destroy; } return mem; destroy: bus_dmamap_destroy(mem->dmat, mem->map); unmap: bus_dmamem_unmap(mem->dmat, mem->kaddr, mem->size); free: bus_dmamem_free(mem->dmat, mem->segs, mem->nsegs); memfree: kmem_free(mem->segs, mem->nsegs * sizeof(*(mem->segs))); kmem_free(mem, sizeof(*mem)); return NULL; } static void dmamem_free(struct dmamem *mem) { bus_dmamap_unload(mem->dmat, mem->map); bus_dmamap_destroy(mem->dmat, mem->map); bus_dmamem_unmap(mem->dmat, mem->kaddr, mem->size); bus_dmamem_free(mem->dmat, mem->segs, mem->nsegs); kmem_free(mem->segs, mem->nsegs * sizeof(*(mem->segs))); kmem_free(mem, sizeof(*mem)); } static void dmamem_sync(struct dmamem *mem, int ops) { bus_dmamap_sync(mem->dmat, mem->map, 0, mem->size, ops); } /* * I/O register access */ static uint8_t emuxki_readio_1(struct emuxki_softc *sc, int addr) { return bus_space_read_1(sc->sc_iot, sc->sc_ioh, addr); } static void emuxki_writeio_1(struct emuxki_softc *sc, int addr, uint8_t data) { bus_space_write_1(sc->sc_iot, sc->sc_ioh, addr, data); } static uint16_t emuxki_readio_2(struct emuxki_softc *sc, int addr) { return bus_space_read_2(sc->sc_iot, sc->sc_ioh, addr); } static void emuxki_writeio_2(struct emuxki_softc *sc, int addr, uint16_t data) { bus_space_write_2(sc->sc_iot, sc->sc_ioh, addr, data); } static uint32_t emuxki_readio_4(struct emuxki_softc *sc, int addr) { return bus_space_read_4(sc->sc_iot, sc->sc_ioh, addr); } static void emuxki_writeio_4(struct emuxki_softc *sc, int addr, uint32_t data) { bus_space_write_4(sc->sc_iot, sc->sc_ioh, addr, data); } static uint32_t emuxki_readptr(struct emuxki_softc *sc, int aptr, int dptr, int addr) { uint32_t data; mutex_spin_enter(&sc->sc_index_lock); emuxki_writeio_4(sc, aptr, addr); data = emuxki_readio_4(sc, dptr); mutex_spin_exit(&sc->sc_index_lock); return data; } static void emuxki_writeptr(struct emuxki_softc *sc, int aptr, int dptr, int addr, uint32_t data) { mutex_spin_enter(&sc->sc_index_lock); emuxki_writeio_4(sc, aptr, addr); emuxki_writeio_4(sc, dptr, data); mutex_spin_exit(&sc->sc_index_lock); } static uint32_t emuxki_read(struct emuxki_softc *sc, int ch, int addr) { /* Original HENTAI addressing is never supported. */ KASSERT((addr & 0xff000000) == 0); return emuxki_readptr(sc, EMU_PTR, EMU_DATA, (addr << 16) + ch); } static void emuxki_write(struct emuxki_softc *sc, int ch, int addr, uint32_t data) { /* Original HENTAI addressing is never supported. */ KASSERT((addr & 0xff000000) == 0); emuxki_writeptr(sc, EMU_PTR, EMU_DATA, (addr << 16) + ch, data); } /* * MD driver */ static int emuxki_match(device_t parent, cfdata_t match, void *aux) { struct pci_attach_args *pa; pcireg_t reg; pa = aux; reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG); if (emuxki_board_lookup(PCI_VENDOR(pa->pa_id), PCI_PRODUCT(pa->pa_id), reg, PCI_REVISION(pa->pa_class)) != NULL) return 1; return 0; } static void emuxki_attach(device_t parent, device_t self, void *aux) { struct emuxki_softc *sc; struct pci_attach_args *pa; const struct emuxki_board *sb; pci_intr_handle_t ih; const char *intrstr; char intrbuf[PCI_INTRSTR_LEN]; pcireg_t reg; sc = device_private(self); sc->sc_dev = self; pa = aux; reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG); sb = emuxki_board_lookup(PCI_VENDOR(pa->pa_id), PCI_PRODUCT(pa->pa_id), reg, PCI_REVISION(pa->pa_class)); KASSERT(sb != NULL); pci_aprint_devinfo(pa, "Audio controller"); aprint_normal_dev(self, "%s [%s]\n", sb->sb_name, sb->sb_board); DPRINTF("dmat=%p\n", (char *)pa->pa_dmat); mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO); mutex_init(&sc->sc_index_lock, MUTEX_DEFAULT, IPL_AUDIO); sc->sc_pc = pa->pa_pc; /* EMU10K1 can only address 31 bits (2GB) */ if (bus_dmatag_subregion(pa->pa_dmat, 0, ((uint32_t)1 << 31) - 1, &(sc->sc_dmat), BUS_DMA_NOWAIT) != 0) { aprint_error_dev(self, "WARNING: failed to restrict dma range," " falling back to parent bus dma range\n"); sc->sc_dmat = pa->pa_dmat; } reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG); reg |= PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MASTER_ENABLE | PCI_COMMAND_MEM_ENABLE; pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, reg); if (pci_mapreg_map(pa, EMU_PCI_CBIO, PCI_MAPREG_TYPE_IO, 0, &sc->sc_iot, &sc->sc_ioh, &sc->sc_iob, &sc->sc_ios)) { aprint_error(": can't map iospace\n"); return; } if (pci_intr_map(pa, &ih)) { aprint_error_dev(self, "couldn't map interrupt\n"); goto unmap; } intrstr = pci_intr_string(pa->pa_pc, ih, intrbuf, sizeof(intrbuf)); sc->sc_ih = pci_intr_establish_xname(pa->pa_pc, ih, IPL_AUDIO, emuxki_intr, sc, device_xname(self)); if (sc->sc_ih == NULL) { aprint_error_dev(self, "couldn't establish interrupt"); if (intrstr != NULL) aprint_error(" at %s", intrstr); aprint_error("\n"); goto unmap; } aprint_normal_dev(self, "interrupting at %s\n", intrstr); /* XXX it's unknown whether APS is made from Audigy as well */ sc->sc_type = sb->sb_flags; if (sc->sc_type & EMUXKI_AUDIGY2_CA0108) { strlcpy(sc->sc_audv.name, "Audigy2+CA0108", sizeof(sc->sc_audv.name)); } else if (sc->sc_type & EMUXKI_AUDIGY2) { strlcpy(sc->sc_audv.name, "Audigy2", sizeof(sc->sc_audv.name)); } else if (sc->sc_type & EMUXKI_AUDIGY) { strlcpy(sc->sc_audv.name, "Audigy", sizeof(sc->sc_audv.name)); } else if (sc->sc_type & EMUXKI_APS) { strlcpy(sc->sc_audv.name, "E-mu APS", sizeof(sc->sc_audv.name)); } else { strlcpy(sc->sc_audv.name, "SB Live!", sizeof(sc->sc_audv.name)); } snprintf(sc->sc_audv.version, sizeof(sc->sc_audv.version), "0x%02x", PCI_REVISION(pa->pa_class)); strlcpy(sc->sc_audv.config, "emuxki", sizeof(sc->sc_audv.config)); if (emuxki_init(sc)) { aprint_error("emuxki_init error\n"); goto intrdis; } if (emuxki_ac97_init(sc)) { aprint_error("emuxki_ac97_init error\n"); goto intrdis; } sc->sc_audev = audio_attach_mi(&emuxki_hw_if, sc, self); if (sc->sc_audev == NULL) { aprint_error("audio_attach_mi error\n"); goto intrdis; } return; intrdis: pci_intr_disestablish(sc->sc_pc, sc->sc_ih); unmap: bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); return; } static int emuxki_detach(device_t self, int flags) { struct emuxki_softc *sc = device_private(self); int error; error = config_detach_children(self, flags); if (error) return error; /* All voices should be stopped now but add some code here if not */ emuxki_writeio_4(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE); emuxki_writeio_4(sc, EMU_INTE, 0); /* Disable any Channels interrupts */ emuxki_write(sc, 0, EMU_CLIEL, 0); emuxki_write(sc, 0, EMU_CLIEH, 0); emuxki_write(sc, 0, EMU_SOLEL, 0); emuxki_write(sc, 0, EMU_SOLEH, 0); /* stop DSP */ emuxki_write(sc, 0, X1(DBG), X1(DBG_SINGLE_STEP)); dmamem_free(sc->ptb); pci_intr_disestablish(sc->sc_pc, sc->sc_ih); bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); mutex_destroy(&sc->sc_lock); mutex_destroy(&sc->sc_intr_lock); mutex_destroy(&sc->sc_index_lock); return 0; } static int emuxki_init(struct emuxki_softc *sc) { int i; uint32_t spcs; uint32_t hcfg; /* clear AUDIO bit */ emuxki_writeio_4(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE); /* mask interrupt without PCIERR */ emuxki_writeio_4(sc, EMU_INTE, EMU_INTE_SAMPLERATER | /* always on this bit */ EMU_INTE_PCIERRENABLE); /* disable all channel interrupt */ emuxki_write(sc, 0, EMU_CLIEL, 0); emuxki_write(sc, 0, EMU_CLIEH, 0); emuxki_write(sc, 0, EMU_SOLEL, 0); emuxki_write(sc, 0, EMU_SOLEH, 0); /* Set recording buffers sizes to zero */ emuxki_write(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emuxki_write(sc, 0, EMU_MICBA, 0); emuxki_write(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emuxki_write(sc, 0, EMU_FXBA, 0); emuxki_write(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emuxki_write(sc, 0, EMU_ADCBA, 0); if(sc->sc_type & EMUXKI_AUDIGY) { emuxki_write(sc, 0, EMU_SPBYPASS, EMU_SPBYPASS_24_BITS); emuxki_write(sc, 0, EMU_AC97SLOT, EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE); } /* Initialize all channels to stopped and no effects */ for (i = 0; i < EMU_NUMCHAN; i++) { emuxki_write(sc, i, EMU_CHAN_DCYSUSV, 0x7f7f); emuxki_write(sc, i, EMU_CHAN_IP, EMU_CHAN_IP_UNITY); emuxki_write(sc, i, EMU_CHAN_VTFT, 0xffff); emuxki_write(sc, i, EMU_CHAN_CVCF, 0xffff); emuxki_write(sc, i, EMU_CHAN_PTRX, 0); emuxki_write(sc, i, EMU_CHAN_CPF, 0); emuxki_write(sc, i, EMU_CHAN_CCR, 0); emuxki_write(sc, i, EMU_CHAN_PSST, 0); emuxki_write(sc, i, EMU_CHAN_DSL, 0); emuxki_write(sc, i, EMU_CHAN_CCCA, EMU_CHAN_CCCA_INTERPROM_1); emuxki_write(sc, i, EMU_CHAN_Z1, 0); emuxki_write(sc, i, EMU_CHAN_Z2, 0); emuxki_write(sc, i, EMU_CHAN_MAPA, 0xffffffff); emuxki_write(sc, i, EMU_CHAN_MAPB, 0xffffffff); emuxki_write(sc, i, EMU_CHAN_FXRT, 0x32100000); emuxki_write(sc, i, EMU_CHAN_ATKHLDM, 0); emuxki_write(sc, i, EMU_CHAN_DCYSUSM, 0); emuxki_write(sc, i, EMU_CHAN_IFATN, 0xffff); emuxki_write(sc, i, EMU_CHAN_PEFE, 0x007f); emuxki_write(sc, i, EMU_CHAN_FMMOD, 0); emuxki_write(sc, i, EMU_CHAN_TREMFRQ, 0); emuxki_write(sc, i, EMU_CHAN_FM2FRQ2, 0); emuxki_write(sc, i, EMU_CHAN_TEMPENV, 0); /* these are last so OFF prevents writing */ emuxki_write(sc, i, EMU_CHAN_LFOVAL2, 0x8000); emuxki_write(sc, i, EMU_CHAN_LFOVAL1, 0x8000); emuxki_write(sc, i, EMU_CHAN_ATKHLDV, 0x7f7f); emuxki_write(sc, i, EMU_CHAN_ENVVOL, 0); emuxki_write(sc, i, EMU_CHAN_ENVVAL, 0x8000); } /* set digital outputs format */ spcs = EMU_SPCS_CLKACCY_1000PPM | EMU_SPCS_SAMPLERATE_48 | EMU_SPCS_CHANNELNUM_LEFT | EMU_SPCS_SOURCENUM_UNSPEC | EMU_SPCS_GENERATIONSTATUS | 0x00001200 /* Cat code. */ | 0x00000000 /* IEC-958 Mode */ | EMU_SPCS_EMPHASIS_NONE | EMU_SPCS_COPYRIGHT; emuxki_write(sc, 0, EMU_SPCS0, spcs); emuxki_write(sc, 0, EMU_SPCS1, spcs); emuxki_write(sc, 0, EMU_SPCS2, spcs); if (sc->sc_type & EMUXKI_AUDIGY2_CA0108) { /* Setup SRCMulti_I2S SamplingRate */ emuxki_write(sc, 0, EMU_A2_SPDIF_SAMPLERATE, emuxki_read(sc, 0, EMU_A2_SPDIF_SAMPLERATE) & 0xfffff1ff); /* Setup SRCSel (Enable SPDIF, I2S SRCMulti) */ emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, EMU_A2_SRCSEL, EMU_A2_SRCSEL_ENABLE_SPDIF | EMU_A2_SRCSEL_ENABLE_SRCMULTI); /* Setup SRCMulti Input Audio Enable */ emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, 0x7b0000, 0xff000000); /* Setup SPDIF Out Audio Enable * The Audigy 2 Value has a separate SPDIF out, * so no need for a mixer switch */ emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, 0x7a0000, 0xff000000); emuxki_writeio_4(sc, EMU_A_IOCFG, emuxki_readio_4(sc, EMU_A_IOCFG) & ~0x8); /* clear bit 3 */ } else if (sc->sc_type & EMUXKI_AUDIGY2) { emuxki_write(sc, 0, EMU_A2_SPDIF_SAMPLERATE, EMU_A2_SPDIF_UNKNOWN); emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, EMU_A2_SRCSEL, EMU_A2_SRCSEL_ENABLE_SPDIF | EMU_A2_SRCSEL_ENABLE_SRCMULTI); emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, EMU_A2_SRCMULTI, EMU_A2_SRCMULTI_ENABLE_INPUT); } /* page table */ sc->ptb = dmamem_alloc(sc, EMU_MAXPTE * sizeof(uint32_t)); if (sc->ptb == NULL) { device_printf(sc->sc_dev, "ptb allocation error\n"); return ENOMEM; } emuxki_write(sc, 0, EMU_PTB, DMAADDR(sc->ptb)); emuxki_write(sc, 0, EMU_TCBS, 0); /* This means 16K TCB */ emuxki_write(sc, 0, EMU_TCB, 0); /* No TCB use for now */ /* Let's play with sound processor */ emuxki_initfx(sc); /* enable interrupt */ emuxki_writeio_4(sc, EMU_INTE, emuxki_readio_4(sc, EMU_INTE) | EMU_INTE_VOLINCRENABLE | EMU_INTE_VOLDECRENABLE | EMU_INTE_MUTEENABLE); if (sc->sc_type & EMUXKI_AUDIGY2_CA0108) { emuxki_writeio_4(sc, EMU_A_IOCFG, 0x0060 | emuxki_readio_4(sc, EMU_A_IOCFG)); } else if (sc->sc_type & EMUXKI_AUDIGY2) { emuxki_writeio_4(sc, EMU_A_IOCFG, EMU_A_IOCFG_GPOUT0 | emuxki_readio_4(sc, EMU_A_IOCFG)); } /* enable AUDIO bit */ hcfg = EMU_HCFG_AUDIOENABLE | EMU_HCFG_AUTOMUTE; if (sc->sc_type & EMUXKI_AUDIGY2) { hcfg |= EMU_HCFG_AC3ENABLE_CDSPDIF | EMU_HCFG_AC3ENABLE_GPSPDIF; } else if (sc->sc_type & EMUXKI_AUDIGY) { } else { hcfg |= EMU_HCFG_LOCKTANKCACHE_MASK; } /* joystick not supported now */ emuxki_writeio_4(sc, EMU_HCFG, hcfg); return 0; } /* * dsp programming */ static void emuxki_dsp_addop(struct emuxki_softc *sc, uint16_t *pc, uint8_t op, uint16_t r, uint16_t a, uint16_t x, uint16_t y) { uint32_t loword; uint32_t hiword; int reg; if (sc->sc_type & EMUXKI_AUDIGY) { reg = EMU_A_MICROCODEBASE; loword = (x << 12) & EMU_A_DSP_LOWORD_OPX_MASK; loword |= y & EMU_A_DSP_LOWORD_OPY_MASK; hiword = (op << 24) & EMU_A_DSP_HIWORD_OPCODE_MASK; hiword |= (r << 12) & EMU_A_DSP_HIWORD_RESULT_MASK; hiword |= a & EMU_A_DSP_HIWORD_OPA_MASK; } else { reg = EMU_MICROCODEBASE; loword = (x << 10) & EMU_DSP_LOWORD_OPX_MASK; loword |= y & EMU_DSP_LOWORD_OPY_MASK; hiword = (op << 20) & EMU_DSP_HIWORD_OPCODE_MASK; hiword |= (r << 10) & EMU_DSP_HIWORD_RESULT_MASK; hiword |= a & EMU_DSP_HIWORD_OPA_MASK; } reg += (*pc) * 2; /* must ordering; lo, hi */ emuxki_write(sc, 0, reg, loword); emuxki_write(sc, 0, reg + 1, hiword); (*pc)++; } static void emuxki_initfx(struct emuxki_softc *sc) { uint16_t pc; /* Set all GPRs to 0 */ for (pc = 0; pc < 256; pc++) emuxki_write(sc, 0, EMU_DSP_GPR(pc), 0); for (pc = 0; pc < 160; pc++) { emuxki_write(sc, 0, EMU_TANKMEMDATAREGBASE + pc, 0); emuxki_write(sc, 0, EMU_TANKMEMADDRREGBASE + pc, 0); } /* stop DSP, single step mode */ emuxki_write(sc, 0, X1(DBG), X1(DBG_SINGLE_STEP)); /* XXX: delay (48kHz equiv. 21us) if needed */ /* start DSP programming */ pc = 0; /* OUT[L/R] = 0 + FX[L/R] * 1 */ emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS, X2(DSP_OUTL, DSP_OUT_A_FRONT), X1(DSP_CST(0)), X1(DSP_FX(0)), X1(DSP_CST(1))); emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS, X2(DSP_OUTR, DSP_OUT_A_FRONT), X1(DSP_CST(0)), X1(DSP_FX(1)), X1(DSP_CST(1))); #if 0 /* XXX: rear feature??? */ /* Rear OUT[L/R] = 0 + FX[L/R] * 1 */ emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS, X2(DSP_OUTL, DSP_OUT_A_REAR), X1(DSP_CST(0)), X1(DSP_FX(0)), X1(DSP_CST(1))); emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS, X2(DSP_OUTR, DSP_OUT_A_REAR), X1(DSP_CST(0)), X1(DSP_FX(1)), X1(DSP_CST(1))); #endif /* ADC recording[L/R] = AC97 In[L/R] */ emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3, X2(DSP_OUTL, DSP_OUT_ADC), X2(DSP_INL, DSP_IN_AC97), X1(DSP_CST(0)), X1(DSP_CST(0))); emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3, X2(DSP_OUTR, DSP_OUT_ADC), X2(DSP_INR, DSP_IN_AC97), X1(DSP_CST(0)), X1(DSP_CST(0))); /* fill NOP the rest of the microcode */ while (pc < 512) { emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3, X1(DSP_CST(0)), X1(DSP_CST(0)), X1(DSP_CST(0)), X1(DSP_CST(0))); } /* clear single step flag, run DSP */ emuxki_write(sc, 0, X1(DBG), 0); } /* * operations */ static void emuxki_play_start(struct emuxki_softc *sc, int ch, uint32_t start, uint32_t end) { uint32_t pitch; uint32_t volume; /* 48kHz:16384 = 128/375 */ pitch = sc->play.sample_rate * 128 / 375; volume = 32767; emuxki_write(sc, ch, EMU_CHAN_DSL, (0 << 24) | /* send amound D = 0 */ end); emuxki_write(sc, ch, EMU_CHAN_PSST, (0 << 24) | /* send amount C = 0 */ start); emuxki_write(sc, ch, EMU_CHAN_VTFT, (volume << 16) | (0xffff)); /* cutoff filter = none */ emuxki_write(sc, ch, EMU_CHAN_CVCF, (volume << 16) | (0xffff)); /* cutoff filter = none */ emuxki_write(sc, ch, EMU_CHAN_PTRX, (pitch << 16) | ((ch == 0 ? 0x7f : 0) << 8) | /* send amount A = 255,0(L) */ ((ch == 0 ? 0 : 0x7f))); /* send amount B = 0,255(R) */ /* set the pitch to start */ emuxki_write(sc, ch, EMU_CHAN_CPF, (pitch << 16) | EMU_CHAN_CPF_STEREO_MASK); /* stereo only */ } static void emuxki_play_stop(struct emuxki_softc *sc, int ch) { /* pitch = 0 to stop playing */ emuxki_write(sc, ch, EMU_CHAN_CPF, EMU_CHAN_CPF_STOP_MASK); /* volume = 0 */ emuxki_write(sc, ch, EMU_CHAN_CVCF, 0); } static void emuxki_timer_start(struct emuxki_softc *sc) { uint32_t timer; /* frame count of half PTE at 16bit, 2ch, 48kHz */ timer = EMU_PTESIZE / 4 / 2; /* EMU_TIMER is 16bit register */ emuxki_writeio_2(sc, EMU_TIMER, timer); emuxki_writeio_4(sc, EMU_INTE, emuxki_readio_4(sc, EMU_INTE) | EMU_INTE_INTERTIMERENB); DPRINTF("timer start\n"); } static void emuxki_timer_stop(struct emuxki_softc *sc) { emuxki_writeio_4(sc, EMU_INTE, emuxki_readio_4(sc, EMU_INTE) & ~EMU_INTE_INTERTIMERENB); /* EMU_TIMER is 16bit register */ emuxki_writeio_2(sc, EMU_TIMER, 0); DPRINTF("timer stop\n"); } /* * audio interface */ static int emuxki_query_format(void *hdl, audio_format_query_t *afp) { return audio_query_format(emuxki_formats, EMUXKI_NFORMATS, afp); } static int emuxki_set_format(void *hdl, int setmode, const audio_params_t *play, const audio_params_t *rec, audio_filter_reg_t *pfil, audio_filter_reg_t *rfil) { struct emuxki_softc *sc = hdl; if ((setmode & AUMODE_PLAY)) sc->play = *play; if ((setmode & AUMODE_RECORD)) sc->rec = *rec; return 0; } static int emuxki_halt_output(void *hdl) { struct emuxki_softc *sc = hdl; emuxki_timer_stop(sc); emuxki_play_stop(sc, 0); emuxki_play_stop(sc, 1); return 0; } static int emuxki_halt_input(void *hdl) { struct emuxki_softc *sc = hdl; /* stop ADC */ emuxki_write(sc, 0, EMU_ADCCR, 0); /* disable interrupt */ emuxki_writeio_4(sc, EMU_INTE, emuxki_readio_4(sc, EMU_INTE) & ~EMU_INTE_ADCBUFENABLE); return 0; } static int emuxki_intr(void *hdl) { struct emuxki_softc *sc = hdl; uint32_t ipr; uint32_t curaddr; int handled = 0; mutex_spin_enter(&sc->sc_intr_lock); ipr = emuxki_readio_4(sc, EMU_IPR); DPRINTFN(3, "emuxki: ipr=%08x\n", ipr); if (sc->pintr && (ipr & EMU_IPR_INTERVALTIMER)) { /* read ch 0 */ curaddr = emuxki_read(sc, 0, EMU_CHAN_CCCA) & EMU_CHAN_CCCA_CURRADDR_MASK; DPRINTFN(3, "curaddr=%08x\n", curaddr); curaddr *= sc->pframesize; if (curaddr < sc->poffset) curaddr += sc->plength; if (curaddr >= sc->poffset + sc->pblksize) { dmamem_sync(sc->pmem, BUS_DMASYNC_POSTWRITE); sc->pintr(sc->pintrarg); sc->poffset += sc->pblksize; if (sc->poffset >= sc->plength) { sc->poffset -= sc->plength; } dmamem_sync(sc->pmem, BUS_DMASYNC_PREWRITE); } handled = 1; } if (sc->rintr && (ipr & (EMU_IPR_ADCBUFHALFFULL | EMU_IPR_ADCBUFFULL))) { char *src; char *dst; /* Record DMA buffer has just 2 blocks */ src = KERNADDR(sc->rmem); if (ipr & EMU_IPR_ADCBUFFULL) { /* 2nd block */ src += EMU_REC_DMABLKSIZE; } dst = (char *)sc->rptr + sc->rcurrent; dmamem_sync(sc->rmem, BUS_DMASYNC_POSTREAD); memcpy(dst, src, EMU_REC_DMABLKSIZE); /* for next trans */ dmamem_sync(sc->rmem, BUS_DMASYNC_PREREAD); sc->rcurrent += EMU_REC_DMABLKSIZE; if (sc->rcurrent >= sc->roffset + sc->rblksize) { sc->rintr(sc->rintrarg); sc->roffset += sc->rblksize; if (sc->roffset >= sc->rlength) { sc->roffset = 0; sc->rcurrent = 0; } } handled = 1; } #if defined(EMUXKI_DEBUG) if (!handled) { char buf[1024]; snprintb(buf, sizeof(buf), "\20" "\x19""RATETRCHANGE" "\x18""FXDSP" "\x17""FORCEINT" "\x16""PCIERROR" "\x15""VOLINCR" "\x14""VOLDECR" "\x13""MUTE" "\x12""MICBUFFULL" "\x11""MICBUFHALFFULL" "\x10""ADCBUFFULL" "\x0f""ADCBUFHALFFULL" "\x0e""EFXBUFFULL" "\x0d""EFXBUFHALFFULL" "\x0c""GPSPDIFSTCHANGE" "\x0b""CDROMSTCHANGE" /* INTERVALTIMER */ "\x09""MIDITRANSBUFE" "\x08""MIDIRECVBUFE" "\x07""CHANNELLOOP" , ipr); DPRINTF("unexpected intr: %s\n", buf); /* for debugging (must not handle if !DEBUG) */ handled = 1; } #endif /* Reset interrupt bit */ emuxki_writeio_4(sc, EMU_IPR, ipr); mutex_spin_exit(&sc->sc_intr_lock); /* Interrupt handler must return !=0 if handled */ return handled; } static int emuxki_getdev(void *hdl, struct audio_device *dev) { struct emuxki_softc *sc = hdl; *dev = sc->sc_audv; return 0; } static int emuxki_set_port(void *hdl, mixer_ctrl_t *mctl) { struct emuxki_softc *sc = hdl; return sc->codecif->vtbl->mixer_set_port(sc->codecif, mctl); } static int emuxki_get_port(void *hdl, mixer_ctrl_t *mctl) { struct emuxki_softc *sc = hdl; return sc->codecif->vtbl->mixer_get_port(sc->codecif, mctl); } static int emuxki_query_devinfo(void *hdl, mixer_devinfo_t *minfo) { struct emuxki_softc *sc = hdl; return sc->codecif->vtbl->query_devinfo(sc->codecif, minfo); } static void * emuxki_allocm(void *hdl, int direction, size_t size) { struct emuxki_softc *sc = hdl; if (direction == AUMODE_PLAY) { if (sc->pmem) { panic("pmem already allocated\n"); return NULL; } sc->pmem = dmamem_alloc(sc, size); return KERNADDR(sc->pmem); } else { /* rmem is fixed size internal DMA buffer */ if (sc->rmem) { panic("rmem already allocated\n"); return NULL; } /* rmem fixed size */ sc->rmem = dmamem_alloc(sc, EMU_REC_DMASIZE); /* recording MI buffer is normal kmem, software trans. */ sc->rptr = kmem_alloc(size, KM_SLEEP); return sc->rptr; } } static void emuxki_freem(void *hdl, void *ptr, size_t size) { struct emuxki_softc *sc = hdl; if (sc->pmem && ptr == KERNADDR(sc->pmem)) { dmamem_free(sc->pmem); sc->pmem = NULL; } if (sc->rmem && ptr == sc->rptr) { dmamem_free(sc->rmem); sc->rmem = NULL; kmem_free(sc->rptr, size); sc->rptr = NULL; } } /* * blocksize rounding to EMU_PTESIZE. It is for easy to drive. */ static int emuxki_round_blocksize(void *hdl, int blksize, int mode, const audio_params_t* param) { /* * This is not necessary for recording, but symmetric for easy. * For recording buffer/block size requirements of hardware, * see EMU_RECBS_BUFSIZE_* */ if (blksize < EMU_PTESIZE) blksize = EMU_PTESIZE; return rounddown(blksize, EMU_PTESIZE); } static size_t emuxki_round_buffersize(void *hdl, int direction, size_t bsize) { /* This is not necessary for recording, but symmetric for easy */ if (bsize < EMU_MINPTE * EMU_PTESIZE) { bsize = EMU_MINPTE * EMU_PTESIZE; } else if (bsize > EMU_MAXPTE * EMU_PTESIZE) { bsize = EMU_MAXPTE * EMU_PTESIZE; } return roundup(bsize, EMU_PTESIZE); } static int emuxki_get_props(void *hdl) { return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE | AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX; } static int emuxki_trigger_output(void *hdl, void *start, void *end, int blksize, void (*intr)(void *), void *arg, const audio_params_t *params) { struct emuxki_softc *sc = hdl; int npage; uint32_t *kptb; bus_addr_t dpmem; int i; uint32_t hwstart; uint32_t hwend; if (sc->pmem == NULL) panic("pmem == NULL\n"); if (start != KERNADDR(sc->pmem)) panic("start != KERNADDR(sc->pmem)\n"); sc->pframesize = 4; /* channels * bit / 8 = 2*16/8=4 */ sc->pblksize = blksize; sc->plength = (char *)end - (char *)start; sc->poffset = 0; npage = roundup(sc->plength, EMU_PTESIZE); kptb = KERNADDR(sc->ptb); dpmem = DMAADDR(sc->pmem); for (i = 0; i < npage; i++) { kptb[i] = htole32(dpmem << 1); dpmem += EMU_PTESIZE; } dmamem_sync(sc->ptb, BUS_DMASYNC_PREWRITE); hwstart = 0; hwend = hwstart + sc->plength / sc->pframesize; sc->pintr = intr; sc->pintrarg = arg; dmamem_sync(sc->pmem, BUS_DMASYNC_PREWRITE); emuxki_play_start(sc, 0, hwstart, hwend); emuxki_play_start(sc, 1, hwstart, hwend); emuxki_timer_start(sc); return 0; } /* * Recording uses temporary buffer. Because it can use ADC_HALF/FULL * interrupts and this method doesn't conflict with playback. */ static int emuxki_trigger_input(void *hdl, void *start, void *end, int blksize, void (*intr)(void *), void *arg, const audio_params_t *params) { struct emuxki_softc *sc = hdl; if (sc->rmem == NULL) panic("rmem == NULL\n"); if (start != sc->rptr) panic("start != sc->rptr\n"); sc->rframesize = 4; /* channels * bit / 8 = 2*16/8=4 */ sc->rblksize = blksize; sc->rlength = (char *)end - (char *)start; sc->roffset = 0; sc->rcurrent = 0; sc->rintr = intr; sc->rintrarg = arg; /* * Memo: * recording source is selected by AC97 * AC97 input source routes to ADC by FX(DSP) * * Must keep following sequence order */ /* first, stop ADC */ emuxki_write(sc, 0, EMU_ADCCR, 0); emuxki_write(sc, 0, EMU_ADCBA, 0); emuxki_write(sc, 0, EMU_ADCBS, 0); dmamem_sync(sc->rmem, BUS_DMASYNC_PREREAD); /* ADC interrupt enable */ emuxki_writeio_4(sc, EMU_INTE, emuxki_readio_4(sc, EMU_INTE) | EMU_INTE_ADCBUFENABLE); /* ADC Enable */ /* stereo, 48kHz, enable */ emuxki_write(sc, 0, EMU_ADCCR, X1(ADCCR_LCHANENABLE) | X1(ADCCR_RCHANENABLE)); /* ADC buffer address */ emuxki_write(sc, 0, X1(ADCIDX), 0); emuxki_write(sc, 0, EMU_ADCBA, DMAADDR(sc->rmem)); /* ADC buffer size, to start */ emuxki_write(sc, 0, EMU_ADCBS, EMU_REC_BUFSIZE_RECBS); return 0; } static void emuxki_get_locks(void *hdl, kmutex_t **intr, kmutex_t **proc) { struct emuxki_softc *sc = hdl; *intr = &sc->sc_intr_lock; *proc = &sc->sc_lock; } /* * AC97 */ static int emuxki_ac97_init(struct emuxki_softc *sc) { sc->hostif.arg = sc; sc->hostif.attach = emuxki_ac97_attach; sc->hostif.read = emuxki_ac97_read; sc->hostif.write = emuxki_ac97_write; sc->hostif.reset = emuxki_ac97_reset; sc->hostif.flags = emuxki_ac97_flags; return ac97_attach(&sc->hostif, sc->sc_dev, &sc->sc_lock); } /* * AC97 callbacks */ static int emuxki_ac97_attach(void *hdl, struct ac97_codec_if *codecif) { struct emuxki_softc *sc = hdl; sc->codecif = codecif; return 0; } static int emuxki_ac97_read(void *hdl, uint8_t reg, uint16_t *val) { struct emuxki_softc *sc = hdl; mutex_spin_enter(&sc->sc_index_lock); emuxki_writeio_1(sc, EMU_AC97ADDR, reg); *val = emuxki_readio_2(sc, EMU_AC97DATA); mutex_spin_exit(&sc->sc_index_lock); return 0; } static int emuxki_ac97_write(void *hdl, uint8_t reg, uint16_t val) { struct emuxki_softc *sc = hdl; mutex_spin_enter(&sc->sc_index_lock); emuxki_writeio_1(sc, EMU_AC97ADDR, reg); emuxki_writeio_2(sc, EMU_AC97DATA, val); mutex_spin_exit(&sc->sc_index_lock); return 0; } static int emuxki_ac97_reset(void *hdl) { return 0; } static enum ac97_host_flags emuxki_ac97_flags(void *hdl) { return AC97_HOST_SWAPPED_CHANNELS; } MODULE(MODULE_CLASS_DRIVER, emuxki, "pci,audio"); #ifdef _MODULE #include "ioconf.c" #endif static int emuxki_modcmd(modcmd_t cmd, void *opaque) { int error = 0; switch (cmd) { case MODULE_CMD_INIT: #ifdef _MODULE error = config_init_component(cfdriver_ioconf_emuxki, cfattach_ioconf_emuxki, cfdata_ioconf_emuxki); #endif return error; case MODULE_CMD_FINI: #ifdef _MODULE error = config_fini_component(cfdriver_ioconf_emuxki, cfattach_ioconf_emuxki, cfdata_ioconf_emuxki); #endif return error; default: return ENOTTY; } }