diff --git a/sys/arm/allwinner/a10_codec.c b/sys/arm/allwinner/a10_codec.c index 12d389d24243..0a4ba7aa31b2 100644 --- a/sys/arm/allwinner/a10_codec.c +++ b/sys/arm/allwinner/a10_codec.c @@ -1,1205 +1,1205 @@ /*- * Copyright (c) 2014-2016 Jared D. McNeill * All rights reserved. * * 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 AUTHOR ``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 AUTHOR 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. */ /* * Allwinner A10/A20 and H3 Audio Codec */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi_dma_if.h" #include "mixer_if.h" struct a10codec_info; struct a10codec_config { /* mixer class */ struct kobj_class *mixer_class; /* toggle DAC/ADC mute */ void (*mute)(struct a10codec_info *, int, int); /* DRQ types */ u_int drqtype_codec; u_int drqtype_sdram; /* register map */ bus_size_t DPC, DAC_FIFOC, DAC_FIFOS, DAC_TXDATA, ADC_FIFOC, ADC_FIFOS, ADC_RXDATA, DAC_CNT, ADC_CNT; }; #define TX_TRIG_LEVEL 0xf #define RX_TRIG_LEVEL 0x7 #define DRQ_CLR_CNT 0x3 #define AC_DAC_DPC(_sc) ((_sc)->cfg->DPC) #define DAC_DPC_EN_DA 0x80000000 #define AC_DAC_FIFOC(_sc) ((_sc)->cfg->DAC_FIFOC) #define DAC_FIFOC_FS_SHIFT 29 #define DAC_FIFOC_FS_MASK (7U << DAC_FIFOC_FS_SHIFT) #define DAC_FS_48KHZ 0 #define DAC_FS_32KHZ 1 #define DAC_FS_24KHZ 2 #define DAC_FS_16KHZ 3 #define DAC_FS_12KHZ 4 #define DAC_FS_8KHZ 5 #define DAC_FS_192KHZ 6 #define DAC_FS_96KHZ 7 #define DAC_FIFOC_FIFO_MODE_SHIFT 24 #define DAC_FIFOC_FIFO_MODE_MASK (3U << DAC_FIFOC_FIFO_MODE_SHIFT) #define FIFO_MODE_24_31_8 0 #define FIFO_MODE_16_31_16 0 #define FIFO_MODE_16_15_0 1 #define DAC_FIFOC_DRQ_CLR_CNT_SHIFT 21 #define DAC_FIFOC_DRQ_CLR_CNT_MASK (3U << DAC_FIFOC_DRQ_CLR_CNT_SHIFT) #define DAC_FIFOC_TX_TRIG_LEVEL_SHIFT 8 #define DAC_FIFOC_TX_TRIG_LEVEL_MASK (0x7f << DAC_FIFOC_TX_TRIG_LEVEL_SHIFT) #define DAC_FIFOC_MONO_EN (1U << 6) #define DAC_FIFOC_TX_BITS (1U << 5) #define DAC_FIFOC_DRQ_EN (1U << 4) #define DAC_FIFOC_FIFO_FLUSH (1U << 0) #define AC_DAC_FIFOS(_sc) ((_sc)->cfg->DAC_FIFOS) #define AC_DAC_TXDATA(_sc) ((_sc)->cfg->DAC_TXDATA) #define AC_ADC_FIFOC(_sc) ((_sc)->cfg->ADC_FIFOC) #define ADC_FIFOC_FS_SHIFT 29 #define ADC_FIFOC_FS_MASK (7U << ADC_FIFOC_FS_SHIFT) #define ADC_FS_48KHZ 0 #define ADC_FIFOC_EN_AD (1U << 28) #define ADC_FIFOC_RX_FIFO_MODE (1U << 24) #define ADC_FIFOC_RX_TRIG_LEVEL_SHIFT 8 #define ADC_FIFOC_RX_TRIG_LEVEL_MASK (0x1f << ADC_FIFOC_RX_TRIG_LEVEL_SHIFT) #define ADC_FIFOC_MONO_EN (1U << 7) #define ADC_FIFOC_RX_BITS (1U << 6) #define ADC_FIFOC_DRQ_EN (1U << 4) #define ADC_FIFOC_FIFO_FLUSH (1U << 1) #define AC_ADC_FIFOS(_sc) ((_sc)->cfg->ADC_FIFOS) #define AC_ADC_RXDATA(_sc) ((_sc)->cfg->ADC_RXDATA) #define AC_DAC_CNT(_sc) ((_sc)->cfg->DAC_CNT) #define AC_ADC_CNT(_sc) ((_sc)->cfg->ADC_CNT) static uint32_t a10codec_fmt[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps a10codec_pcaps = { 8000, 192000, a10codec_fmt, 0 }; static struct pcmchan_caps a10codec_rcaps = { 8000, 48000, a10codec_fmt, 0 }; struct a10codec_info; struct a10codec_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct a10codec_info *parent; bus_dmamap_t dmamap; void *dmaaddr; bus_addr_t physaddr; bus_size_t fifo; device_t dmac; void *dmachan; int dir; int run; uint32_t pos; uint32_t format; uint32_t blocksize; uint32_t speed; }; struct a10codec_info { device_t dev; struct resource *res[2]; struct mtx *lock; bus_dma_tag_t dmat; unsigned dmasize; void *ih; struct a10codec_config *cfg; struct a10codec_chinfo play; struct a10codec_chinfo rec; }; static struct resource_spec a10codec_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define CODEC_ANALOG_READ(sc, reg) bus_read_4((sc)->res[1], (reg)) #define CODEC_ANALOG_WRITE(sc, reg, val) bus_write_4((sc)->res[1], (reg), (val)) #define CODEC_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) #define CODEC_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) /* * A10/A20 mixer interface */ #define A10_DAC_ACTL 0x10 #define A10_DACAREN (1U << 31) #define A10_DACALEN (1U << 30) #define A10_MIXEN (1U << 29) #define A10_DACPAS (1U << 8) #define A10_PAMUTE (1U << 6) #define A10_PAVOL_SHIFT 0 #define A10_PAVOL_MASK (0x3f << A10_PAVOL_SHIFT) #define A10_ADC_ACTL 0x28 #define A10_ADCREN (1U << 31) #define A10_ADCLEN (1U << 30) #define A10_PREG1EN (1U << 29) #define A10_PREG2EN (1U << 28) #define A10_VMICEN (1U << 27) #define A10_ADCG_SHIFT 20 #define A10_ADCG_MASK (7U << A10_ADCG_SHIFT) #define A10_ADCIS_SHIFT 17 #define A10_ADCIS_MASK (7U << A10_ADCIS_SHIFT) #define A10_ADC_IS_LINEIN 0 #define A10_ADC_IS_FMIN 1 #define A10_ADC_IS_MIC1 2 #define A10_ADC_IS_MIC2 3 #define A10_ADC_IS_MIC1_L_MIC2_R 4 #define A10_ADC_IS_MIC1_LR_MIC2_LR 5 #define A10_ADC_IS_OMIX 6 #define A10_ADC_IS_LINEIN_L_MIC1_R 7 #define A10_LNRDF (1U << 16) #define A10_LNPREG_SHIFT 13 #define A10_LNPREG_MASK (7U << A10_LNPREG_SHIFT) #define A10_PA_EN (1U << 4) #define A10_DDE (1U << 3) static int a10_mixer_init(struct snd_mixer *m) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; mix_setdevs(m, SOUND_MASK_VOLUME | SOUND_MASK_LINE | SOUND_MASK_RECLEV); mix_setrecdevs(m, SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC); /* Unmute input source to PA */ val = CODEC_READ(sc, A10_DAC_ACTL); val |= A10_PAMUTE; CODEC_WRITE(sc, A10_DAC_ACTL, val); /* Enable PA */ val = CODEC_READ(sc, A10_ADC_ACTL); val |= A10_PA_EN; CODEC_WRITE(sc, A10_ADC_ACTL, val); return (0); } static const struct a10_mixer { unsigned reg; unsigned mask; unsigned shift; } a10_mixers[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { A10_DAC_ACTL, A10_PAVOL_MASK, A10_PAVOL_SHIFT }, [SOUND_MIXER_LINE] = { A10_ADC_ACTL, A10_LNPREG_MASK, A10_LNPREG_SHIFT }, [SOUND_MIXER_RECLEV] = { A10_ADC_ACTL, A10_ADCG_MASK, A10_ADCG_SHIFT }, }; static int a10_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; unsigned nvol, max; max = a10_mixers[dev].mask >> a10_mixers[dev].shift; nvol = (left * max) / 100; val = CODEC_READ(sc, a10_mixers[dev].reg); val &= ~a10_mixers[dev].mask; val |= (nvol << a10_mixers[dev].shift); CODEC_WRITE(sc, a10_mixers[dev].reg, val); left = right = (left * 100) / max; return (left | (right << 8)); } static uint32_t a10_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; val = CODEC_READ(sc, A10_ADC_ACTL); switch (src) { case SOUND_MASK_LINE: /* line-in */ val &= ~A10_ADCIS_MASK; val |= (A10_ADC_IS_LINEIN << A10_ADCIS_SHIFT); break; case SOUND_MASK_MIC: /* MIC1 */ val &= ~A10_ADCIS_MASK; val |= (A10_ADC_IS_MIC1 << A10_ADCIS_SHIFT); break; case SOUND_MASK_LINE1: /* MIC2 */ val &= ~A10_ADCIS_MASK; val |= (A10_ADC_IS_MIC2 << A10_ADCIS_SHIFT); break; default: break; } CODEC_WRITE(sc, A10_ADC_ACTL, val); switch ((val & A10_ADCIS_MASK) >> A10_ADCIS_SHIFT) { case A10_ADC_IS_LINEIN: return (SOUND_MASK_LINE); case A10_ADC_IS_MIC1: return (SOUND_MASK_MIC); case A10_ADC_IS_MIC2: return (SOUND_MASK_LINE1); default: return (0); } } static void a10_mute(struct a10codec_info *sc, int mute, int dir) { uint32_t val; if (dir == PCMDIR_PLAY) { val = CODEC_READ(sc, A10_DAC_ACTL); if (mute) { /* Disable DAC analog l/r channels and output mixer */ val &= ~A10_DACAREN; val &= ~A10_DACALEN; val &= ~A10_DACPAS; } else { /* Enable DAC analog l/r channels and output mixer */ val |= A10_DACAREN; val |= A10_DACALEN; val |= A10_DACPAS; } CODEC_WRITE(sc, A10_DAC_ACTL, val); } else { val = CODEC_READ(sc, A10_ADC_ACTL); if (mute) { /* Disable ADC analog l/r channels, MIC1 preamp, * and VMIC pin voltage */ val &= ~A10_ADCREN; val &= ~A10_ADCLEN; val &= ~A10_PREG1EN; val &= ~A10_VMICEN; } else { /* Enable ADC analog l/r channels, MIC1 preamp, * and VMIC pin voltage */ val |= A10_ADCREN; val |= A10_ADCLEN; val |= A10_PREG1EN; val |= A10_VMICEN; } CODEC_WRITE(sc, A10_ADC_ACTL, val); } } static kobj_method_t a10_mixer_methods[] = { KOBJMETHOD(mixer_init, a10_mixer_init), KOBJMETHOD(mixer_set, a10_mixer_set), KOBJMETHOD(mixer_setrecsrc, a10_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(a10_mixer); /* * H3 mixer interface */ #define H3_PR_CFG 0x00 #define H3_AC_PR_RST (1 << 28) #define H3_AC_PR_RW (1 << 24) #define H3_AC_PR_ADDR_SHIFT 16 #define H3_AC_PR_ADDR_MASK (0x1f << H3_AC_PR_ADDR_SHIFT) #define H3_ACDA_PR_WDAT_SHIFT 8 #define H3_ACDA_PR_WDAT_MASK (0xff << H3_ACDA_PR_WDAT_SHIFT) #define H3_ACDA_PR_RDAT_SHIFT 0 #define H3_ACDA_PR_RDAT_MASK (0xff << H3_ACDA_PR_RDAT_SHIFT) #define H3_LOMIXSC 0x01 #define H3_LOMIXSC_LDAC (1 << 1) #define H3_ROMIXSC 0x02 #define H3_ROMIXSC_RDAC (1 << 1) #define H3_DAC_PA_SRC 0x03 #define H3_DACAREN (1 << 7) #define H3_DACALEN (1 << 6) #define H3_RMIXEN (1 << 5) #define H3_LMIXEN (1 << 4) #define H3_LINEIN_GCTR 0x05 #define H3_LINEING_SHIFT 4 #define H3_LINEING_MASK (0x7 << H3_LINEING_SHIFT) #define H3_MIC_GCTR 0x06 #define H3_MIC1_GAIN_SHIFT 4 #define H3_MIC1_GAIN_MASK (0x7 << H3_MIC1_GAIN_SHIFT) #define H3_MIC2_GAIN_SHIFT 0 #define H3_MIC2_GAIN_MASK (0x7 << H3_MIC2_GAIN_SHIFT) #define H3_PAEN_CTR 0x07 #define H3_LINEOUTEN (1 << 7) #define H3_LINEOUT_VOLC 0x09 #define H3_LINEOUTVOL_SHIFT 3 #define H3_LINEOUTVOL_MASK (0x1f << H3_LINEOUTVOL_SHIFT) #define H3_MIC2G_LINEOUT_CTR 0x0a #define H3_LINEOUT_LSEL (1 << 3) #define H3_LINEOUT_RSEL (1 << 2) #define H3_LADCMIXSC 0x0c #define H3_RADCMIXSC 0x0d #define H3_ADCMIXSC_MIC1 (1 << 6) #define H3_ADCMIXSC_MIC2 (1 << 5) #define H3_ADCMIXSC_LINEIN (1 << 2) #define H3_ADCMIXSC_OMIXER (3 << 0) #define H3_ADC_AP_EN 0x0f #define H3_ADCREN (1 << 7) #define H3_ADCLEN (1 << 6) #define H3_ADCG_SHIFT 0 #define H3_ADCG_MASK (0x7 << H3_ADCG_SHIFT) static u_int h3_pr_read(struct a10codec_info *sc, u_int addr) { uint32_t val; /* Read current value */ val = CODEC_ANALOG_READ(sc, H3_PR_CFG); /* De-assert reset */ val |= H3_AC_PR_RST; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Read mode */ val &= ~H3_AC_PR_RW; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Set address */ val &= ~H3_AC_PR_ADDR_MASK; val |= (addr << H3_AC_PR_ADDR_SHIFT); CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Read data */ return (CODEC_ANALOG_READ(sc , H3_PR_CFG) & H3_ACDA_PR_RDAT_MASK); } static void h3_pr_write(struct a10codec_info *sc, u_int addr, u_int data) { uint32_t val; /* Read current value */ val = CODEC_ANALOG_READ(sc, H3_PR_CFG); /* De-assert reset */ val |= H3_AC_PR_RST; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Set address */ val &= ~H3_AC_PR_ADDR_MASK; val |= (addr << H3_AC_PR_ADDR_SHIFT); CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Write data */ val &= ~H3_ACDA_PR_WDAT_MASK; val |= (data << H3_ACDA_PR_WDAT_SHIFT); CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Write mode */ val |= H3_AC_PR_RW; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); } static void h3_pr_set_clear(struct a10codec_info *sc, u_int addr, u_int set, u_int clr) { u_int old, new; old = h3_pr_read(sc, addr); new = set | (old & ~clr); h3_pr_write(sc, addr, new); } static int h3_mixer_init(struct snd_mixer *m) { int rid=1; pcell_t reg[2]; phandle_t analogref; struct a10codec_info *sc = mix_getdevinfo(m); if (OF_getencprop(ofw_bus_get_node(sc->dev), "allwinner,codec-analog-controls", &analogref, sizeof(analogref)) <= 0) { return (ENXIO); } if (OF_getencprop(OF_node_from_xref(analogref), "reg", reg, sizeof(reg)) <= 0) { return (ENXIO); } sc->res[1] = bus_alloc_resource(sc->dev, SYS_RES_MEMORY, &rid, reg[0], reg[0]+reg[1], reg[1], RF_ACTIVE ); if (sc->res[1] == NULL) { return (ENXIO); } mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_LINE1); mix_setrecdevs(m, SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_IMIX); pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); /* Right & Left LINEOUT enable */ h3_pr_set_clear(sc, H3_PAEN_CTR, H3_LINEOUTEN, 0); h3_pr_set_clear(sc, H3_MIC2G_LINEOUT_CTR, H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0); return (0); } static const struct h3_mixer { unsigned reg; unsigned mask; unsigned shift; } h3_mixers[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { H3_LINEOUT_VOLC, H3_LINEOUTVOL_MASK, H3_LINEOUTVOL_SHIFT }, [SOUND_MIXER_RECLEV] = { H3_ADC_AP_EN, H3_ADCG_MASK, H3_ADCG_SHIFT }, [SOUND_MIXER_LINE] = { H3_LINEIN_GCTR, H3_LINEING_MASK, H3_LINEING_SHIFT }, [SOUND_MIXER_MIC] = { H3_MIC_GCTR, H3_MIC1_GAIN_MASK, H3_MIC1_GAIN_SHIFT }, [SOUND_MIXER_LINE1] = { H3_MIC_GCTR, H3_MIC2_GAIN_MASK, H3_MIC2_GAIN_SHIFT }, }; static int h3_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct a10codec_info *sc = mix_getdevinfo(m); unsigned nvol, max; max = h3_mixers[dev].mask >> h3_mixers[dev].shift; nvol = (left * max) / 100; h3_pr_set_clear(sc, h3_mixers[dev].reg, nvol << h3_mixers[dev].shift, h3_mixers[dev].mask); left = right = (left * 100) / max; return (left | (right << 8)); } static uint32_t h3_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; val = 0; src &= (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_LINE1 | SOUND_MASK_IMIX); if ((src & SOUND_MASK_LINE) != 0) /* line-in */ val |= H3_ADCMIXSC_LINEIN; if ((src & SOUND_MASK_MIC) != 0) /* MIC1 */ val |= H3_ADCMIXSC_MIC1; if ((src & SOUND_MASK_LINE1) != 0) /* MIC2 */ val |= H3_ADCMIXSC_MIC2; if ((src & SOUND_MASK_IMIX) != 0) /* l/r output mixer */ val |= H3_ADCMIXSC_OMIXER; h3_pr_write(sc, H3_LADCMIXSC, val); h3_pr_write(sc, H3_RADCMIXSC, val); return (src); } static void h3_mute(struct a10codec_info *sc, int mute, int dir) { if (dir == PCMDIR_PLAY) { if (mute) { /* Mute DAC l/r channels to output mixer */ h3_pr_set_clear(sc, H3_LOMIXSC, 0, H3_LOMIXSC_LDAC); h3_pr_set_clear(sc, H3_ROMIXSC, 0, H3_ROMIXSC_RDAC); /* Disable DAC analog l/r channels and output mixer */ h3_pr_set_clear(sc, H3_DAC_PA_SRC, 0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN); } else { /* Enable DAC analog l/r channels and output mixer */ h3_pr_set_clear(sc, H3_DAC_PA_SRC, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0); /* Unmute DAC l/r channels to output mixer */ h3_pr_set_clear(sc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0); h3_pr_set_clear(sc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0); } } else { if (mute) { /* Disable ADC analog l/r channels */ h3_pr_set_clear(sc, H3_ADC_AP_EN, 0, H3_ADCREN | H3_ADCLEN); } else { /* Enable ADC analog l/r channels */ h3_pr_set_clear(sc, H3_ADC_AP_EN, H3_ADCREN | H3_ADCLEN, 0); } } } static kobj_method_t h3_mixer_methods[] = { KOBJMETHOD(mixer_init, h3_mixer_init), KOBJMETHOD(mixer_set, h3_mixer_set), KOBJMETHOD(mixer_setrecsrc, h3_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(h3_mixer); /* * Channel interface */ static void a10codec_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct a10codec_chinfo *ch = arg; if (error != 0) return; ch->physaddr = segs[0].ds_addr; } static void a10codec_transfer(struct a10codec_chinfo *ch) { bus_addr_t src, dst; int error; if (ch->dir == PCMDIR_PLAY) { src = ch->physaddr + ch->pos; dst = ch->fifo; } else { src = ch->fifo; dst = ch->physaddr + ch->pos; } error = SUNXI_DMA_TRANSFER(ch->dmac, ch->dmachan, src, dst, ch->blocksize); if (error) { ch->run = 0; device_printf(ch->parent->dev, "DMA transfer failed: %d\n", error); } } static void a10codec_dmaconfig(struct a10codec_chinfo *ch) { struct a10codec_info *sc = ch->parent; struct sunxi_dma_config conf; memset(&conf, 0, sizeof(conf)); conf.src_width = conf.dst_width = 16; conf.src_burst_len = conf.dst_burst_len = 4; if (ch->dir == PCMDIR_PLAY) { conf.dst_noincr = true; conf.src_drqtype = sc->cfg->drqtype_sdram; conf.dst_drqtype = sc->cfg->drqtype_codec; } else { conf.src_noincr = true; conf.src_drqtype = sc->cfg->drqtype_codec; conf.dst_drqtype = sc->cfg->drqtype_sdram; } SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf); } static void a10codec_dmaintr(void *priv) { struct a10codec_chinfo *ch = priv; unsigned bufsize; - bufsize = sndbuf_getsize(ch->buffer); + bufsize = ch->buffer->bufsize; ch->pos += ch->blocksize; if (ch->pos >= bufsize) ch->pos -= bufsize; if (ch->run) { chn_intr(ch->channel); a10codec_transfer(ch); } } static unsigned a10codec_fs(struct a10codec_chinfo *ch) { switch (ch->speed) { case 48000: return (DAC_FS_48KHZ); case 24000: return (DAC_FS_24KHZ); case 12000: return (DAC_FS_12KHZ); case 192000: return (DAC_FS_192KHZ); case 32000: return (DAC_FS_32KHZ); case 16000: return (DAC_FS_16KHZ); case 8000: return (DAC_FS_8KHZ); case 96000: return (DAC_FS_96KHZ); default: return (DAC_FS_48KHZ); } } static void a10codec_start(struct a10codec_chinfo *ch) { struct a10codec_info *sc = ch->parent; uint32_t val; ch->pos = 0; if (ch->dir == PCMDIR_PLAY) { /* Flush DAC FIFO */ CODEC_WRITE(sc, AC_DAC_FIFOC(sc), DAC_FIFOC_FIFO_FLUSH); /* Clear DAC FIFO status */ CODEC_WRITE(sc, AC_DAC_FIFOS(sc), CODEC_READ(sc, AC_DAC_FIFOS(sc))); /* Unmute output */ sc->cfg->mute(sc, 0, ch->dir); /* Configure DAC DMA channel */ a10codec_dmaconfig(ch); /* Configure DAC FIFO */ CODEC_WRITE(sc, AC_DAC_FIFOC(sc), (AFMT_CHANNEL(ch->format) == 1 ? DAC_FIFOC_MONO_EN : 0) | (a10codec_fs(ch) << DAC_FIFOC_FS_SHIFT) | (FIFO_MODE_16_15_0 << DAC_FIFOC_FIFO_MODE_SHIFT) | (DRQ_CLR_CNT << DAC_FIFOC_DRQ_CLR_CNT_SHIFT) | (TX_TRIG_LEVEL << DAC_FIFOC_TX_TRIG_LEVEL_SHIFT)); /* Enable DAC DRQ */ val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); val |= DAC_FIFOC_DRQ_EN; CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val); } else { /* Flush ADC FIFO */ CODEC_WRITE(sc, AC_ADC_FIFOC(sc), ADC_FIFOC_FIFO_FLUSH); /* Clear ADC FIFO status */ CODEC_WRITE(sc, AC_ADC_FIFOS(sc), CODEC_READ(sc, AC_ADC_FIFOS(sc))); /* Unmute input */ sc->cfg->mute(sc, 0, ch->dir); /* Configure ADC DMA channel */ a10codec_dmaconfig(ch); /* Configure ADC FIFO */ CODEC_WRITE(sc, AC_ADC_FIFOC(sc), ADC_FIFOC_EN_AD | ADC_FIFOC_RX_FIFO_MODE | (AFMT_CHANNEL(ch->format) == 1 ? ADC_FIFOC_MONO_EN : 0) | (a10codec_fs(ch) << ADC_FIFOC_FS_SHIFT) | (RX_TRIG_LEVEL << ADC_FIFOC_RX_TRIG_LEVEL_SHIFT)); /* Enable ADC DRQ */ val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); val |= ADC_FIFOC_DRQ_EN; CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val); } /* Start DMA transfer */ a10codec_transfer(ch); } static void a10codec_stop(struct a10codec_chinfo *ch) { struct a10codec_info *sc = ch->parent; /* Disable DMA channel */ SUNXI_DMA_HALT(ch->dmac, ch->dmachan); sc->cfg->mute(sc, 1, ch->dir); if (ch->dir == PCMDIR_PLAY) { /* Disable DAC DRQ */ CODEC_WRITE(sc, AC_DAC_FIFOC(sc), 0); } else { /* Disable ADC DRQ */ CODEC_WRITE(sc, AC_ADC_FIFOC(sc), 0); } } static void * a10codec_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct a10codec_info *sc = devinfo; struct a10codec_chinfo *ch = dir == PCMDIR_PLAY ? &sc->play : &sc->rec; phandle_t xref; pcell_t *cells; int ncells, error; error = ofw_bus_parse_xref_list_alloc(ofw_bus_get_node(sc->dev), "dmas", "#dma-cells", dir == PCMDIR_PLAY ? 1 : 0, &xref, &ncells, &cells); if (error != 0) { device_printf(sc->dev, "cannot parse 'dmas' property\n"); return (NULL); } OF_prop_free(cells); ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->fifo = rman_get_start(sc->res[0]) + (dir == PCMDIR_REC ? AC_ADC_RXDATA(sc) : AC_DAC_TXDATA(sc)); ch->dmac = OF_device_from_xref(xref); if (ch->dmac == NULL) { device_printf(sc->dev, "cannot find DMA controller\n"); device_printf(sc->dev, "xref = 0x%x\n", (u_int)xref); return (NULL); } ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, false, a10codec_dmaintr, ch); if (ch->dmachan == NULL) { device_printf(sc->dev, "cannot allocate DMA channel\n"); return (NULL); } error = bus_dmamem_alloc(sc->dmat, &ch->dmaaddr, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &ch->dmamap); if (error != 0) { device_printf(sc->dev, "cannot allocate channel buffer\n"); return (NULL); } error = bus_dmamap_load(sc->dmat, ch->dmamap, ch->dmaaddr, sc->dmasize, a10codec_dmamap_cb, ch, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->dev, "cannot load DMA map\n"); return (NULL); } memset(ch->dmaaddr, 0, sc->dmasize); if (sndbuf_setup(ch->buffer, ch->dmaaddr, sc->dmasize) != 0) { device_printf(sc->dev, "cannot setup sndbuf\n"); return (NULL); } return (ch); } static int a10codec_chan_free(kobj_t obj, void *data) { struct a10codec_chinfo *ch = data; struct a10codec_info *sc = ch->parent; SUNXI_DMA_FREE(ch->dmac, ch->dmachan); bus_dmamap_unload(sc->dmat, ch->dmamap); bus_dmamem_free(sc->dmat, ch->dmaaddr, ch->dmamap); return (0); } static int a10codec_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct a10codec_chinfo *ch = data; ch->format = format; return (0); } static uint32_t a10codec_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct a10codec_chinfo *ch = data; /* * The codec supports full duplex operation but both DAC and ADC * use the same source clock (PLL2). Limit the available speeds to * those supported by a 24576000 Hz input. */ switch (speed) { case 8000: case 12000: case 16000: case 24000: case 32000: case 48000: ch->speed = speed; break; case 96000: case 192000: /* 96 KHz / 192 KHz mode only supported for playback */ if (ch->dir == PCMDIR_PLAY) { ch->speed = speed; } else { ch->speed = 48000; } break; case 44100: ch->speed = 48000; break; case 22050: ch->speed = 24000; break; case 11025: ch->speed = 12000; break; default: ch->speed = 48000; break; } return (ch->speed); } static uint32_t a10codec_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct a10codec_chinfo *ch = data; ch->blocksize = blocksize & ~3; return (ch->blocksize); } static int a10codec_chan_trigger(kobj_t obj, void *data, int go) { struct a10codec_chinfo *ch = data; struct a10codec_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; a10codec_stop(ch); a10codec_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; a10codec_stop(ch); break; default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t a10codec_chan_getptr(kobj_t obj, void *data) { struct a10codec_chinfo *ch = data; return (ch->pos); } static struct pcmchan_caps * a10codec_chan_getcaps(kobj_t obj, void *data) { struct a10codec_chinfo *ch = data; if (ch->dir == PCMDIR_PLAY) { return (&a10codec_pcaps); } else { return (&a10codec_rcaps); } } static kobj_method_t a10codec_chan_methods[] = { KOBJMETHOD(channel_init, a10codec_chan_init), KOBJMETHOD(channel_free, a10codec_chan_free), KOBJMETHOD(channel_setformat, a10codec_chan_setformat), KOBJMETHOD(channel_setspeed, a10codec_chan_setspeed), KOBJMETHOD(channel_setblocksize, a10codec_chan_setblocksize), KOBJMETHOD(channel_trigger, a10codec_chan_trigger), KOBJMETHOD(channel_getptr, a10codec_chan_getptr), KOBJMETHOD(channel_getcaps, a10codec_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(a10codec_chan); /* * Device interface */ static const struct a10codec_config a10_config = { .mixer_class = &a10_mixer_class, .mute = a10_mute, .drqtype_codec = 19, .drqtype_sdram = 22, .DPC = 0x00, .DAC_FIFOC = 0x04, .DAC_FIFOS = 0x08, .DAC_TXDATA = 0x0c, .ADC_FIFOC = 0x1c, .ADC_FIFOS = 0x20, .ADC_RXDATA = 0x24, .DAC_CNT = 0x30, .ADC_CNT = 0x34, }; static const struct a10codec_config h3_config = { .mixer_class = &h3_mixer_class, .mute = h3_mute, .drqtype_codec = 15, .drqtype_sdram = 1, .DPC = 0x00, .DAC_FIFOC = 0x04, .DAC_FIFOS = 0x08, .DAC_TXDATA = 0x20, .ADC_FIFOC = 0x10, .ADC_FIFOS = 0x14, .ADC_RXDATA = 0x18, .DAC_CNT = 0x40, .ADC_CNT = 0x44, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-codec", (uintptr_t)&a10_config }, { "allwinner,sun7i-a20-codec", (uintptr_t)&a10_config }, { "allwinner,sun8i-h3-codec", (uintptr_t)&h3_config }, { NULL, 0 } }; static int a10codec_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner Audio Codec"); return (BUS_PROBE_DEFAULT); } static int a10codec_attach(device_t dev) { struct a10codec_info *sc; char status[SND_STATUSLEN]; struct gpiobus_pin *pa_pin; phandle_t node; clk_t clk_bus, clk_codec; hwreset_t rst; uint32_t val; int error; node = ofw_bus_get_node(dev); sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->cfg = (void *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; sc->dev = dev; sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10codec softc"); if (bus_alloc_resources(dev, a10codec_spec, sc->res)) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } sc->dmasize = 131072; error = bus_dma_tag_create( bus_get_dma_tag(dev), 4, sc->dmasize, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->dmasize, 1, /* maxsize, nsegs */ sc->dmasize, 0, /* maxsegsize, flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dmat); if (error != 0) { device_printf(dev, "cannot create DMA tag\n"); goto fail; } /* Get clocks */ if (clk_get_by_ofw_name(dev, 0, "apb", &clk_bus) != 0 && clk_get_by_ofw_name(dev, 0, "ahb", &clk_bus) != 0) { device_printf(dev, "cannot find bus clock\n"); goto fail; } if (clk_get_by_ofw_name(dev, 0, "codec", &clk_codec) != 0) { device_printf(dev, "cannot find codec clock\n"); goto fail; } /* Gating bus clock for codec */ if (clk_enable(clk_bus) != 0) { device_printf(dev, "cannot enable bus clock\n"); goto fail; } /* Activate audio codec clock. According to the A10 and A20 user * manuals, Audio_pll can be either 24.576MHz or 22.5792MHz. Most * audio sampling rates require an 24.576MHz input clock with the * exception of 44.1kHz, 22.05kHz, and 11.025kHz. Unfortunately, * both capture and playback use the same clock source so to * safely support independent full duplex operation, we use a fixed * 24.576MHz clock source and don't advertise native support for * the three sampling rates that require a 22.5792MHz input. */ error = clk_set_freq(clk_codec, 24576000, CLK_SET_ROUND_DOWN); if (error != 0) { device_printf(dev, "cannot set codec clock frequency\n"); goto fail; } /* Enable audio codec clock */ error = clk_enable(clk_codec); if (error != 0) { device_printf(dev, "cannot enable codec clock\n"); goto fail; } /* De-assert hwreset */ if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } /* Enable DAC */ val = CODEC_READ(sc, AC_DAC_DPC(sc)); val |= DAC_DPC_EN_DA; CODEC_WRITE(sc, AC_DAC_DPC(sc), val); if (mixer_init(dev, sc->cfg->mixer_class, sc)) { device_printf(dev, "mixer_init failed\n"); goto fail; } /* Unmute PA */ if (gpio_pin_get_by_ofw_property(dev, node, "allwinner,pa-gpios", &pa_pin) == 0) { error = gpio_pin_set_active(pa_pin, 1); if (error != 0) device_printf(dev, "failed to unmute PA\n"); } pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &a10codec_chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &a10codec_chan_class, sc); snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev)); if (pcm_register(dev, status)) { device_printf(dev, "pcm_register failed\n"); goto fail; } return (0); fail: bus_release_resources(dev, a10codec_spec, sc->res); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static device_method_t a10codec_pcm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10codec_probe), DEVMETHOD(device_attach, a10codec_attach), DEVMETHOD_END }; static driver_t a10codec_pcm_driver = { "pcm", a10codec_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(a10codec, simplebus, a10codec_pcm_driver, 0, 0); MODULE_DEPEND(a10codec, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(a10codec, 1); diff --git a/sys/arm/allwinner/aw_i2s.c b/sys/arm/allwinner/aw_i2s.c index 87dfb109363f..31c6d9854171 100644 --- a/sys/arm/allwinner/aw_i2s.c +++ b/sys/arm/allwinner/aw_i2s.c @@ -1,805 +1,805 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Oleksandr Tymoshenko * Copyright (c) 2018 Jared McNeill * * 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 AUTHOR ``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 AUTHOR 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #include "opt_snd.h" #include #include #include "audio_dai_if.h" #define FIFO_LEVEL 0x40 #define DA_CTL 0x00 #define DA_CTL_BCLK_OUT (1 << 18) /* sun8i */ #define DA_CLK_LRCK_OUT (1 << 17) /* sun8i */ #define DA_CTL_SDO_EN (1 << 8) #define DA_CTL_MS (1 << 5) /* sun4i */ #define DA_CTL_PCM (1 << 4) /* sun4i */ #define DA_CTL_MODE_SEL_MASK (3 << 4) /* sun8i */ #define DA_CTL_MODE_SEL_PCM (0 << 4) /* sun8i */ #define DA_CTL_MODE_SEL_LJ (1 << 4) /* sun8i */ #define DA_CTL_MODE_SEL_RJ (2 << 4) /* sun8i */ #define DA_CTL_TXEN (1 << 2) #define DA_CTL_RXEN (1 << 1) #define DA_CTL_GEN (1 << 0) #define DA_FAT0 0x04 #define DA_FAT0_LRCK_PERIOD_MASK (0x3ff << 8) /* sun8i */ #define DA_FAT0_LRCK_PERIOD(n) (((n) & 0x3fff) << 8) /* sun8i */ #define DA_FAT0_LRCP_MASK (1 << 7) #define DA_LRCP_NORMAL (0 << 7) #define DA_LRCP_INVERTED (1 << 7) #define DA_FAT0_BCP_MASK (1 << 6) #define DA_BCP_NORMAL (0 << 6) #define DA_BCP_INVERTED (1 << 6) #define DA_FAT0_SR __BITS(5,4) #define DA_FAT0_WSS __BITS(3,2) #define DA_FAT0_FMT_MASK (3 << 0) #define DA_FMT_I2S 0 #define DA_FMT_LJ 1 #define DA_FMT_RJ 2 #define DA_FAT1 0x08 #define DA_ISTA 0x0c #define DA_ISTA_TXUI_INT (1 << 6) #define DA_ISTA_TXEI_INT (1 << 4) #define DA_ISTA_RXAI_INT (1 << 0) #define DA_RXFIFO 0x10 #define DA_FCTL 0x14 #define DA_FCTL_HUB_EN (1 << 31) #define DA_FCTL_FTX (1 << 25) #define DA_FCTL_FRX (1 << 24) #define DA_FCTL_TXTL_MASK (0x7f << 12) #define DA_FCTL_TXTL(v) (((v) & 0x7f) << 12) #define DA_FCTL_TXIM (1 << 2) #define DA_FSTA 0x18 #define DA_FSTA_TXE_CNT(v) (((v) >> 16) & 0xff) #define DA_FSTA_RXA_CNT(v) ((v) & 0x3f) #define DA_INT 0x1c #define DA_INT_TX_DRQ (1 << 7) #define DA_INT_TXUI_EN (1 << 6) #define DA_INT_TXEI_EN (1 << 4) #define DA_INT_RX_DRQ (1 << 3) #define DA_INT_RXAI_EN (1 << 0) #define DA_TXFIFO 0x20 #define DA_CLKD 0x24 #define DA_CLKD_MCLKO_EN_SUN8I (1 << 8) #define DA_CLKD_MCLKO_EN_SUN4I (1 << 7) #define DA_CLKD_BCLKDIV_SUN8I(n) (((n) & 0xf) << 4) #define DA_CLKD_BCLKDIV_SUN8I_MASK (0xf << 4) #define DA_CLKD_BCLKDIV_SUN4I(n) (((n) & 7) << 4) #define DA_CLKD_BCLKDIV_SUN4I_MASK (7 << 4) #define DA_CLKD_BCLKDIV_8 3 #define DA_CLKD_BCLKDIV_16 5 #define DA_CLKD_MCLKDIV(n) (((n) & 0xff) << 0) #define DA_CLKD_MCLKDIV_MASK (0xf << 0) #define DA_CLKD_MCLKDIV_1 0 #define DA_TXCNT 0x28 #define DA_RXCNT 0x2c #define DA_CHCFG 0x30 /* sun8i */ #define DA_CHCFG_TX_SLOT_HIZ (1 << 9) #define DA_CHCFG_TXN_STATE (1 << 8) #define DA_CHCFG_RX_SLOT_NUM_MASK (7 << 4) #define DA_CHCFG_RX_SLOT_NUM(n) (((n) & 7) << 4) #define DA_CHCFG_TX_SLOT_NUM_MASK (7 << 0) #define DA_CHCFG_TX_SLOT_NUM(n) (((n) & 7) << 0) #define DA_CHSEL_OFFSET(n) (((n) & 3) << 12) /* sun8i */ #define DA_CHSEL_OFFSET_MASK (3 << 12) /* sun8i */ #define DA_CHSEL_EN(n) (((n) & 0xff) << 4) #define DA_CHSEL_EN_MASK (0xff << 4) #define DA_CHSEL_SEL(n) (((n) & 7) << 0) #define DA_CHSEL_SEL_MASK (7 << 0) #define AUDIO_BUFFER_SIZE 48000 * 4 #define AW_I2S_SAMPLE_RATE 48000 #define AW_I2S_CLK_RATE 24576000 enum sunxi_i2s_type { SUNXI_I2S_SUN4I, SUNXI_I2S_SUN8I, }; struct sunxi_i2s_config { const char *name; enum sunxi_i2s_type type; bus_size_t txchsel; bus_size_t txchmap; bus_size_t rxchsel; bus_size_t rxchmap; }; static const struct sunxi_i2s_config sun50i_a64_codec_config = { .name = "Audio Codec (digital part)", .type = SUNXI_I2S_SUN4I, .txchsel = 0x30, .txchmap = 0x34, .rxchsel = 0x38, .rxchmap = 0x3c, }; static const struct sunxi_i2s_config sun8i_h3_config = { .name = "I2S/PCM controller", .type = SUNXI_I2S_SUN8I, .txchsel = 0x34, .txchmap = 0x44, .rxchsel = 0x54, .rxchmap = 0x58, }; static const u_int sun4i_i2s_bclk_divmap[] = { [0] = 2, [1] = 4, [2] = 6, [3] = 8, [4] = 12, [5] = 16, }; static const u_int sun4i_i2s_mclk_divmap[] = { [0] = 1, [1] = 2, [2] = 4, [3] = 6, [4] = 8, [5] = 12, [6] = 16, [7] = 24, }; static const u_int sun8i_i2s_divmap[] = { [1] = 1, [2] = 2, [3] = 4, [4] = 6, [5] = 8, [6] = 12, [7] = 16, [8] = 24, [9] = 32, [10] = 48, [11] = 64, [12] = 96, [13] = 128, [14] = 176, [15] = 192, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun50i-a64-codec-i2s", (uintptr_t)&sun50i_a64_codec_config }, { "allwinner,sun8i-h3-i2s", (uintptr_t)&sun8i_h3_config }, { NULL, 0 } }; static struct resource_spec aw_i2s_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; struct aw_i2s_softc { device_t dev; struct resource *res[2]; struct mtx mtx; clk_t clk; struct sunxi_i2s_config *cfg; void * intrhand; /* pointers to playback/capture buffers */ uint32_t play_ptr; uint32_t rec_ptr; }; #define I2S_LOCK(sc) mtx_lock(&(sc)->mtx) #define I2S_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define I2S_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) #define I2S_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) #define I2S_TYPE(sc) ((sc)->cfg->type) static int aw_i2s_probe(device_t dev); static int aw_i2s_attach(device_t dev); static int aw_i2s_detach(device_t dev); static u_int sunxi_i2s_div_to_regval(const u_int *divmap, u_int divmaplen, u_int div) { u_int n; for (n = 0; n < divmaplen; n++) if (divmap[n] == div) return n; return -1; } static uint32_t sc_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps aw_i2s_caps = {AW_I2S_SAMPLE_RATE, AW_I2S_SAMPLE_RATE, sc_fmt, 0}; static int aw_i2s_init(struct aw_i2s_softc *sc) { uint32_t val; int error; error = clk_enable(sc->clk); if (error != 0) { device_printf(sc->dev, "cannot enable mod clock\n"); return (ENXIO); } /* Reset */ val = I2S_READ(sc, DA_CTL); val &= ~(DA_CTL_TXEN|DA_CTL_RXEN|DA_CTL_GEN); I2S_WRITE(sc, DA_CTL, val); val = I2S_READ(sc, DA_FCTL); val &= ~(DA_FCTL_FTX|DA_FCTL_FRX); val &= ~(DA_FCTL_TXTL_MASK); val |= DA_FCTL_TXTL(FIFO_LEVEL); I2S_WRITE(sc, DA_FCTL, val); I2S_WRITE(sc, DA_TXCNT, 0); I2S_WRITE(sc, DA_RXCNT, 0); /* Enable */ val = I2S_READ(sc, DA_CTL); val |= DA_CTL_GEN; I2S_WRITE(sc, DA_CTL, val); val |= DA_CTL_SDO_EN; I2S_WRITE(sc, DA_CTL, val); /* Setup channels */ I2S_WRITE(sc, sc->cfg->txchmap, 0x76543210); val = I2S_READ(sc, sc->cfg->txchsel); val &= ~DA_CHSEL_EN_MASK; val |= DA_CHSEL_EN(3); val &= ~DA_CHSEL_SEL_MASK; val |= DA_CHSEL_SEL(1); I2S_WRITE(sc, sc->cfg->txchsel, val); I2S_WRITE(sc, sc->cfg->rxchmap, 0x76543210); val = I2S_READ(sc, sc->cfg->rxchsel); val &= ~DA_CHSEL_EN_MASK; val |= DA_CHSEL_EN(3); val &= ~DA_CHSEL_SEL_MASK; val |= DA_CHSEL_SEL(1); I2S_WRITE(sc, sc->cfg->rxchsel, val); if (I2S_TYPE(sc) == SUNXI_I2S_SUN8I) { val = I2S_READ(sc, DA_CHCFG); val &= ~DA_CHCFG_TX_SLOT_NUM_MASK; val |= DA_CHCFG_TX_SLOT_NUM(1); val &= ~DA_CHCFG_RX_SLOT_NUM_MASK; val |= DA_CHCFG_RX_SLOT_NUM(1); I2S_WRITE(sc, DA_CHCFG, val); } return (0); } static int aw_i2s_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Allwinner I2S"); return (BUS_PROBE_DEFAULT); } static int aw_i2s_attach(device_t dev) { struct aw_i2s_softc *sc; int error; phandle_t node; hwreset_t rst; clk_t clk; sc = device_get_softc(dev); sc->dev = dev; sc->cfg = (void*)ofw_bus_search_compatible(dev, compat_data)->ocd_data; mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); if (bus_alloc_resources(dev, aw_i2s_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } error = clk_get_by_ofw_name(dev, 0, "mod", &sc->clk); if (error != 0) { device_printf(dev, "cannot get i2s_clk clock\n"); goto fail; } error = clk_get_by_ofw_name(dev, 0, "apb", &clk); if (error != 0) { device_printf(dev, "cannot get APB clock\n"); goto fail; } error = clk_enable(clk); if (error != 0) { device_printf(dev, "cannot enable APB clock\n"); goto fail; } if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } aw_i2s_init(sc); node = ofw_bus_get_node(dev); OF_device_register_xref(OF_xref_from_node(node), dev); return (0); fail: aw_i2s_detach(dev); return (error); } static int aw_i2s_detach(device_t dev) { struct aw_i2s_softc *i2s; i2s = device_get_softc(dev); if (i2s->clk) clk_release(i2s->clk); if (i2s->intrhand != NULL) bus_teardown_intr(i2s->dev, i2s->res[1], i2s->intrhand); bus_release_resources(dev, aw_i2s_spec, i2s->res); mtx_destroy(&i2s->mtx); return (0); } static int aw_i2s_dai_init(device_t dev, uint32_t format) { struct aw_i2s_softc *sc; int fmt, pol; uint32_t ctl, fat0, chsel; u_int offset; sc = device_get_softc(dev); fmt = AUDIO_DAI_FORMAT_FORMAT(format); pol = AUDIO_DAI_FORMAT_POLARITY(format); ctl = I2S_READ(sc, DA_CTL); fat0 = I2S_READ(sc, DA_FAT0); if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) { fat0 &= ~DA_FAT0_FMT_MASK; switch (fmt) { case AUDIO_DAI_FORMAT_I2S: fat0 |= DA_FMT_I2S; break; case AUDIO_DAI_FORMAT_RJ: fat0 |= DA_FMT_RJ; break; case AUDIO_DAI_FORMAT_LJ: fat0 |= DA_FMT_LJ; break; default: return EINVAL; } ctl &= ~DA_CTL_PCM; } else { ctl &= ~DA_CTL_MODE_SEL_MASK; switch (fmt) { case AUDIO_DAI_FORMAT_I2S: ctl |= DA_CTL_MODE_SEL_LJ; offset = 1; break; case AUDIO_DAI_FORMAT_LJ: ctl |= DA_CTL_MODE_SEL_LJ; offset = 0; break; case AUDIO_DAI_FORMAT_RJ: ctl |= DA_CTL_MODE_SEL_RJ; offset = 0; break; case AUDIO_DAI_FORMAT_DSPA: ctl |= DA_CTL_MODE_SEL_PCM; offset = 1; break; case AUDIO_DAI_FORMAT_DSPB: ctl |= DA_CTL_MODE_SEL_PCM; offset = 0; break; default: return EINVAL; } chsel = I2S_READ(sc, sc->cfg->txchsel); chsel &= ~DA_CHSEL_OFFSET_MASK; chsel |= DA_CHSEL_OFFSET(offset); I2S_WRITE(sc, sc->cfg->txchsel, chsel); chsel = I2S_READ(sc, sc->cfg->rxchsel); chsel &= ~DA_CHSEL_OFFSET_MASK; chsel |= DA_CHSEL_OFFSET(offset); I2S_WRITE(sc, sc->cfg->rxchsel, chsel); } fat0 &= ~(DA_FAT0_LRCP_MASK|DA_FAT0_BCP_MASK); if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) { if (AUDIO_DAI_POLARITY_INVERTED_BCLK(pol)) fat0 |= DA_BCP_INVERTED; if (AUDIO_DAI_POLARITY_INVERTED_FRAME(pol)) fat0 |= DA_LRCP_INVERTED; } else { if (AUDIO_DAI_POLARITY_INVERTED_BCLK(pol)) fat0 |= DA_BCP_INVERTED; if (!AUDIO_DAI_POLARITY_INVERTED_FRAME(pol)) fat0 |= DA_LRCP_INVERTED; fat0 &= ~DA_FAT0_LRCK_PERIOD_MASK; fat0 |= DA_FAT0_LRCK_PERIOD(32 - 1); } I2S_WRITE(sc, DA_CTL, ctl); I2S_WRITE(sc, DA_FAT0, fat0); return (0); } static int aw_i2s_dai_intr(device_t dev, struct snd_dbuf *play_buf, struct snd_dbuf *rec_buf) { struct aw_i2s_softc *sc; int ret = 0; uint32_t val, status; sc = device_get_softc(dev); I2S_LOCK(sc); status = I2S_READ(sc, DA_ISTA); /* Clear interrupts */ // device_printf(sc->dev, "status: %08x\n", status); I2S_WRITE(sc, DA_ISTA, status); if (status & DA_ISTA_TXEI_INT) { uint8_t *samples; uint32_t count, size, readyptr, written, empty; val = I2S_READ(sc, DA_FSTA); empty = DA_FSTA_TXE_CNT(val); count = sndbuf_getready(play_buf); - size = sndbuf_getsize(play_buf); + size = play_buf->bufsize; readyptr = sndbuf_getreadyptr(play_buf); - samples = (uint8_t*)sndbuf_getbuf(play_buf); + samples = play_buf->buf; written = 0; if (empty > count / 2) empty = count / 2; for (; empty > 0; empty--) { val = (samples[readyptr++ % size] << 16); val |= (samples[readyptr++ % size] << 24); written += 2; I2S_WRITE(sc, DA_TXFIFO, val); } sc->play_ptr += written; sc->play_ptr %= size; ret |= AUDIO_DAI_PLAY_INTR; } if (status & DA_ISTA_RXAI_INT) { uint8_t *samples; uint32_t count, size, freeptr, recorded, available; val = I2S_READ(sc, DA_FSTA); available = DA_FSTA_RXA_CNT(val); count = sndbuf_getfree(rec_buf); - size = sndbuf_getsize(rec_buf); + size = rec_buf->bufsize; freeptr = sndbuf_getfreeptr(rec_buf); - samples = (uint8_t*)sndbuf_getbuf(rec_buf); + samples = rec_buf->buf; recorded = 0; if (available > count / 2) available = count / 2; for (; available > 0; available--) { val = I2S_READ(sc, DA_RXFIFO); samples[freeptr++ % size] = (val >> 16) & 0xff; samples[freeptr++ % size] = (val >> 24) & 0xff; recorded += 2; } sc->rec_ptr += recorded; sc->rec_ptr %= size; ret |= AUDIO_DAI_REC_INTR; } I2S_UNLOCK(sc); return (ret); } static struct pcmchan_caps * aw_i2s_dai_get_caps(device_t dev) { return (&aw_i2s_caps); } static int aw_i2s_dai_trigger(device_t dev, int go, int pcm_dir) { struct aw_i2s_softc *sc = device_get_softc(dev); uint32_t val; if ((pcm_dir != PCMDIR_PLAY) && (pcm_dir != PCMDIR_REC)) return (EINVAL); switch (go) { case PCMTRIG_START: if (pcm_dir == PCMDIR_PLAY) { /* Flush FIFO */ val = I2S_READ(sc, DA_FCTL); I2S_WRITE(sc, DA_FCTL, val | DA_FCTL_FTX); I2S_WRITE(sc, DA_FCTL, val & ~DA_FCTL_FTX); /* Reset TX sample counter */ I2S_WRITE(sc, DA_TXCNT, 0); /* Enable TX block */ val = I2S_READ(sc, DA_CTL); I2S_WRITE(sc, DA_CTL, val | DA_CTL_TXEN); /* Enable TX underrun interrupt */ val = I2S_READ(sc, DA_INT); I2S_WRITE(sc, DA_INT, val | DA_INT_TXEI_EN); } if (pcm_dir == PCMDIR_REC) { /* Flush FIFO */ val = I2S_READ(sc, DA_FCTL); I2S_WRITE(sc, DA_FCTL, val | DA_FCTL_FRX); I2S_WRITE(sc, DA_FCTL, val & ~DA_FCTL_FRX); /* Reset RX sample counter */ I2S_WRITE(sc, DA_RXCNT, 0); /* Enable RX block */ val = I2S_READ(sc, DA_CTL); I2S_WRITE(sc, DA_CTL, val | DA_CTL_RXEN); /* Enable RX data available interrupt */ val = I2S_READ(sc, DA_INT); I2S_WRITE(sc, DA_INT, val | DA_INT_RXAI_EN); } break; case PCMTRIG_STOP: case PCMTRIG_ABORT: I2S_LOCK(sc); if (pcm_dir == PCMDIR_PLAY) { /* Disable TX block */ val = I2S_READ(sc, DA_CTL); I2S_WRITE(sc, DA_CTL, val & ~DA_CTL_TXEN); /* Enable TX underrun interrupt */ val = I2S_READ(sc, DA_INT); I2S_WRITE(sc, DA_INT, val & ~DA_INT_TXEI_EN); sc->play_ptr = 0; } else { /* Disable RX block */ val = I2S_READ(sc, DA_CTL); I2S_WRITE(sc, DA_CTL, val & ~DA_CTL_RXEN); /* Disable RX data available interrupt */ val = I2S_READ(sc, DA_INT); I2S_WRITE(sc, DA_INT, val & ~DA_INT_RXAI_EN); sc->rec_ptr = 0; } I2S_UNLOCK(sc); break; } return (0); } static uint32_t aw_i2s_dai_get_ptr(device_t dev, int pcm_dir) { struct aw_i2s_softc *sc; uint32_t ptr; sc = device_get_softc(dev); I2S_LOCK(sc); if (pcm_dir == PCMDIR_PLAY) ptr = sc->play_ptr; else ptr = sc->rec_ptr; I2S_UNLOCK(sc); return ptr; } static int aw_i2s_dai_setup_intr(device_t dev, driver_intr_t intr_handler, void *intr_arg) { struct aw_i2s_softc *sc = device_get_softc(dev); if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, intr_handler, intr_arg, &sc->intrhand)) { device_printf(dev, "cannot setup interrupt handler\n"); return (ENXIO); } return (0); } static uint32_t aw_i2s_dai_set_chanformat(device_t dev, uint32_t format) { return (0); } static int aw_i2s_dai_set_sysclk(device_t dev, unsigned int rate, int dai_dir) { struct aw_i2s_softc *sc; int bclk_val, mclk_val; uint32_t val; int error; sc = device_get_softc(dev); error = clk_set_freq(sc->clk, AW_I2S_CLK_RATE, CLK_SET_ROUND_DOWN); if (error != 0) { device_printf(sc->dev, "couldn't set mod clock rate to %u Hz: %d\n", AW_I2S_CLK_RATE, error); return error; } error = clk_enable(sc->clk); if (error != 0) { device_printf(sc->dev, "couldn't enable mod clock: %d\n", error); return error; } const u_int bclk_prate = I2S_TYPE(sc) == SUNXI_I2S_SUN4I ? rate : AW_I2S_CLK_RATE; const u_int bclk_div = bclk_prate / (2 * 32 * AW_I2S_SAMPLE_RATE); const u_int mclk_div = AW_I2S_CLK_RATE / rate; if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) { bclk_val = sunxi_i2s_div_to_regval(sun4i_i2s_bclk_divmap, nitems(sun4i_i2s_bclk_divmap), bclk_div); mclk_val = sunxi_i2s_div_to_regval(sun4i_i2s_mclk_divmap, nitems(sun4i_i2s_mclk_divmap), mclk_div); } else { bclk_val = sunxi_i2s_div_to_regval(sun8i_i2s_divmap, nitems(sun8i_i2s_divmap), bclk_div); mclk_val = sunxi_i2s_div_to_regval(sun8i_i2s_divmap, nitems(sun8i_i2s_divmap), mclk_div); } if (bclk_val == -1 || mclk_val == -1) { device_printf(sc->dev, "couldn't configure bclk/mclk dividers\n"); return EIO; } val = I2S_READ(sc, DA_CLKD); if (I2S_TYPE(sc) == SUNXI_I2S_SUN4I) { val |= DA_CLKD_MCLKO_EN_SUN4I; val &= ~DA_CLKD_BCLKDIV_SUN4I_MASK; val |= DA_CLKD_BCLKDIV_SUN4I(bclk_val); } else { val |= DA_CLKD_MCLKO_EN_SUN8I; val &= ~DA_CLKD_BCLKDIV_SUN8I_MASK; val |= DA_CLKD_BCLKDIV_SUN8I(bclk_val); } val &= ~DA_CLKD_MCLKDIV_MASK; val |= DA_CLKD_MCLKDIV(mclk_val); I2S_WRITE(sc, DA_CLKD, val); return (0); } static uint32_t aw_i2s_dai_set_chanspeed(device_t dev, uint32_t speed) { return (speed); } static device_method_t aw_i2s_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_i2s_probe), DEVMETHOD(device_attach, aw_i2s_attach), DEVMETHOD(device_detach, aw_i2s_detach), DEVMETHOD(audio_dai_init, aw_i2s_dai_init), DEVMETHOD(audio_dai_setup_intr, aw_i2s_dai_setup_intr), DEVMETHOD(audio_dai_set_sysclk, aw_i2s_dai_set_sysclk), DEVMETHOD(audio_dai_set_chanspeed, aw_i2s_dai_set_chanspeed), DEVMETHOD(audio_dai_set_chanformat, aw_i2s_dai_set_chanformat), DEVMETHOD(audio_dai_intr, aw_i2s_dai_intr), DEVMETHOD(audio_dai_get_caps, aw_i2s_dai_get_caps), DEVMETHOD(audio_dai_trigger, aw_i2s_dai_trigger), DEVMETHOD(audio_dai_get_ptr, aw_i2s_dai_get_ptr), DEVMETHOD_END }; static driver_t aw_i2s_driver = { "i2s", aw_i2s_methods, sizeof(struct aw_i2s_softc), }; DRIVER_MODULE(aw_i2s, simplebus, aw_i2s_driver, 0, 0); SIMPLEBUS_PNP_INFO(compat_data); diff --git a/sys/arm/broadcom/bcm2835/bcm2835_audio.c b/sys/arm/broadcom/bcm2835/bcm2835_audio.c index 2df6ac76124f..06bbc67bd7bd 100644 --- a/sys/arm/broadcom/bcm2835/bcm2835_audio.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_audio.c @@ -1,1010 +1,1011 @@ /*- * Copyright (c) 2015 Oleksandr Tymoshenko * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include "mixer_if.h" #include "interface/compat/vchi_bsd.h" #include "interface/vchi/vchi.h" #include "interface/vchiq_arm/vchiq.h" #include "vc_vchi_audioserv_defs.h" /* Audio destination */ #define DEST_AUTO 0 #define DEST_HEADPHONES 1 #define DEST_HDMI 2 /* Playback state */ #define PLAYBACK_IDLE 0 #define PLAYBACK_PLAYING 1 #define PLAYBACK_STOPPING 2 /* Worker thread state */ #define WORKER_RUNNING 0 #define WORKER_STOPPING 1 #define WORKER_STOPPED 2 /* * Worker thread flags, set to 1 in flags_pending * when driver requests one or another operation * from worker. Cleared to 0 once worker performs * the operations. */ #define AUDIO_PARAMS (1 << 0) #define AUDIO_PLAY (1 << 1) #define AUDIO_STOP (1 << 2) #define VCHIQ_AUDIO_PACKET_SIZE 4000 #define VCHIQ_AUDIO_BUFFER_SIZE 10*VCHIQ_AUDIO_PACKET_SIZE #define VCHIQ_AUDIO_MAX_VOLUME /* volume in terms of 0.01dB */ #define VCHIQ_AUDIO_VOLUME_MIN -10239 #define VCHIQ_AUDIO_VOLUME(db100) (uint32_t)(-((db100) << 8)/100) /* dB levels with 5% volume step */ static int db_levels[] = { VCHIQ_AUDIO_VOLUME_MIN, -4605, -3794, -3218, -2772, -2407, -2099, -1832, -1597, -1386, -1195, -1021, -861, -713, -575, -446, -325, -210, -102, 0, }; static uint32_t bcm2835_audio_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps bcm2835_audio_playcaps = {8000, 48000, bcm2835_audio_playfmt, 0}; struct bcm2835_audio_info; struct bcm2835_audio_chinfo { struct bcm2835_audio_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; uint32_t fmt, spd, blksz; /* Pointer to first unsubmitted sample */ uint32_t unsubmittedptr; /* * Number of bytes in "submitted but not played" * pseudo-buffer */ int available_space; int playback_state; uint64_t callbacks; uint64_t submitted_samples; uint64_t retrieved_samples; uint64_t underruns; int starved; }; struct bcm2835_audio_info { device_t dev; unsigned int bufsz; struct bcm2835_audio_chinfo pch; uint32_t dest, volume; struct intr_config_hook intr_hook; /* VCHI data */ VCHI_INSTANCE_T vchi_instance; VCHI_CONNECTION_T *vchi_connection; VCHI_SERVICE_HANDLE_T vchi_handle; struct mtx lock; struct cv worker_cv; uint32_t flags_pending; int verbose_trace; /* Worker thread state */ int worker_state; }; #define BCM2835_AUDIO_LOCK(sc) mtx_lock(&(sc)->lock) #define BCM2835_AUDIO_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED) #define BCM2835_AUDIO_UNLOCK(sc) mtx_unlock(&(sc)->lock) #define BCM2835_LOG_ERROR(sc,...) \ do { \ device_printf((sc)->dev, __VA_ARGS__); \ } while(0) #define BCM2835_LOG_INFO(sc,...) \ do { \ if (sc->verbose_trace > 0) \ device_printf((sc)->dev, __VA_ARGS__); \ } while(0) #define BCM2835_LOG_WARN(sc,...) \ do { \ if (sc->verbose_trace > 1) \ device_printf((sc)->dev, __VA_ARGS__); \ } while(0) #define BCM2835_LOG_TRACE(sc,...) \ do { \ if(sc->verbose_trace > 2) \ device_printf((sc)->dev, __VA_ARGS__); \ } while(0) static const char * dest_description(uint32_t dest) { switch (dest) { case DEST_AUTO: return "AUTO"; break; case DEST_HEADPHONES: return "HEADPHONES"; break; case DEST_HDMI: return "HDMI"; break; default: return "UNKNOWN"; break; } } static void bcm2835_worker_update_params(struct bcm2835_audio_info *sc) { BCM2835_AUDIO_LOCKED(sc); sc->flags_pending |= AUDIO_PARAMS; cv_signal(&sc->worker_cv); } static void bcm2835_worker_play_start(struct bcm2835_audio_info *sc) { BCM2835_AUDIO_LOCK(sc); sc->flags_pending &= ~(AUDIO_STOP); sc->flags_pending |= AUDIO_PLAY; cv_signal(&sc->worker_cv); BCM2835_AUDIO_UNLOCK(sc); } static void bcm2835_worker_play_stop(struct bcm2835_audio_info *sc) { BCM2835_AUDIO_LOCK(sc); sc->flags_pending &= ~(AUDIO_PLAY); sc->flags_pending |= AUDIO_STOP; cv_signal(&sc->worker_cv); BCM2835_AUDIO_UNLOCK(sc); } static void bcm2835_audio_callback(void *param, const VCHI_CALLBACK_REASON_T reason, void *msg_handle) { struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)param; int32_t status; uint32_t msg_len; VC_AUDIO_MSG_T m; if (reason != VCHI_CALLBACK_MSG_AVAILABLE) return; status = vchi_msg_dequeue(sc->vchi_handle, &m, sizeof m, &msg_len, VCHI_FLAGS_NONE); if (status != 0) return; if (m.type == VC_AUDIO_MSG_TYPE_RESULT) { if (m.u.result.success) { device_printf(sc->dev, "msg type %08x failed\n", m.type); } } else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) { struct bcm2835_audio_chinfo *ch = m.u.complete.cookie; int count = m.u.complete.count & 0xffff; int perr = (m.u.complete.count & (1U << 30)) != 0; ch->callbacks++; if (perr) ch->underruns++; BCM2835_AUDIO_LOCK(sc); if (ch->playback_state != PLAYBACK_IDLE) { /* Prevent LOR */ BCM2835_AUDIO_UNLOCK(sc); chn_intr(sc->pch.channel); BCM2835_AUDIO_LOCK(sc); } /* We should check again, state might have changed */ if (ch->playback_state != PLAYBACK_IDLE) { if (!perr) { if ((ch->available_space + count)> VCHIQ_AUDIO_BUFFER_SIZE) { device_printf(sc->dev, "inconsistent data in callback:\n"); device_printf(sc->dev, "available_space == %d, count = %d, perr=%d\n", ch->available_space, count, perr); device_printf(sc->dev, "retrieved_samples = %ju, submitted_samples = %ju\n", (uintmax_t)ch->retrieved_samples, (uintmax_t)ch->submitted_samples); } ch->available_space += count; ch->retrieved_samples += count; } if (perr || (ch->available_space >= VCHIQ_AUDIO_PACKET_SIZE)) cv_signal(&sc->worker_cv); } BCM2835_AUDIO_UNLOCK(sc); } else BCM2835_LOG_WARN(sc, "%s: unknown m.type: %d\n", __func__, m.type); } /* VCHIQ stuff */ static void bcm2835_audio_init(struct bcm2835_audio_info *sc) { int status; /* Initialize and create a VCHI connection */ status = vchi_initialise(&sc->vchi_instance); if (status != 0) { BCM2835_LOG_ERROR(sc, "vchi_initialise failed: %d\n", status); return; } status = vchi_connect(NULL, 0, sc->vchi_instance); if (status != 0) { BCM2835_LOG_ERROR(sc, "vchi_connect failed: %d\n", status); return; } SERVICE_CREATION_T params = { VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER), VC_AUDIO_SERVER_NAME, /* 4cc service code */ sc->vchi_connection, /* passed in fn pointers */ 0, /* rx fifo size */ 0, /* tx fifo size */ bcm2835_audio_callback, /* service callback */ sc, /* service callback parameter */ 1, 1, 0 /* want crc check on bulk transfers */ }; status = vchi_service_open(sc->vchi_instance, ¶ms, &sc->vchi_handle); if (status != 0) sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; } static void bcm2835_audio_release(struct bcm2835_audio_info *sc) { int success; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { success = vchi_service_close(sc->vchi_handle); if (success != 0) BCM2835_LOG_ERROR(sc, "vchi_service_close failed: %d\n", success); vchi_service_release(sc->vchi_handle); sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; } vchi_disconnect(sc->vchi_instance); } static void bcm2835_audio_reset_channel(struct bcm2835_audio_chinfo *ch) { ch->available_space = VCHIQ_AUDIO_BUFFER_SIZE; ch->unsubmittedptr = 0; sndbuf_reset(ch->buffer); } static void bcm2835_audio_start(struct bcm2835_audio_chinfo *ch) { VC_AUDIO_MSG_T m; int ret; struct bcm2835_audio_info *sc = ch->parent; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_START; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_stop(struct bcm2835_audio_chinfo *ch) { VC_AUDIO_MSG_T m; int ret; struct bcm2835_audio_info *sc = ch->parent; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_STOP; m.u.stop.draining = 0; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_open(struct bcm2835_audio_info *sc) { VC_AUDIO_MSG_T m; int ret; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_OPEN; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_update_controls(struct bcm2835_audio_info *sc, uint32_t volume, uint32_t dest) { VC_AUDIO_MSG_T m; int ret, db; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_CONTROL; m.u.control.dest = dest; if (volume > 99) volume = 99; db = db_levels[volume/5]; m.u.control.volume = VCHIQ_AUDIO_VOLUME(db); ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_update_params(struct bcm2835_audio_info *sc, uint32_t fmt, uint32_t speed) { VC_AUDIO_MSG_T m; int ret; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_CONFIG; m.u.config.channels = AFMT_CHANNEL(fmt); m.u.config.samplerate = speed; m.u.config.bps = AFMT_BIT(fmt); ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static bool bcm2835_audio_buffer_should_sleep(struct bcm2835_audio_chinfo *ch) { if (ch->playback_state != PLAYBACK_PLAYING) return (true); /* Not enough data */ if (sndbuf_getready(ch->buffer) < VCHIQ_AUDIO_PACKET_SIZE) { printf("starve\n"); ch->starved++; return (true); } /* Not enough free space */ if (ch->available_space < VCHIQ_AUDIO_PACKET_SIZE) { return (true); } return (false); } static void bcm2835_audio_write_samples(struct bcm2835_audio_chinfo *ch, void *buf, uint32_t count) { struct bcm2835_audio_info *sc = ch->parent; VC_AUDIO_MSG_T m; int ret; if (sc->vchi_handle == VCHIQ_SERVICE_HANDLE_INVALID) { return; } m.type = VC_AUDIO_MSG_TYPE_WRITE; m.u.write.count = count; m.u.write.max_packet = VCHIQ_AUDIO_PACKET_SIZE; m.u.write.callback = NULL; m.u.write.cookie = ch; m.u.write.silence = 0; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", __func__, ret); while (count > 0) { int bytes = MIN((int)m.u.write.max_packet, (int)count); ret = vchi_msg_queue(sc->vchi_handle, buf, bytes, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed: %d\n", __func__, ret); buf = (char *)buf + bytes; count -= bytes; } } static void bcm2835_audio_worker(void *data) { struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)data; struct bcm2835_audio_chinfo *ch = &sc->pch; uint32_t speed, format; uint32_t volume, dest; uint32_t flags; uint32_t count, size, readyptr; uint8_t *buf; ch->playback_state = PLAYBACK_IDLE; while (1) { if (sc->worker_state != WORKER_RUNNING) break; BCM2835_AUDIO_LOCK(sc); /* * wait until there are flags set or buffer is ready * to consume more samples */ while ((sc->flags_pending == 0) && bcm2835_audio_buffer_should_sleep(ch)) { cv_wait_sig(&sc->worker_cv, &sc->lock); } flags = sc->flags_pending; /* Clear pending flags */ sc->flags_pending = 0; BCM2835_AUDIO_UNLOCK(sc); /* Requested to change parameters */ if (flags & AUDIO_PARAMS) { BCM2835_AUDIO_LOCK(sc); speed = ch->spd; format = ch->fmt; volume = sc->volume; dest = sc->dest; BCM2835_AUDIO_UNLOCK(sc); if (ch->playback_state == PLAYBACK_IDLE) bcm2835_audio_update_params(sc, format, speed); bcm2835_audio_update_controls(sc, volume, dest); } /* Requested to stop playback */ if ((flags & AUDIO_STOP) && (ch->playback_state == PLAYBACK_PLAYING)) { bcm2835_audio_stop(ch); BCM2835_AUDIO_LOCK(sc); bcm2835_audio_reset_channel(&sc->pch); ch->playback_state = PLAYBACK_IDLE; BCM2835_AUDIO_UNLOCK(sc); continue; } /* Requested to start playback */ if ((flags & AUDIO_PLAY) && (ch->playback_state == PLAYBACK_IDLE)) { BCM2835_AUDIO_LOCK(sc); ch->playback_state = PLAYBACK_PLAYING; BCM2835_AUDIO_UNLOCK(sc); bcm2835_audio_start(ch); } if (ch->playback_state == PLAYBACK_IDLE) continue; if (sndbuf_getready(ch->buffer) == 0) continue; count = sndbuf_getready(ch->buffer); - size = sndbuf_getsize(ch->buffer); + size = ch->buffer->bufsize; readyptr = sndbuf_getreadyptr(ch->buffer); BCM2835_AUDIO_LOCK(sc); if (readyptr + count > size) count = size - readyptr; count = min(count, ch->available_space); count -= (count % VCHIQ_AUDIO_PACKET_SIZE); BCM2835_AUDIO_UNLOCK(sc); if (count < VCHIQ_AUDIO_PACKET_SIZE) continue; - buf = (uint8_t*)sndbuf_getbuf(ch->buffer) + readyptr; + buf = ch->buffer->buf + readyptr; bcm2835_audio_write_samples(ch, buf, count); BCM2835_AUDIO_LOCK(sc); - ch->unsubmittedptr = (ch->unsubmittedptr + count) % sndbuf_getsize(ch->buffer); + ch->unsubmittedptr = (ch->unsubmittedptr + count) % + ch->buffer->bufsize; ch->available_space -= count; ch->submitted_samples += count; KASSERT(ch->available_space >= 0, ("ch->available_space == %d\n", ch->available_space)); BCM2835_AUDIO_UNLOCK(sc); } BCM2835_AUDIO_LOCK(sc); sc->worker_state = WORKER_STOPPED; cv_signal(&sc->worker_cv); BCM2835_AUDIO_UNLOCK(sc); kproc_exit(0); } static void bcm2835_audio_create_worker(struct bcm2835_audio_info *sc) { struct proc *newp; sc->worker_state = WORKER_RUNNING; if (kproc_create(bcm2835_audio_worker, (void*)sc, &newp, 0, 0, "bcm2835_audio_worker") != 0) { BCM2835_LOG_ERROR(sc, "failed to create bcm2835_audio_worker\n"); } } /* -------------------------------------------------------------------- */ /* channel interface for VCHI audio */ static void * bcmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct bcm2835_audio_info *sc = devinfo; struct bcm2835_audio_chinfo *ch = &sc->pch; void *buffer; if (dir == PCMDIR_REC) return NULL; ch->parent = sc; ch->channel = c; ch->buffer = b; /* default values */ ch->spd = 44100; ch->fmt = SND_FORMAT(AFMT_S16_LE, 2, 0); ch->blksz = VCHIQ_AUDIO_PACKET_SIZE; buffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO); if (sndbuf_setup(ch->buffer, buffer, sc->bufsz) != 0) { device_printf(sc->dev, "sndbuf_setup failed\n"); free(buffer, M_DEVBUF); return NULL; } BCM2835_AUDIO_LOCK(sc); bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); return ch; } static int bcmchan_free(kobj_t obj, void *data) { struct bcm2835_audio_chinfo *ch = data; void *buffer; - buffer = sndbuf_getbuf(ch->buffer); + buffer = ch->buffer->buf; if (buffer) free(buffer, M_DEVBUF); return (0); } static int bcmchan_setformat(kobj_t obj, void *data, uint32_t format) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; BCM2835_AUDIO_LOCK(sc); ch->fmt = format; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); return 0; } static uint32_t bcmchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; BCM2835_AUDIO_LOCK(sc); ch->spd = speed; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); return ch->spd; } static uint32_t bcmchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct bcm2835_audio_chinfo *ch = data; return ch->blksz; } static int bcmchan_trigger(kobj_t obj, void *data, int go) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return (0); switch (go) { case PCMTRIG_START: /* kickstart data flow */ chn_intr(sc->pch.channel); ch->submitted_samples = 0; ch->retrieved_samples = 0; bcm2835_worker_play_start(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: bcm2835_worker_play_stop(sc); break; default: break; } return 0; } static uint32_t bcmchan_getptr(kobj_t obj, void *data) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; uint32_t ret; BCM2835_AUDIO_LOCK(sc); ret = ch->unsubmittedptr; BCM2835_AUDIO_UNLOCK(sc); return ret; } static struct pcmchan_caps * bcmchan_getcaps(kobj_t obj, void *data) { return &bcm2835_audio_playcaps; } static kobj_method_t bcmchan_methods[] = { KOBJMETHOD(channel_init, bcmchan_init), KOBJMETHOD(channel_free, bcmchan_free), KOBJMETHOD(channel_setformat, bcmchan_setformat), KOBJMETHOD(channel_setspeed, bcmchan_setspeed), KOBJMETHOD(channel_setblocksize, bcmchan_setblocksize), KOBJMETHOD(channel_trigger, bcmchan_trigger), KOBJMETHOD(channel_getptr, bcmchan_getptr), KOBJMETHOD(channel_getcaps, bcmchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(bcmchan); /************************************************************/ static int bcmmix_init(struct snd_mixer *m) { mix_setdevs(m, SOUND_MASK_VOLUME); return (0); } static int bcmmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct bcm2835_audio_info *sc = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_VOLUME: BCM2835_AUDIO_LOCK(sc); sc->volume = left; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); break; default: break; } return left | (left << 8); } static kobj_method_t bcmmixer_methods[] = { KOBJMETHOD(mixer_init, bcmmix_init), KOBJMETHOD(mixer_set, bcmmix_set), KOBJMETHOD_END }; MIXER_DECLARE(bcmmixer); static int sysctl_bcm2835_audio_dest(SYSCTL_HANDLER_ARGS) { struct bcm2835_audio_info *sc = arg1; int val; int err; val = sc->dest; err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); if ((val < 0) || (val > 2)) return (EINVAL); BCM2835_AUDIO_LOCK(sc); sc->dest = val; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); if (bootverbose) device_printf(sc->dev, "destination set to %s\n", dest_description(val)); return (0); } static void vchi_audio_sysctl_init(struct bcm2835_audio_info *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node; struct sysctl_oid_list *tree; /* * Add system sysctl tree/handlers. */ ctx = device_get_sysctl_ctx(sc->dev); tree_node = device_get_sysctl_tree(sc->dev); tree = SYSCTL_CHILDREN(tree_node); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "dest", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_NEEDGIANT, sc, sizeof(*sc), sysctl_bcm2835_audio_dest, "IU", "audio destination, " "0 - auto, 1 - headphones, 2 - HDMI"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "callbacks", CTLFLAG_RD, &sc->pch.callbacks, "callbacks total"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "submitted", CTLFLAG_RD, &sc->pch.submitted_samples, "last play submitted samples"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "retrieved", CTLFLAG_RD, &sc->pch.retrieved_samples, "last play retrieved samples"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "underruns", CTLFLAG_RD, &sc->pch.underruns, "callback underruns"); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "freebuffer", CTLFLAG_RD, &sc->pch.available_space, sc->pch.available_space, "callbacks total"); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "starved", CTLFLAG_RD, &sc->pch.starved, sc->pch.starved, "number of starved conditions"); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "trace", CTLFLAG_RW, &sc->verbose_trace, sc->verbose_trace, "enable tracing of transfers"); } static void bcm2835_audio_identify(driver_t *driver, device_t parent) { BUS_ADD_CHILD(parent, 0, "pcm", 0); } static int bcm2835_audio_probe(device_t dev) { device_set_desc(dev, "VCHIQ audio"); return (BUS_PROBE_DEFAULT); } static void bcm2835_audio_delayed_init(void *xsc) { struct bcm2835_audio_info *sc; char status[SND_STATUSLEN]; sc = xsc; config_intrhook_disestablish(&sc->intr_hook); bcm2835_audio_init(sc); bcm2835_audio_open(sc); sc->volume = 75; sc->dest = DEST_AUTO; sc->verbose_trace = 0; if (mixer_init(sc->dev, &bcmmixer_class, sc)) { device_printf(sc->dev, "mixer_init failed\n"); goto no; } pcm_init(sc->dev, sc); pcm_addchan(sc->dev, PCMDIR_PLAY, &bcmchan_class, sc); snprintf(status, SND_STATUSLEN, "at VCHIQ"); if (pcm_register(sc->dev, status)) { device_printf(sc->dev, "pcm_register failed\n"); goto no; } bcm2835_audio_reset_channel(&sc->pch); bcm2835_audio_create_worker(sc); vchi_audio_sysctl_init(sc); no: ; } static int bcm2835_audio_attach(device_t dev) { struct bcm2835_audio_info *sc; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->bufsz = VCHIQ_AUDIO_BUFFER_SIZE; mtx_init(&sc->lock, device_get_nameunit(dev), "bcm_audio_lock", MTX_DEF); cv_init(&sc->worker_cv, "worker_cv"); sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; /* * We need interrupts enabled for VCHI to work properly, * so delay initialization until it happens. */ sc->intr_hook.ich_func = bcm2835_audio_delayed_init; sc->intr_hook.ich_arg = sc; if (config_intrhook_establish(&sc->intr_hook) != 0) goto no; return 0; no: return ENXIO; } static int bcm2835_audio_detach(device_t dev) { int r; struct bcm2835_audio_info *sc; sc = pcm_getdevinfo(dev); /* Stop worker thread */ BCM2835_AUDIO_LOCK(sc); sc->worker_state = WORKER_STOPPING; cv_signal(&sc->worker_cv); /* Wait for thread to exit */ while (sc->worker_state != WORKER_STOPPED) cv_wait_sig(&sc->worker_cv, &sc->lock); BCM2835_AUDIO_UNLOCK(sc); r = pcm_unregister(dev); if (r) return r; mtx_destroy(&sc->lock); cv_destroy(&sc->worker_cv); bcm2835_audio_release(sc); free(sc, M_DEVBUF); return 0; } static device_method_t bcm2835_audio_methods[] = { /* Device interface */ DEVMETHOD(device_identify, bcm2835_audio_identify), DEVMETHOD(device_probe, bcm2835_audio_probe), DEVMETHOD(device_attach, bcm2835_audio_attach), DEVMETHOD(device_detach, bcm2835_audio_detach), { 0, 0 } }; static driver_t bcm2835_audio_driver = { "pcm", bcm2835_audio_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(bcm2835_audio, vchiq, bcm2835_audio_driver, 0, 0); MODULE_DEPEND(bcm2835_audio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(bcm2835_audio, vchiq, 1, 1, 1); MODULE_VERSION(bcm2835_audio, 1); diff --git a/sys/arm/freescale/imx/imx6_ssi.c b/sys/arm/freescale/imx/imx6_ssi.c index cb77f1454e63..5a0e671f15a2 100644 --- a/sys/arm/freescale/imx/imx6_ssi.c +++ b/sys/arm/freescale/imx/imx6_ssi.c @@ -1,855 +1,855 @@ /*- * Copyright (c) 2015 Ruslan Bukin * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * i.MX6 Synchronous Serial Interface (SSI) * * Chapter 61, i.MX 6Dual/6Quad Applications Processor Reference Manual, * Rev. 1, 04/2013 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define READ4(_sc, _reg) \ bus_space_read_4(_sc->bst, _sc->bsh, _reg) #define WRITE4(_sc, _reg, _val) \ bus_space_write_4(_sc->bst, _sc->bsh, _reg, _val) #define SSI_NCHANNELS 1 #define DMAS_TOTAL 8 /* i.MX6 SSI registers */ #define SSI_STX0 0x00 /* Transmit Data Register n */ #define SSI_STX1 0x04 /* Transmit Data Register n */ #define SSI_SRX0 0x08 /* Receive Data Register n */ #define SSI_SRX1 0x0C /* Receive Data Register n */ #define SSI_SCR 0x10 /* Control Register */ #define SCR_I2S_MODE_S 5 /* I2S Mode Select. */ #define SCR_I2S_MODE_M 0x3 #define SCR_SYN (1 << 4) #define SCR_NET (1 << 3) /* Network mode */ #define SCR_RE (1 << 2) /* Receive Enable. */ #define SCR_TE (1 << 1) /* Transmit Enable. */ #define SCR_SSIEN (1 << 0) /* SSI Enable */ #define SSI_SISR 0x14 /* Interrupt Status Register */ #define SSI_SIER 0x18 /* Interrupt Enable Register */ #define SIER_RDMAE (1 << 22) /* Receive DMA Enable. */ #define SIER_RIE (1 << 21) /* Receive Interrupt Enable. */ #define SIER_TDMAE (1 << 20) /* Transmit DMA Enable. */ #define SIER_TIE (1 << 19) /* Transmit Interrupt Enable. */ #define SIER_TDE0IE (1 << 12) /* Transmit Data Register Empty 0. */ #define SIER_TUE0IE (1 << 8) /* Transmitter Underrun Error 0. */ #define SIER_TFE0IE (1 << 0) /* Transmit FIFO Empty 0 IE. */ #define SSI_STCR 0x1C /* Transmit Configuration Register */ #define STCR_TXBIT0 (1 << 9) /* Transmit Bit 0 shift MSB/LSB */ #define STCR_TFEN1 (1 << 8) /* Transmit FIFO Enable 1. */ #define STCR_TFEN0 (1 << 7) /* Transmit FIFO Enable 0. */ #define STCR_TFDIR (1 << 6) /* Transmit Frame Direction. */ #define STCR_TXDIR (1 << 5) /* Transmit Clock Direction. */ #define STCR_TSHFD (1 << 4) /* Transmit Shift Direction. */ #define STCR_TSCKP (1 << 3) /* Transmit Clock Polarity. */ #define STCR_TFSI (1 << 2) /* Transmit Frame Sync Invert. */ #define STCR_TFSL (1 << 1) /* Transmit Frame Sync Length. */ #define STCR_TEFS (1 << 0) /* Transmit Early Frame Sync. */ #define SSI_SRCR 0x20 /* Receive Configuration Register */ #define SSI_STCCR 0x24 /* Transmit Clock Control Register */ #define STCCR_DIV2 (1 << 18) /* Divide By 2. */ #define STCCR_PSR (1 << 17) /* Divide clock by 8. */ #define WL3_WL0_S 13 #define WL3_WL0_M 0xf #define DC4_DC0_S 8 #define DC4_DC0_M 0x1f #define PM7_PM0_S 0 #define PM7_PM0_M 0xff #define SSI_SRCCR 0x28 /* Receive Clock Control Register */ #define SSI_SFCSR 0x2C /* FIFO Control/Status Register */ #define SFCSR_RFWM1_S 20 /* Receive FIFO Empty WaterMark 1 */ #define SFCSR_RFWM1_M 0xf #define SFCSR_TFWM1_S 16 /* Transmit FIFO Empty WaterMark 1 */ #define SFCSR_TFWM1_M 0xf #define SFCSR_RFWM0_S 4 /* Receive FIFO Empty WaterMark 0 */ #define SFCSR_RFWM0_M 0xf #define SFCSR_TFWM0_S 0 /* Transmit FIFO Empty WaterMark 0 */ #define SFCSR_TFWM0_M 0xf #define SSI_SACNT 0x38 /* AC97 Control Register */ #define SSI_SACADD 0x3C /* AC97 Command Address Register */ #define SSI_SACDAT 0x40 /* AC97 Command Data Register */ #define SSI_SATAG 0x44 /* AC97 Tag Register */ #define SSI_STMSK 0x48 /* Transmit Time Slot Mask Register */ #define SSI_SRMSK 0x4C /* Receive Time Slot Mask Register */ #define SSI_SACCST 0x50 /* AC97 Channel Status Register */ #define SSI_SACCEN 0x54 /* AC97 Channel Enable Register */ #define SSI_SACCDIS 0x58 /* AC97 Channel Disable Register */ static MALLOC_DEFINE(M_SSI, "ssi", "ssi audio"); uint32_t ssi_dma_intr(void *arg, int chn); struct ssi_rate { uint32_t speed; uint32_t mfi; /* PLL4 Multiplication Factor Integer */ uint32_t mfn; /* PLL4 Multiplication Factor Numerator */ uint32_t mfd; /* PLL4 Multiplication Factor Denominator */ /* More dividers to configure can be added here */ }; static struct ssi_rate rate_map[] = { { 192000, 49, 152, 1000 }, /* PLL4 49.152 Mhz */ /* TODO: add more frequences */ { 0, 0 }, }; /* * i.MX6 example bit clock formula * * BCLK = 2 channels * 192000 hz * 24 bit = 9216000 hz = * (24000000 * (49 + 152/1000.0) / 4 / 4 / 2 / 2 / 2 / 1 / 1) * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ * | | | | | | | | | | | * Fref ------/ | | | | | | | | | | * PLL4 div select -/ | | | | | | | | | * PLL4 num --------------/ | | | | | | | | * PLL4 denom -------------------/ | | | | | | | * PLL4 post div ---------------------/ | | | | | | * CCM ssi pre div (CCM_CS1CDR) ----------/ | | | | | * CCM ssi post div (CCM_CS1CDR) -------------/ | | | | * SSI PM7_PM0_S ---------------------------------/ | | | * SSI Fixed divider ---------------------------------/ | | * SSI DIV2 ----------------------------------------------/ | * SSI PSR (prescaler /1 or /8) ------------------------------/ * * MCLK (Master clock) depends on DAC, usually BCLK * 4 */ struct sc_info { struct resource *res[2]; bus_space_tag_t bst; bus_space_handle_t bsh; device_t dev; struct mtx *lock; void *ih; int pos; int dma_size; bus_dma_tag_t dma_tag; bus_dmamap_t dma_map; bus_addr_t buf_base_phys; uint32_t *buf_base; struct sdma_conf *conf; struct ssi_rate *sr; struct sdma_softc *sdma_sc; uint32_t sdma_ev_rx; uint32_t sdma_ev_tx; int sdma_channel; }; /* Channel registers */ struct sc_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_pcminfo *parent; /* Channel information */ uint32_t dir; uint32_t format; /* Flags */ uint32_t run; }; /* PCM device private data */ struct sc_pcminfo { device_t dev; uint32_t (*ih)(struct sc_pcminfo *scp); uint32_t chnum; struct sc_chinfo chan[SSI_NCHANNELS]; struct sc_info *sc; }; static struct resource_spec ssi_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; static int setup_dma(struct sc_pcminfo *scp); static void setup_ssi(struct sc_info *); static void ssi_configure_clock(struct sc_info *); /* * Mixer interface. */ static int ssimixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return -1; mask = SOUND_MASK_PCM; mask |= SOUND_MASK_VOLUME; snd_mtxlock(sc->lock); pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, mask); snd_mtxunlock(sc->lock); return (0); } static int ssimixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_pcminfo *scp; scp = mix_getdevinfo(m); /* Here we can configure hardware volume on our DAC */ #if 1 device_printf(scp->dev, "ssimixer_set() %d %d\n", left, right); #endif return (0); } static kobj_method_t ssimixer_methods[] = { KOBJMETHOD(mixer_init, ssimixer_init), KOBJMETHOD(mixer_set, ssimixer_set), KOBJMETHOD_END }; MIXER_DECLARE(ssimixer); /* * Channel interface. */ static void * ssichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; scp = (struct sc_pcminfo *)devinfo; sc = scp->sc; snd_mtxlock(sc->lock); ch = &scp->chan[0]; ch->dir = dir; ch->run = 0; ch->buffer = b; ch->channel = c; ch->parent = scp; snd_mtxunlock(sc->lock); if (sndbuf_setup(ch->buffer, sc->buf_base, sc->dma_size) != 0) { device_printf(scp->dev, "Can't setup sndbuf.\n"); return NULL; } return ch; } static int ssichan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_pcminfo *scp = ch->parent; struct sc_info *sc = scp->sc; #if 0 device_printf(scp->dev, "ssichan_free()\n"); #endif snd_mtxlock(sc->lock); /* TODO: free channel buffer */ snd_mtxunlock(sc->lock); return (0); } static int ssichan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch = data; ch->format = format; return (0); } static uint32_t ssichan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct ssi_rate *sr; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; sr = NULL; /* First look for equal frequency. */ for (i = 0; rate_map[i].speed != 0; i++) { if (rate_map[i].speed == speed) sr = &rate_map[i]; } /* If no match, just find nearest. */ if (sr == NULL) { for (i = 0; rate_map[i].speed != 0; i++) { sr = &rate_map[i]; threshold = sr->speed + ((rate_map[i + 1].speed != 0) ? ((rate_map[i + 1].speed - sr->speed) >> 1) : 0); if (speed < threshold) break; } } sc->sr = sr; ssi_configure_clock(sc); return (sr->speed); } static void ssi_configure_clock(struct sc_info *sc) { struct ssi_rate *sr; sr = sc->sr; pll4_configure_output(sr->mfi, sr->mfn, sr->mfd); /* Configure other dividers here, if any */ } static uint32_t ssichan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_pcminfo *scp = ch->parent; struct sc_info *sc = scp->sc; sndbuf_resize(ch->buffer, sc->dma_size / blocksize, blocksize); setup_dma(scp); - return (sndbuf_getblksz(ch->buffer)); + return (ch->buffer->blksz); } uint32_t ssi_dma_intr(void *arg, int chn) { struct sc_pcminfo *scp; struct sdma_conf *conf; struct sc_chinfo *ch; struct sc_info *sc; int bufsize; scp = arg; ch = &scp->chan[0]; sc = scp->sc; conf = sc->conf; - bufsize = sndbuf_getsize(ch->buffer); + bufsize = ch->buffer->bufsize; sc->pos += conf->period; if (sc->pos >= bufsize) sc->pos -= bufsize; if (ch->run) chn_intr(ch->channel); return (0); } static int find_sdma_controller(struct sc_info *sc) { struct sdma_softc *sdma_sc; phandle_t node; device_t sdma_dev; pcell_t dts_value[DMAS_TOTAL]; int len; if ((node = ofw_bus_get_node(sc->dev)) == -1) return (ENXIO); if ((len = OF_getproplen(node, "dmas")) <= 0) return (ENXIO); if (len != sizeof(dts_value)) { device_printf(sc->dev, "\"dmas\" property length is invalid: %d (expected %d)", len, sizeof(dts_value)); return (ENXIO); } OF_getencprop(node, "dmas", dts_value, sizeof(dts_value)); sc->sdma_ev_rx = dts_value[1]; sc->sdma_ev_tx = dts_value[5]; sdma_sc = NULL; sdma_dev = devclass_get_device(devclass_find("sdma"), 0); if (sdma_dev) sdma_sc = device_get_softc(sdma_dev); if (sdma_sc == NULL) { device_printf(sc->dev, "No sDMA found. Can't operate\n"); return (ENXIO); } sc->sdma_sc = sdma_sc; return (0); }; static int setup_dma(struct sc_pcminfo *scp) { struct sdma_conf *conf; struct sc_chinfo *ch; struct sc_info *sc; int fmt; ch = &scp->chan[0]; sc = scp->sc; conf = sc->conf; conf->ih = ssi_dma_intr; conf->ih_user = scp; conf->saddr = sc->buf_base_phys; conf->daddr = rman_get_start(sc->res[0]) + SSI_STX0; conf->event = sc->sdma_ev_tx; /* SDMA TX event */ - conf->period = sndbuf_getblksz(ch->buffer); - conf->num_bd = sndbuf_getblkcnt(ch->buffer); + conf->period = ch->buffer->blksz; + conf->num_bd = ch->buffer->blkcnt; /* * Word Length * Can be 32, 24, 16 or 8 for sDMA. * * SSI supports 24 at max. */ - fmt = sndbuf_getfmt(ch->buffer); + fmt = ch->buffer->fmt; if (fmt & AFMT_16BIT) { conf->word_length = 16; conf->command = CMD_2BYTES; } else if (fmt & AFMT_24BIT) { conf->word_length = 24; conf->command = CMD_3BYTES; } else { device_printf(sc->dev, "Unknown format\n"); return (-1); } return (0); } static int ssi_start(struct sc_pcminfo *scp) { struct sc_info *sc; int reg; sc = scp->sc; if (sdma_configure(sc->sdma_channel, sc->conf) != 0) { device_printf(sc->dev, "Can't configure sDMA\n"); return (-1); } /* Enable DMA interrupt */ reg = (SIER_TDMAE); WRITE4(sc, SSI_SIER, reg); sdma_start(sc->sdma_channel); return (0); } static int ssi_stop(struct sc_pcminfo *scp) { struct sc_info *sc; int reg; sc = scp->sc; reg = READ4(sc, SSI_SIER); reg &= ~(SIER_TDMAE); WRITE4(sc, SSI_SIER, reg); sdma_stop(sc->sdma_channel); bzero(sc->buf_base, sc->dma_size); return (0); } static int ssichan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "trigger start\n"); #endif ch->run = 1; ssi_start(scp); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "trigger stop or abort\n"); #endif ch->run = 0; ssi_stop(scp); break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t ssichan_getptr(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; return (sc->pos); } static uint32_t ssi_pfmt[] = { SND_FORMAT(AFMT_S24_LE, 2, 0), 0 }; static struct pcmchan_caps ssi_pcaps = {44100, 192000, ssi_pfmt, 0}; static struct pcmchan_caps * ssichan_getcaps(kobj_t obj, void *data) { return (&ssi_pcaps); } static kobj_method_t ssichan_methods[] = { KOBJMETHOD(channel_init, ssichan_init), KOBJMETHOD(channel_free, ssichan_free), KOBJMETHOD(channel_setformat, ssichan_setformat), KOBJMETHOD(channel_setspeed, ssichan_setspeed), KOBJMETHOD(channel_setblocksize, ssichan_setblocksize), KOBJMETHOD(channel_trigger, ssichan_trigger), KOBJMETHOD(channel_getptr, ssichan_getptr), KOBJMETHOD(channel_getcaps, ssichan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(ssichan); static int ssi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "fsl,imx6q-ssi")) return (ENXIO); device_set_desc(dev, "i.MX6 Synchronous Serial Interface (SSI)"); return (BUS_PROBE_DEFAULT); } static void ssi_intr(void *arg) { #if 0 struct sc_pcminfo *scp; struct sc_info *sc; scp = arg; sc = scp->sc; #endif /* We don't use SSI interrupt */ #if 0 device_printf(scp->sc->dev, "SSI Intr 0x%08x\n", READ4(sc, SSI_SISR)); #endif } static void setup_ssi(struct sc_info *sc) { int reg; reg = READ4(sc, SSI_STCCR); reg &= ~(WL3_WL0_M << WL3_WL0_S); reg |= (0xb << WL3_WL0_S); /* 24 bit */ reg &= ~(DC4_DC0_M << DC4_DC0_S); reg |= (1 << DC4_DC0_S); /* 2 words per frame */ reg &= ~(STCCR_DIV2); /* Divide by 1 */ reg &= ~(STCCR_PSR); /* Divide by 1 */ reg &= ~(PM7_PM0_M << PM7_PM0_S); reg |= (1 << PM7_PM0_S); /* Divide by 2 */ WRITE4(sc, SSI_STCCR, reg); reg = READ4(sc, SSI_SFCSR); reg &= ~(SFCSR_TFWM0_M << SFCSR_TFWM0_S); reg |= (8 << SFCSR_TFWM0_S); /* empty slots */ WRITE4(sc, SSI_SFCSR, reg); reg = READ4(sc, SSI_STCR); reg |= (STCR_TFEN0); reg &= ~(STCR_TFEN1); reg &= ~(STCR_TSHFD); /* MSB */ reg |= (STCR_TXBIT0); reg |= (STCR_TXDIR | STCR_TFDIR); reg |= (STCR_TSCKP); /* falling edge */ reg |= (STCR_TFSI); reg &= ~(STCR_TFSI); /* active high frame sync */ reg &= ~(STCR_TFSL); reg |= STCR_TEFS; WRITE4(sc, SSI_STCR, reg); reg = READ4(sc, SSI_SCR); reg &= ~(SCR_I2S_MODE_M << SCR_I2S_MODE_S); /* Not master */ reg |= (SCR_SSIEN | SCR_TE); reg |= (SCR_NET); reg |= (SCR_SYN); WRITE4(sc, SSI_SCR, reg); } static void ssi_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) { bus_addr_t *addr; if (err) return; addr = (bus_addr_t*)arg; *addr = segs[0].ds_addr; } static int ssi_attach(device_t dev) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; struct sc_info *sc; int err; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->sr = &rate_map[0]; sc->pos = 0; sc->conf = malloc(sizeof(struct sdma_conf), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "ssi softc"); if (sc->lock == NULL) { device_printf(dev, "Can't create mtx\n"); return (ENXIO); } if (bus_alloc_resources(dev, ssi_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* Memory interface */ sc->bst = rman_get_bustag(sc->res[0]); sc->bsh = rman_get_bushandle(sc->res[0]); /* SDMA */ if (find_sdma_controller(sc)) { device_printf(dev, "could not find active SDMA\n"); return (ENXIO); } /* Setup PCM */ scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); scp->sc = sc; scp->dev = dev; /* * Maximum possible DMA buffer. * Will be used partially to match 24 bit word. */ sc->dma_size = 131072; /* * Must use dma_size boundary as modulo feature required. * Modulo feature allows setup circular buffer. */ err = bus_dma_tag_create( bus_get_dma_tag(sc->dev), 4, sc->dma_size, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->dma_size, 1, /* maxsize, nsegments */ sc->dma_size, 0, /* maxsegsize, flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dma_tag); err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->buf_base, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &sc->dma_map); if (err) { device_printf(dev, "cannot allocate framebuffer\n"); return (ENXIO); } err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->buf_base, sc->dma_size, ssi_dmamap_cb, &sc->buf_base_phys, BUS_DMA_NOWAIT); if (err) { device_printf(dev, "cannot load DMA map\n"); return (ENXIO); } bzero(sc->buf_base, sc->dma_size); /* Setup interrupt handler */ err = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_AV, NULL, ssi_intr, scp, &sc->ih); if (err) { device_printf(dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); pcm_init(dev, scp); scp->chnum = 0; pcm_addchan(dev, PCMDIR_PLAY, &ssichan_class, scp); scp->chnum++; snprintf(status, SND_STATUSLEN, "at simplebus"); err = pcm_register(dev, status); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } mixer_init(dev, &ssimixer_class, scp); setup_ssi(sc); imx_ccm_ssi_configure(dev); sc->sdma_channel = sdma_alloc(); if (sc->sdma_channel < 0) { device_printf(sc->dev, "Can't get sDMA channel\n"); return (1); } return (0); } static device_method_t ssi_pcm_methods[] = { DEVMETHOD(device_probe, ssi_probe), DEVMETHOD(device_attach, ssi_attach), { 0, 0 } }; static driver_t ssi_pcm_driver = { "pcm", ssi_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(ssi, simplebus, ssi_pcm_driver, 0, 0); MODULE_DEPEND(ssi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(ssi, sdma, 0, 0, 0); MODULE_VERSION(ssi, 1); diff --git a/sys/arm/freescale/vybrid/vf_sai.c b/sys/arm/freescale/vybrid/vf_sai.c index e895529c4810..9c0125768fee 100644 --- a/sys/arm/freescale/vybrid/vf_sai.c +++ b/sys/arm/freescale/vybrid/vf_sai.c @@ -1,800 +1,800 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Ruslan Bukin * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * Vybrid Family Synchronous Audio Interface (SAI) * Chapter 51, Vybrid Reference Manual, Rev. 5, 07/2013 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define I2S_TCSR 0x00 /* SAI Transmit Control */ #define I2S_TCR1 0x04 /* SAI Transmit Configuration 1 */ #define I2S_TCR2 0x08 /* SAI Transmit Configuration 2 */ #define I2S_TCR3 0x0C /* SAI Transmit Configuration 3 */ #define I2S_TCR4 0x10 /* SAI Transmit Configuration 4 */ #define I2S_TCR5 0x14 /* SAI Transmit Configuration 5 */ #define I2S_TDR0 0x20 /* SAI Transmit Data */ #define I2S_TFR0 0x40 /* SAI Transmit FIFO */ #define I2S_TMR 0x60 /* SAI Transmit Mask */ #define I2S_RCSR 0x80 /* SAI Receive Control */ #define I2S_RCR1 0x84 /* SAI Receive Configuration 1 */ #define I2S_RCR2 0x88 /* SAI Receive Configuration 2 */ #define I2S_RCR3 0x8C /* SAI Receive Configuration 3 */ #define I2S_RCR4 0x90 /* SAI Receive Configuration 4 */ #define I2S_RCR5 0x94 /* SAI Receive Configuration 5 */ #define I2S_RDR0 0xA0 /* SAI Receive Data */ #define I2S_RFR0 0xC0 /* SAI Receive FIFO */ #define I2S_RMR 0xE0 /* SAI Receive Mask */ #define TCR1_TFW_M 0x1f /* Transmit FIFO Watermark Mask */ #define TCR1_TFW_S 0 /* Transmit FIFO Watermark Shift */ #define TCR2_MSEL_M 0x3 /* MCLK Select Mask*/ #define TCR2_MSEL_S 26 /* MCLK Select Shift*/ #define TCR2_BCP (1 << 25) /* Bit Clock Polarity */ #define TCR2_BCD (1 << 24) /* Bit Clock Direction */ #define TCR3_TCE (1 << 16) /* Transmit Channel Enable */ #define TCR4_FRSZ_M 0x1f /* Frame size Mask */ #define TCR4_FRSZ_S 16 /* Frame size Shift */ #define TCR4_SYWD_M 0x1f /* Sync Width Mask */ #define TCR4_SYWD_S 8 /* Sync Width Shift */ #define TCR4_MF (1 << 4) /* MSB First */ #define TCR4_FSE (1 << 3) /* Frame Sync Early */ #define TCR4_FSP (1 << 1) /* Frame Sync Polarity Low */ #define TCR4_FSD (1 << 0) /* Frame Sync Direction Master */ #define TCR5_FBT_M 0x1f /* First Bit Shifted */ #define TCR5_FBT_S 8 /* First Bit Shifted */ #define TCR5_W0W_M 0x1f /* Word 0 Width */ #define TCR5_W0W_S 16 /* Word 0 Width */ #define TCR5_WNW_M 0x1f /* Word N Width */ #define TCR5_WNW_S 24 /* Word N Width */ #define TCSR_TE (1 << 31) /* Transmitter Enable */ #define TCSR_BCE (1 << 28) /* Bit Clock Enable */ #define TCSR_FRDE (1 << 0) /* FIFO Request DMA Enable */ #define SAI_NCHANNELS 1 static MALLOC_DEFINE(M_SAI, "sai", "sai audio"); struct sai_rate { uint32_t speed; uint32_t div; /* Bit Clock Divide. Division value is (div + 1) * 2. */ uint32_t mfi; /* PLL4 Multiplication Factor Integer */ uint32_t mfn; /* PLL4 Multiplication Factor Numerator */ uint32_t mfd; /* PLL4 Multiplication Factor Denominator */ }; /* * Bit clock divider formula * (div + 1) * 2 = MCLK/(nch * LRCLK * bits/1000000), * where: * MCLK - master clock * nch - number of channels * LRCLK - left right clock * e.g. (div + 1) * 2 = 16.9344/(2 * 44100 * 24/1000000) * * Example for 96khz, 24bit, 18.432 Mhz mclk (192fs) * { 96000, 1, 18, 40176000, 93000000 }, */ static struct sai_rate rate_map[] = { { 44100, 7, 33, 80798400, 93000000 }, /* 33.8688 Mhz */ { 96000, 3, 36, 80352000, 93000000 }, /* 36.864 Mhz */ { 192000, 1, 36, 80352000, 93000000 }, /* 36.864 Mhz */ { 0, 0 }, }; struct sc_info { struct resource *res[2]; bus_space_tag_t bst; bus_space_handle_t bsh; device_t dev; struct mtx *lock; uint32_t speed; uint32_t period; void *ih; int pos; int dma_size; bus_dma_tag_t dma_tag; bus_dmamap_t dma_map; bus_addr_t buf_base_phys; uint32_t *buf_base; struct tcd_conf *tcd; struct sai_rate *sr; struct edma_softc *edma_sc; int edma_chnum; }; /* Channel registers */ struct sc_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_pcminfo *parent; /* Channel information */ uint32_t dir; uint32_t format; /* Flags */ uint32_t run; }; /* PCM device private data */ struct sc_pcminfo { device_t dev; uint32_t (*ih) (struct sc_pcminfo *scp); uint32_t chnum; struct sc_chinfo chan[SAI_NCHANNELS]; struct sc_info *sc; }; static struct resource_spec sai_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; static int setup_dma(struct sc_pcminfo *scp); static void setup_sai(struct sc_info *); static void sai_configure_clock(struct sc_info *); /* * Mixer interface. */ static int saimixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return -1; mask = SOUND_MASK_PCM; snd_mtxlock(sc->lock); pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, mask); snd_mtxunlock(sc->lock); return (0); } static int saimixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { #if 0 struct sc_pcminfo *scp; scp = mix_getdevinfo(m); device_printf(scp->dev, "saimixer_set() %d %d\n", left, right); #endif return (0); } static kobj_method_t saimixer_methods[] = { KOBJMETHOD(mixer_init, saimixer_init), KOBJMETHOD(mixer_set, saimixer_set), KOBJMETHOD_END }; MIXER_DECLARE(saimixer); /* * Channel interface. */ static void * saichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; scp = (struct sc_pcminfo *)devinfo; sc = scp->sc; snd_mtxlock(sc->lock); ch = &scp->chan[0]; ch->dir = dir; ch->run = 0; ch->buffer = b; ch->channel = c; ch->parent = scp; snd_mtxunlock(sc->lock); if (sndbuf_setup(ch->buffer, sc->buf_base, sc->dma_size) != 0) { device_printf(scp->dev, "Can't setup sndbuf.\n"); return NULL; } return ch; } static int saichan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_pcminfo *scp = ch->parent; struct sc_info *sc = scp->sc; #if 0 device_printf(scp->dev, "saichan_free()\n"); #endif snd_mtxlock(sc->lock); /* TODO: free channel buffer */ snd_mtxunlock(sc->lock); return (0); } static int saichan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch = data; ch->format = format; return (0); } static uint32_t saichan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sai_rate *sr; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; sr = NULL; /* First look for equal frequency. */ for (i = 0; rate_map[i].speed != 0; i++) { if (rate_map[i].speed == speed) sr = &rate_map[i]; } /* If no match, just find nearest. */ if (sr == NULL) { for (i = 0; rate_map[i].speed != 0; i++) { sr = &rate_map[i]; threshold = sr->speed + ((rate_map[i + 1].speed != 0) ? ((rate_map[i + 1].speed - sr->speed) >> 1) : 0); if (speed < threshold) break; } } sc->sr = sr; sai_configure_clock(sc); return (sr->speed); } static void sai_configure_clock(struct sc_info *sc) { struct sai_rate *sr; int reg; sr = sc->sr; /* * Manual says that TCR/RCR registers must not be * altered when TCSR[TE] is set. * We ignore it since we have problem sometimes * after re-enabling transmitter (DMA goes stall). */ reg = READ4(sc, I2S_TCR2); reg &= ~(0xff << 0); reg |= (sr->div << 0); WRITE4(sc, I2S_TCR2, reg); pll4_configure_output(sr->mfi, sr->mfn, sr->mfd); } static uint32_t saichan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_pcminfo *scp = ch->parent; struct sc_info *sc = scp->sc; sndbuf_resize(ch->buffer, sc->dma_size / blocksize, blocksize); - sc->period = sndbuf_getblksz(ch->buffer); + sc->period = ch->buffer->blksz; return (sc->period); } uint32_t sai_dma_intr(void *arg, int chn); uint32_t sai_dma_intr(void *arg, int chn) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; struct tcd_conf *tcd; scp = arg; ch = &scp->chan[0]; sc = scp->sc; tcd = sc->tcd; sc->pos += (tcd->nbytes * tcd->nmajor); if (sc->pos >= sc->dma_size) sc->pos -= sc->dma_size; if (ch->run) chn_intr(ch->channel); return (0); } static int find_edma_controller(struct sc_info *sc) { struct edma_softc *edma_sc; phandle_t node, edma_node; int edma_src_transmit; int edma_mux_group; int edma_device_id; device_t edma_dev; int dts_value; int len; int i; if ((node = ofw_bus_get_node(sc->dev)) == -1) return (ENXIO); if ((len = OF_getproplen(node, "edma-controller")) <= 0) return (ENXIO); if ((len = OF_getproplen(node, "edma-src-transmit")) <= 0) return (ENXIO); if ((len = OF_getproplen(node, "edma-mux-group")) <= 0) return (ENXIO); OF_getencprop(node, "edma-src-transmit", &dts_value, len); edma_src_transmit = dts_value; OF_getencprop(node, "edma-mux-group", &dts_value, len); edma_mux_group = dts_value; OF_getencprop(node, "edma-controller", &dts_value, len); edma_node = OF_node_from_xref(dts_value); if ((len = OF_getproplen(edma_node, "device-id")) <= 0) { return (ENXIO); } OF_getencprop(edma_node, "device-id", &dts_value, len); edma_device_id = dts_value; edma_sc = NULL; for (i = 0; i < EDMA_NUM_DEVICES; i++) { edma_dev = devclass_get_device(devclass_find("edma"), i); if (edma_dev) { edma_sc = device_get_softc(edma_dev); if (edma_sc->device_id == edma_device_id) { /* found */ break; } edma_sc = NULL; } } if (edma_sc == NULL) { device_printf(sc->dev, "no eDMA. can't operate\n"); return (ENXIO); } sc->edma_sc = edma_sc; sc->edma_chnum = edma_sc->channel_configure(edma_sc, edma_mux_group, edma_src_transmit); if (sc->edma_chnum < 0) { /* can't setup eDMA */ return (ENXIO); } return (0); }; static int setup_dma(struct sc_pcminfo *scp) { struct tcd_conf *tcd; struct sc_info *sc; sc = scp->sc; tcd = malloc(sizeof(struct tcd_conf), M_DEVBUF, M_WAITOK | M_ZERO); tcd->channel = sc->edma_chnum; tcd->ih = sai_dma_intr; tcd->ih_user = scp; tcd->saddr = sc->buf_base_phys; tcd->daddr = rman_get_start(sc->res[0]) + I2S_TDR0; /* * Bytes to transfer per each minor loop. * Hardware FIFO buffer size is 32x32bits. */ tcd->nbytes = 64; tcd->nmajor = 512; tcd->smod = 17; /* dma_size range */ tcd->dmod = 0; tcd->esg = 0; tcd->soff = 0x4; tcd->doff = 0; tcd->ssize = 0x2; tcd->dsize = 0x2; tcd->slast = 0; tcd->dlast_sga = 0; sc->tcd = tcd; sc->edma_sc->dma_setup(sc->edma_sc, sc->tcd); return (0); } static int saichan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_pcminfo *scp = ch->parent; struct sc_info *sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "trigger start\n"); #endif ch->run = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "trigger stop or abort\n"); #endif ch->run = 0; break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t saichan_getptr(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; return (sc->pos); } static uint32_t sai_pfmt[] = { /* * eDMA doesn't allow 24-bit coping, * so we use 32. */ SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps sai_pcaps = {44100, 192000, sai_pfmt, 0}; static struct pcmchan_caps * saichan_getcaps(kobj_t obj, void *data) { return (&sai_pcaps); } static kobj_method_t saichan_methods[] = { KOBJMETHOD(channel_init, saichan_init), KOBJMETHOD(channel_free, saichan_free), KOBJMETHOD(channel_setformat, saichan_setformat), KOBJMETHOD(channel_setspeed, saichan_setspeed), KOBJMETHOD(channel_setblocksize, saichan_setblocksize), KOBJMETHOD(channel_trigger, saichan_trigger), KOBJMETHOD(channel_getptr, saichan_getptr), KOBJMETHOD(channel_getcaps, saichan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(saichan); static int sai_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "fsl,mvf600-sai")) return (ENXIO); device_set_desc(dev, "Vybrid Family Synchronous Audio Interface"); return (BUS_PROBE_DEFAULT); } static void sai_intr(void *arg) { struct sc_pcminfo *scp; struct sc_info *sc; scp = arg; sc = scp->sc; device_printf(sc->dev, "Error I2S_TCSR == 0x%08x\n", READ4(sc, I2S_TCSR)); } static void setup_sai(struct sc_info *sc) { int reg; /* * TCR/RCR registers must not be altered when TCSR[TE] is set. */ reg = READ4(sc, I2S_TCSR); reg &= ~(TCSR_BCE | TCSR_TE | TCSR_FRDE); WRITE4(sc, I2S_TCSR, reg); reg = READ4(sc, I2S_TCR3); reg &= ~(TCR3_TCE); WRITE4(sc, I2S_TCR3, reg); reg = (64 << TCR1_TFW_S); WRITE4(sc, I2S_TCR1, reg); reg = READ4(sc, I2S_TCR2); reg &= ~(TCR2_MSEL_M << TCR2_MSEL_S); reg |= (1 << TCR2_MSEL_S); reg |= (TCR2_BCP | TCR2_BCD); WRITE4(sc, I2S_TCR2, reg); sai_configure_clock(sc); reg = READ4(sc, I2S_TCR3); reg |= (TCR3_TCE); WRITE4(sc, I2S_TCR3, reg); /* Configure to 32-bit I2S mode */ reg = READ4(sc, I2S_TCR4); reg &= ~(TCR4_FRSZ_M << TCR4_FRSZ_S); reg |= (1 << TCR4_FRSZ_S); /* 2 words per frame */ reg &= ~(TCR4_SYWD_M << TCR4_SYWD_S); reg |= (23 << TCR4_SYWD_S); reg |= (TCR4_MF | TCR4_FSE | TCR4_FSP | TCR4_FSD); WRITE4(sc, I2S_TCR4, reg); reg = READ4(sc, I2S_TCR5); reg &= ~(TCR5_W0W_M << TCR5_W0W_S); reg |= (23 << TCR5_W0W_S); reg &= ~(TCR5_WNW_M << TCR5_WNW_S); reg |= (23 << TCR5_WNW_S); reg &= ~(TCR5_FBT_M << TCR5_FBT_S); reg |= (31 << TCR5_FBT_S); WRITE4(sc, I2S_TCR5, reg); /* Enable transmitter */ reg = READ4(sc, I2S_TCSR); reg |= (TCSR_BCE | TCSR_TE | TCSR_FRDE); reg |= (1 << 10); /* FEIE */ WRITE4(sc, I2S_TCSR, reg); } static void sai_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) { bus_addr_t *addr; if (err) return; addr = (bus_addr_t*)arg; *addr = segs[0].ds_addr; } static int sai_attach(device_t dev) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; struct sc_info *sc; int err; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->sr = &rate_map[0]; sc->pos = 0; sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sai softc"); if (sc->lock == NULL) { device_printf(dev, "Cant create mtx\n"); return (ENXIO); } if (bus_alloc_resources(dev, sai_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* Memory interface */ sc->bst = rman_get_bustag(sc->res[0]); sc->bsh = rman_get_bushandle(sc->res[0]); /* eDMA */ if (find_edma_controller(sc)) { device_printf(dev, "could not find active eDMA\n"); return (ENXIO); } /* Setup PCM */ scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); scp->sc = sc; scp->dev = dev; /* DMA */ sc->dma_size = 131072; /* * Must use dma_size boundary as modulo feature required. * Modulo feature allows setup circular buffer. */ err = bus_dma_tag_create( bus_get_dma_tag(sc->dev), 4, sc->dma_size, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->dma_size, 1, /* maxsize, nsegments */ sc->dma_size, 0, /* maxsegsize, flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dma_tag); err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->buf_base, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &sc->dma_map); if (err) { device_printf(dev, "cannot allocate framebuffer\n"); return (ENXIO); } err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->buf_base, sc->dma_size, sai_dmamap_cb, &sc->buf_base_phys, BUS_DMA_NOWAIT); if (err) { device_printf(dev, "cannot load DMA map\n"); return (ENXIO); } bzero(sc->buf_base, sc->dma_size); /* Setup interrupt handler */ err = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_AV, NULL, sai_intr, scp, &sc->ih); if (err) { device_printf(dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); pcm_init(dev, scp); scp->chnum = 0; pcm_addchan(dev, PCMDIR_PLAY, &saichan_class, scp); scp->chnum++; snprintf(status, SND_STATUSLEN, "at simplebus"); err = pcm_register(dev, status); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } mixer_init(dev, &saimixer_class, scp); setup_dma(scp); setup_sai(sc); return (0); } static device_method_t sai_pcm_methods[] = { DEVMETHOD(device_probe, sai_probe), DEVMETHOD(device_attach, sai_attach), { 0, 0 } }; static driver_t sai_pcm_driver = { "pcm", sai_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(sai, simplebus, sai_pcm_driver, 0, 0); MODULE_DEPEND(sai, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(sai, 1); diff --git a/sys/arm64/rockchip/rk_i2s.c b/sys/arm64/rockchip/rk_i2s.c index 5f1b6bbdeabf..856fa20e6ce4 100644 --- a/sys/arm64/rockchip/rk_i2s.c +++ b/sys/arm64/rockchip/rk_i2s.c @@ -1,653 +1,653 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Oleksandr Tymoshenko * * 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 AUTHOR ``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 AUTHOR 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #include "opt_snd.h" #include #include #include "audio_dai_if.h" #define I2S_TXCR 0x0000 #define I2S_CSR_2 (0 << 15) #define I2S_CSR_4 (1 << 15) #define I2S_CSR_6 (2 << 15) #define I2S_CSR_8 (3 << 15) #define I2S_TXCR_IBM_NORMAL (0 << 9) #define I2S_TXCR_IBM_LJ (1 << 9) #define I2S_TXCR_IBM_RJ (2 << 9) #define I2S_TXCR_PBM_NODELAY (0 << 7) #define I2S_TXCR_PBM_1 (1 << 7) #define I2S_TXCR_PBM_2 (2 << 7) #define I2S_TXCR_PBM_3 (3 << 7) #define I2S_TXCR_TFS_I2S (0 << 5) #define I2S_TXCR_TFS_PCM (1 << 5) #define I2S_TXCR_VDW_16 (0xf << 0) #define I2S_RXCR 0x0004 #define I2S_RXCR_IBM_NORMAL (0 << 9) #define I2S_RXCR_IBM_LJ (1 << 9) #define I2S_RXCR_IBM_RJ (2 << 9) #define I2S_RXCR_PBM_NODELAY (0 << 7) #define I2S_RXCR_PBM_1 (1 << 7) #define I2S_RXCR_PBM_2 (2 << 7) #define I2S_RXCR_PBM_3 (3 << 7) #define I2S_RXCR_TFS_I2S (0 << 5) #define I2S_RXCR_TFS_PCM (1 << 5) #define I2S_RXCR_VDW_16 (0xf << 0) #define I2S_CKR 0x0008 #define I2S_CKR_MSS_MASK (1 << 27) #define I2S_CKR_MSS_MASTER (0 << 27) #define I2S_CKR_MSS_SLAVE (1 << 27) #define I2S_CKR_CKP (1 << 26) #define I2S_CKR_MDIV(n) (((n) - 1) << 16) #define I2S_CKR_MDIV_MASK (0xff << 16) #define I2S_CKR_RSD(n) (((n) - 1) << 8) #define I2S_CKR_RSD_MASK (0xff << 8) #define I2S_CKR_TSD(n) (((n) - 1) << 0) #define I2S_CKR_TSD_MASK (0xff << 0) #define I2S_TXFIFOLR 0x000c #define TXFIFO0LR_MASK 0x3f #define I2S_DMACR 0x0010 #define I2S_DMACR_RDE_ENABLE (1 << 24) #define I2S_DMACR_RDL(n) (((n) - 1) << 16) #define I2S_DMACR_TDE_ENABLE (1 << 8) #define I2S_DMACR_TDL(n) ((n) << 0) #define I2S_INTCR 0x0014 #define I2S_INTCR_RFT(n) (((n) - 1) << 20) #define I2S_INTCR_TFT(n) ((n) << 4) #define I2S_INTCR_RXOIC (1 << 18) #define I2S_INTCR_RXOIE (1 << 17) #define I2S_INTCR_RXFIE (1 << 16) #define I2S_INTCR_TXUIC (1 << 2) #define I2S_INTCR_TXUIE (1 << 1) #define I2S_INTCR_TXEIE (1 << 0) #define I2S_INTSR 0x0018 #define I2S_INTSR_RXOI (1 << 17) #define I2S_INTSR_RXFI (1 << 16) #define I2S_INTSR_TXUI (1 << 1) #define I2S_INTSR_TXEI (1 << 0) #define I2S_XFER 0x001c #define I2S_XFER_RXS_START (1 << 1) #define I2S_XFER_TXS_START (1 << 0) #define I2S_CLR 0x0020 #define I2S_CLR_RXC (1 << 1) #define I2S_CLR_TXC (1 << 0) #define I2S_TXDR 0x0024 #define I2S_RXDR 0x0028 #define I2S_RXFIFOLR 0x002c #define RXFIFO0LR_MASK 0x3f /* syscon */ #define GRF_SOC_CON8 0xe220 #define I2S_IO_DIRECTION_MASK 7 #define I2S_IO_DIRECTION_SHIFT 11 #define I2S_IO_8CH_OUT_2CH_IN 0 #define I2S_IO_6CH_OUT_4CH_IN 4 #define I2S_IO_4CH_OUT_6CH_IN 6 #define I2S_IO_2CH_OUT_8CH_IN 7 #define DIV_ROUND_CLOSEST(n,d) (((n) + (d) / 2) / (d)) #define RK_I2S_SAMPLING_RATE 48000 #define FIFO_SIZE 32 static struct ofw_compat_data compat_data[] = { { "rockchip,rk3066-i2s", 1 }, { "rockchip,rk3399-i2s", 1 }, { NULL, 0 } }; static struct resource_spec rk_i2s_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; struct rk_i2s_softc { device_t dev; struct resource *res[2]; struct mtx mtx; clk_t clk; clk_t hclk; void * intrhand; struct syscon *grf; /* pointers to playback/capture buffers */ uint32_t play_ptr; uint32_t rec_ptr; }; #define RK_I2S_LOCK(sc) mtx_lock(&(sc)->mtx) #define RK_I2S_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define RK_I2S_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) #define RK_I2S_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) static int rk_i2s_probe(device_t dev); static int rk_i2s_attach(device_t dev); static int rk_i2s_detach(device_t dev); static uint32_t sc_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps rk_i2s_caps = {RK_I2S_SAMPLING_RATE, RK_I2S_SAMPLING_RATE, sc_fmt, 0}; static int rk_i2s_init(struct rk_i2s_softc *sc) { uint32_t val; int error; clk_set_freq(sc->clk, RK_I2S_SAMPLING_RATE * 256, CLK_SET_ROUND_DOWN); error = clk_enable(sc->clk); if (error != 0) { device_printf(sc->dev, "cannot enable i2s_clk clock\n"); return (ENXIO); } val = I2S_INTCR_TFT(FIFO_SIZE/2); val |= I2S_INTCR_RFT(FIFO_SIZE/2); RK_I2S_WRITE_4(sc, I2S_INTCR, val); if (sc->grf && ofw_bus_is_compatible(sc->dev, "rockchip,rk3399-i2s")) { val = (I2S_IO_2CH_OUT_8CH_IN << I2S_IO_DIRECTION_SHIFT); val |= (I2S_IO_DIRECTION_MASK << I2S_IO_DIRECTION_SHIFT) << 16; SYSCON_WRITE_4(sc->grf, GRF_SOC_CON8, val); #if 0 // HACK: enable IO domain val = (1 << 1); val |= (1 << 1) << 16; SYSCON_WRITE_4(sc->grf, 0xe640, val); #endif } RK_I2S_WRITE_4(sc, I2S_XFER, 0); return (0); } static int rk_i2s_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Rockchip I2S"); return (BUS_PROBE_DEFAULT); } static int rk_i2s_attach(device_t dev) { struct rk_i2s_softc *sc; int error; phandle_t node; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); if (bus_alloc_resources(dev, rk_i2s_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } error = clk_get_by_ofw_name(dev, 0, "i2s_hclk", &sc->hclk); if (error != 0) { device_printf(dev, "cannot get i2s_hclk clock\n"); goto fail; } error = clk_get_by_ofw_name(dev, 0, "i2s_clk", &sc->clk); if (error != 0) { device_printf(dev, "cannot get i2s_clk clock\n"); goto fail; } /* Activate the module clock. */ error = clk_enable(sc->hclk); if (error != 0) { device_printf(dev, "cannot enable i2s_hclk clock\n"); goto fail; } node = ofw_bus_get_node(dev); if (OF_hasprop(node, "rockchip,grf") && syscon_get_by_ofw_property(dev, node, "rockchip,grf", &sc->grf) != 0) { device_printf(dev, "cannot get grf driver handle\n"); return (ENXIO); } rk_i2s_init(sc); OF_device_register_xref(OF_xref_from_node(node), dev); return (0); fail: rk_i2s_detach(dev); return (error); } static int rk_i2s_detach(device_t dev) { struct rk_i2s_softc *i2s; i2s = device_get_softc(dev); if (i2s->hclk != NULL) clk_release(i2s->hclk); if (i2s->clk) clk_release(i2s->clk); if (i2s->intrhand != NULL) bus_teardown_intr(i2s->dev, i2s->res[1], i2s->intrhand); bus_release_resources(dev, rk_i2s_spec, i2s->res); mtx_destroy(&i2s->mtx); return (0); } static int rk_i2s_dai_init(device_t dev, uint32_t format) { uint32_t val, txcr, rxcr; struct rk_i2s_softc *sc; int fmt, pol, clk; sc = device_get_softc(dev); fmt = AUDIO_DAI_FORMAT_FORMAT(format); pol = AUDIO_DAI_FORMAT_POLARITY(format); clk = AUDIO_DAI_FORMAT_CLOCK(format); /* Set format */ val = RK_I2S_READ_4(sc, I2S_CKR); val &= ~(I2S_CKR_MSS_MASK); switch (clk) { case AUDIO_DAI_CLOCK_CBM_CFM: val |= I2S_CKR_MSS_MASTER; break; case AUDIO_DAI_CLOCK_CBS_CFS: val |= I2S_CKR_MSS_SLAVE; break; default: return (EINVAL); } switch (pol) { case AUDIO_DAI_POLARITY_IB_NF: val |= I2S_CKR_CKP; break; case AUDIO_DAI_POLARITY_NB_NF: val &= ~I2S_CKR_CKP; break; default: return (EINVAL); } RK_I2S_WRITE_4(sc, I2S_CKR, val); txcr = I2S_TXCR_VDW_16 | I2S_CSR_2; rxcr = I2S_RXCR_VDW_16 | I2S_CSR_2; switch (fmt) { case AUDIO_DAI_FORMAT_I2S: txcr |= I2S_TXCR_IBM_NORMAL; rxcr |= I2S_RXCR_IBM_NORMAL; break; case AUDIO_DAI_FORMAT_LJ: txcr |= I2S_TXCR_IBM_LJ; rxcr |= I2S_RXCR_IBM_LJ; break; case AUDIO_DAI_FORMAT_RJ: txcr |= I2S_TXCR_IBM_RJ; rxcr |= I2S_RXCR_IBM_RJ; break; case AUDIO_DAI_FORMAT_DSPA: txcr |= I2S_TXCR_TFS_PCM; rxcr |= I2S_RXCR_TFS_PCM; txcr |= I2S_TXCR_PBM_1; rxcr |= I2S_RXCR_PBM_1; break; case AUDIO_DAI_FORMAT_DSPB: txcr |= I2S_TXCR_TFS_PCM; rxcr |= I2S_RXCR_TFS_PCM; txcr |= I2S_TXCR_PBM_2; rxcr |= I2S_RXCR_PBM_2; break; default: return EINVAL; } RK_I2S_WRITE_4(sc, I2S_TXCR, txcr); RK_I2S_WRITE_4(sc, I2S_RXCR, rxcr); RK_I2S_WRITE_4(sc, I2S_XFER, 0); return (0); } static int rk_i2s_dai_intr(device_t dev, struct snd_dbuf *play_buf, struct snd_dbuf *rec_buf) { struct rk_i2s_softc *sc; uint32_t status; uint32_t level; uint32_t val = 0x00; int ret = 0; sc = device_get_softc(dev); RK_I2S_LOCK(sc); status = RK_I2S_READ_4(sc, I2S_INTSR); if (status & I2S_INTSR_TXEI) { level = RK_I2S_READ_4(sc, I2S_TXFIFOLR) & TXFIFO0LR_MASK; uint8_t *samples; uint32_t count, size, readyptr, written; count = sndbuf_getready(play_buf); if (count > FIFO_SIZE - 1) count = FIFO_SIZE - 1; - size = sndbuf_getsize(play_buf); + size = play_buf->bufsize; readyptr = sndbuf_getreadyptr(play_buf); - samples = (uint8_t*)sndbuf_getbuf(play_buf); + samples = play_buf->buf; written = 0; for (; level < count; level++) { val = (samples[readyptr++ % size] << 0); val |= (samples[readyptr++ % size] << 8); val |= (samples[readyptr++ % size] << 16); val |= (samples[readyptr++ % size] << 24); written += 4; RK_I2S_WRITE_4(sc, I2S_TXDR, val); } sc->play_ptr += written; sc->play_ptr %= size; ret |= AUDIO_DAI_PLAY_INTR; } if (status & I2S_INTSR_RXFI) { level = RK_I2S_READ_4(sc, I2S_RXFIFOLR) & RXFIFO0LR_MASK; uint8_t *samples; uint32_t count, size, freeptr, recorded; count = sndbuf_getfree(rec_buf); - size = sndbuf_getsize(rec_buf); + size = rec_buf->bufsize; freeptr = sndbuf_getfreeptr(rec_buf); - samples = (uint8_t*)sndbuf_getbuf(rec_buf); + samples = rec_buf->buf; recorded = 0; if (level > count / 4) level = count / 4; for (; level > 0; level--) { val = RK_I2S_READ_4(sc, I2S_RXDR); samples[freeptr++ % size] = val & 0xff; samples[freeptr++ % size] = (val >> 8) & 0xff; samples[freeptr++ % size] = (val >> 16) & 0xff; samples[freeptr++ % size] = (val >> 24) & 0xff; recorded += 4; } sc->rec_ptr += recorded; sc->rec_ptr %= size; ret |= AUDIO_DAI_REC_INTR; } RK_I2S_UNLOCK(sc); return (ret); } static struct pcmchan_caps * rk_i2s_dai_get_caps(device_t dev) { return (&rk_i2s_caps); } static int rk_i2s_dai_trigger(device_t dev, int go, int pcm_dir) { struct rk_i2s_softc *sc = device_get_softc(dev); uint32_t val; uint32_t clear_bit; if ((pcm_dir != PCMDIR_PLAY) && (pcm_dir != PCMDIR_REC)) return (EINVAL); switch (go) { case PCMTRIG_START: val = RK_I2S_READ_4(sc, I2S_INTCR); if (pcm_dir == PCMDIR_PLAY) val |= I2S_INTCR_TXEIE; else if (pcm_dir == PCMDIR_REC) val |= I2S_INTCR_RXFIE; RK_I2S_WRITE_4(sc, I2S_INTCR, val); val = I2S_XFER_TXS_START | I2S_XFER_RXS_START; RK_I2S_WRITE_4(sc, I2S_XFER, val); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: val = RK_I2S_READ_4(sc, I2S_INTCR); if (pcm_dir == PCMDIR_PLAY) val &= ~I2S_INTCR_TXEIE; else if (pcm_dir == PCMDIR_REC) val &= ~I2S_INTCR_RXFIE; RK_I2S_WRITE_4(sc, I2S_INTCR, val); /* * If there is no other activity going on, stop transfers */ if ((val & (I2S_INTCR_TXEIE | I2S_INTCR_RXFIE)) == 0) { RK_I2S_WRITE_4(sc, I2S_XFER, 0); if (pcm_dir == PCMDIR_PLAY) clear_bit = I2S_CLR_TXC; else if (pcm_dir == PCMDIR_REC) clear_bit = I2S_CLR_RXC; else return (EINVAL); val = RK_I2S_READ_4(sc, I2S_CLR); val |= clear_bit; RK_I2S_WRITE_4(sc, I2S_CLR, val); while ((RK_I2S_READ_4(sc, I2S_CLR) & clear_bit) != 0) DELAY(1); } RK_I2S_LOCK(sc); if (pcm_dir == PCMDIR_PLAY) sc->play_ptr = 0; else sc->rec_ptr = 0; RK_I2S_UNLOCK(sc); break; } return (0); } static uint32_t rk_i2s_dai_get_ptr(device_t dev, int pcm_dir) { struct rk_i2s_softc *sc; uint32_t ptr; sc = device_get_softc(dev); RK_I2S_LOCK(sc); if (pcm_dir == PCMDIR_PLAY) ptr = sc->play_ptr; else ptr = sc->rec_ptr; RK_I2S_UNLOCK(sc); return ptr; } static int rk_i2s_dai_setup_intr(device_t dev, driver_intr_t intr_handler, void *intr_arg) { struct rk_i2s_softc *sc = device_get_softc(dev); if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_AV | INTR_MPSAFE, NULL, intr_handler, intr_arg, &sc->intrhand)) { device_printf(dev, "cannot setup interrupt handler\n"); return (ENXIO); } return (0); } static uint32_t rk_i2s_dai_set_chanformat(device_t dev, uint32_t format) { return (0); } static int rk_i2s_dai_set_sysclk(device_t dev, unsigned int rate, int dai_dir) { struct rk_i2s_softc *sc; int error; sc = device_get_softc(dev); error = clk_disable(sc->clk); if (error != 0) { device_printf(sc->dev, "could not disable i2s_clk clock\n"); return (error); } error = clk_set_freq(sc->clk, rate, CLK_SET_ROUND_DOWN); if (error != 0) device_printf(sc->dev, "could not set i2s_clk freq\n"); error = clk_enable(sc->clk); if (error != 0) { device_printf(sc->dev, "could not enable i2s_clk clock\n"); return (error); } return (0); } static uint32_t rk_i2s_dai_set_chanspeed(device_t dev, uint32_t speed) { struct rk_i2s_softc *sc; int error; uint32_t val; uint32_t bus_clock_div, lr_clock_div; uint64_t bus_clk_freq; uint64_t clk_freq; sc = device_get_softc(dev); /* Set format */ val = RK_I2S_READ_4(sc, I2S_CKR); if ((val & I2S_CKR_MSS_SLAVE) == 0) { error = clk_get_freq(sc->clk, &clk_freq); if (error != 0) { device_printf(sc->dev, "failed to get clk frequency: err=%d\n", error); return (error); } bus_clk_freq = 2 * 32 * speed; bus_clock_div = DIV_ROUND_CLOSEST(clk_freq, bus_clk_freq); lr_clock_div = bus_clk_freq / speed; val &= ~(I2S_CKR_MDIV_MASK | I2S_CKR_RSD_MASK | I2S_CKR_TSD_MASK); val |= I2S_CKR_MDIV(bus_clock_div); val |= I2S_CKR_RSD(lr_clock_div); val |= I2S_CKR_TSD(lr_clock_div); RK_I2S_WRITE_4(sc, I2S_CKR, val); } return (speed); } static device_method_t rk_i2s_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rk_i2s_probe), DEVMETHOD(device_attach, rk_i2s_attach), DEVMETHOD(device_detach, rk_i2s_detach), DEVMETHOD(audio_dai_init, rk_i2s_dai_init), DEVMETHOD(audio_dai_setup_intr, rk_i2s_dai_setup_intr), DEVMETHOD(audio_dai_set_sysclk, rk_i2s_dai_set_sysclk), DEVMETHOD(audio_dai_set_chanspeed, rk_i2s_dai_set_chanspeed), DEVMETHOD(audio_dai_set_chanformat, rk_i2s_dai_set_chanformat), DEVMETHOD(audio_dai_intr, rk_i2s_dai_intr), DEVMETHOD(audio_dai_get_caps, rk_i2s_dai_get_caps), DEVMETHOD(audio_dai_trigger, rk_i2s_dai_trigger), DEVMETHOD(audio_dai_get_ptr, rk_i2s_dai_get_ptr), DEVMETHOD_END }; static driver_t rk_i2s_driver = { "i2s", rk_i2s_methods, sizeof(struct rk_i2s_softc), }; DRIVER_MODULE(rk_i2s, simplebus, rk_i2s_driver, 0, 0); SIMPLEBUS_PNP_INFO(compat_data); diff --git a/sys/dev/sound/dummy.c b/sys/dev/sound/dummy.c index 39214a141bf9..0a2717ec418c 100644 --- a/sys/dev/sound/dummy.c +++ b/sys/dev/sound/dummy.c @@ -1,392 +1,392 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024-2025 The FreeBSD Foundation * * This software was developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #define DUMMY_NPCHAN 1 #define DUMMY_NRCHAN 1 #define DUMMY_NCHAN (DUMMY_NPCHAN + DUMMY_NRCHAN) struct dummy_chan { struct dummy_softc *sc; struct pcm_channel *chan; struct snd_dbuf *buf; struct pcmchan_caps *caps; uint32_t ptr; int dir; int run; }; struct dummy_softc { struct snddev_info info; device_t dev; uint32_t cap_fmts[4]; struct pcmchan_caps caps; int chnum; struct dummy_chan chans[DUMMY_NCHAN]; struct callout callout; struct mtx *lock; bool stopped; }; static bool dummy_active(struct dummy_softc *sc) { struct dummy_chan *ch; int i; snd_mtxassert(sc->lock); for (i = 0; i < sc->chnum; i++) { ch = &sc->chans[i]; if (ch->run) return (true); } /* No channel is running at the moment. */ return (false); } static void dummy_chan_io(void *arg) { struct dummy_softc *sc = arg; struct dummy_chan *ch; int i = 0; if (sc->stopped) return; /* Do not reschedule if no channel is running. */ if (!dummy_active(sc)) return; for (i = 0; i < sc->chnum; i++) { ch = &sc->chans[i]; if (!ch->run) continue; if (ch->dir == PCMDIR_PLAY) { - ch->ptr += sndbuf_getblksz(ch->buf); - ch->ptr %= sndbuf_getsize(ch->buf); + ch->ptr += ch->buf->blksz; + ch->ptr %= ch->buf->bufsize; } else sndbuf_fillsilence(ch->buf); snd_mtxunlock(sc->lock); chn_intr(ch->chan); snd_mtxlock(sc->lock); } if (!sc->stopped) callout_schedule(&sc->callout, 1); } static int dummy_chan_free(kobj_t obj, void *data) { struct dummy_chan *ch =data; uint8_t *buf; - buf = sndbuf_getbuf(ch->buf); + buf = ch->buf->buf; if (buf != NULL) free(buf, M_DEVBUF); return (0); } static void * dummy_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct dummy_softc *sc; struct dummy_chan *ch; uint8_t *buf; size_t bufsz; sc = devinfo; snd_mtxlock(sc->lock); ch = &sc->chans[sc->chnum++]; ch->sc = sc; ch->dir = dir; ch->chan = c; ch->buf = b; ch->caps = &sc->caps; snd_mtxunlock(sc->lock); bufsz = pcm_getbuffersize(sc->dev, 2048, 2048, 65536); buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO); if (sndbuf_setup(ch->buf, buf, bufsz) != 0) { dummy_chan_free(obj, ch); return (NULL); } return (ch); } static int dummy_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct dummy_chan *ch = data; int i; for (i = 0; ch->caps->fmtlist[i]; i++) if (format == ch->caps->fmtlist[i]) return (0); return (EINVAL); } static uint32_t dummy_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct dummy_chan *ch = data; RANGE(speed, ch->caps->minspeed, ch->caps->maxspeed); return (speed); } static uint32_t dummy_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct dummy_chan *ch = data; - return (sndbuf_getblksz(ch->buf)); + return (ch->buf->blksz); } static int dummy_chan_trigger(kobj_t obj, void *data, int go) { struct dummy_chan *ch = data; struct dummy_softc *sc = ch->sc; snd_mtxlock(sc->lock); if (sc->stopped) { snd_mtxunlock(sc->lock); return (0); } switch (go) { case PCMTRIG_START: ch->ptr = 0; ch->run = 1; callout_reset(&sc->callout, 1, dummy_chan_io, sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; /* If all channels are stopped, stop the callout as well. */ if (!dummy_active(sc)) callout_stop(&sc->callout); default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t dummy_chan_getptr(kobj_t obj, void *data) { struct dummy_chan *ch = data; return (ch->run ? ch->ptr : 0); } static struct pcmchan_caps * dummy_chan_getcaps(kobj_t obj, void *data) { struct dummy_chan *ch = data; return (ch->caps); } static kobj_method_t dummy_chan_methods[] = { KOBJMETHOD(channel_init, dummy_chan_init), KOBJMETHOD(channel_free, dummy_chan_free), KOBJMETHOD(channel_setformat, dummy_chan_setformat), KOBJMETHOD(channel_setspeed, dummy_chan_setspeed), KOBJMETHOD(channel_setblocksize,dummy_chan_setblocksize), KOBJMETHOD(channel_trigger, dummy_chan_trigger), KOBJMETHOD(channel_getptr, dummy_chan_getptr), KOBJMETHOD(channel_getcaps, dummy_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(dummy_chan); static int dummy_mixer_init(struct snd_mixer *m) { struct dummy_softc *sc; sc = mix_getdevinfo(m); if (sc == NULL) return (-1); pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV); mix_setrecdevs(m, SOUND_MASK_RECLEV); return (0); } static int dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { return (0); } static uint32_t dummy_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { return (src == SOUND_MASK_RECLEV ? src : 0); } static kobj_method_t dummy_mixer_methods[] = { KOBJMETHOD(mixer_init, dummy_mixer_init), KOBJMETHOD(mixer_set, dummy_mixer_set), KOBJMETHOD(mixer_setrecsrc, dummy_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(dummy_mixer); static void dummy_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) return; if (BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int dummy_probe(device_t dev) { device_set_desc(dev, "Dummy Audio Device"); return (0); } static int dummy_attach(device_t dev) { struct dummy_softc *sc; char status[SND_STATUSLEN]; int i = 0; sc = device_get_softc(dev); sc->dev = dev; sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_dummy softc"); callout_init_mtx(&sc->callout, sc->lock, 0); sc->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, 2, 0); sc->cap_fmts[1] = SND_FORMAT(AFMT_S24_LE, 2, 0); sc->cap_fmts[2] = SND_FORMAT(AFMT_S16_LE, 2, 0); sc->cap_fmts[3] = 0; sc->caps = (struct pcmchan_caps){ 8000, /* minspeed */ 96000, /* maxspeed */ sc->cap_fmts, /* fmtlist */ 0, /* caps */ }; pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); pcm_init(dev, sc); for (i = 0; i < DUMMY_NPCHAN; i++) pcm_addchan(dev, PCMDIR_PLAY, &dummy_chan_class, sc); for (i = 0; i < DUMMY_NRCHAN; i++) pcm_addchan(dev, PCMDIR_REC, &dummy_chan_class, sc); snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) return (ENXIO); mixer_init(dev, &dummy_mixer_class, sc); /* * Create an alias so that tests do not need to guess which one is the * dummy device if there are more devices present in the system. */ make_dev_alias(sc->info.dsp_dev, "dsp.dummy"); return (0); } static int dummy_detach(device_t dev) { struct dummy_softc *sc = device_get_softc(dev); int err; snd_mtxlock(sc->lock); sc->stopped = true; snd_mtxunlock(sc->lock); callout_drain(&sc->callout); err = pcm_unregister(dev); snd_mtxfree(sc->lock); return (err); } static device_method_t dummy_methods[] = { /* Device interface */ DEVMETHOD(device_identify, dummy_identify), DEVMETHOD(device_probe, dummy_probe), DEVMETHOD(device_attach, dummy_attach), DEVMETHOD(device_detach, dummy_detach), DEVMETHOD_END }; static driver_t dummy_driver = { "pcm", dummy_methods, sizeof(struct dummy_softc), }; DRIVER_MODULE(snd_dummy, nexus, dummy_driver, 0, 0); MODULE_DEPEND(snd_dummy, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_dummy, 1); diff --git a/sys/dev/sound/fdt/audio_soc.c b/sys/dev/sound/fdt/audio_soc.c index c2bdea399364..84867cb3d781 100644 --- a/sys/dev/sound/fdt/audio_soc.c +++ b/sys/dev/sound/fdt/audio_soc.c @@ -1,538 +1,538 @@ /*- * Copyright (c) 2019 Oleksandr Tymoshenko * * 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 AUTHOR ``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 AUTHOR 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. * */ #include #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "audio_dai_if.h" #define AUDIO_BUFFER_SIZE 48000 * 4 struct audio_soc_aux_node { SLIST_ENTRY(audio_soc_aux_node) link; device_t dev; }; struct audio_soc_channel { struct audio_soc_softc *sc; /* parent device's softc */ struct pcm_channel *pcm; /* PCM channel */ struct snd_dbuf *buf; /* PCM buffer */ int dir; /* direction */ }; struct audio_soc_softc { /* * pcm_register assumes that sc is snddev_info, * so this has to be first structure member for "compatibility" */ struct snddev_info info; device_t dev; char *name; struct intr_config_hook init_hook; device_t cpu_dev; device_t codec_dev; SLIST_HEAD(, audio_soc_aux_node) aux_devs; unsigned int mclk_fs; struct audio_soc_channel play_channel; struct audio_soc_channel rec_channel; /* * The format is from the CPU node, for CODEC node clock roles * need to be reversed. */ uint32_t format; uint32_t link_mclk_fs; }; static struct ofw_compat_data compat_data[] = { {"simple-audio-card", 1}, {NULL, 0}, }; static struct { const char *name; unsigned int fmt; } ausoc_dai_formats[] = { { "i2s", AUDIO_DAI_FORMAT_I2S }, { "right_j", AUDIO_DAI_FORMAT_RJ }, { "left_j", AUDIO_DAI_FORMAT_LJ }, { "dsp_a", AUDIO_DAI_FORMAT_DSPA }, { "dsp_b", AUDIO_DAI_FORMAT_DSPB }, { "ac97", AUDIO_DAI_FORMAT_AC97 }, { "pdm", AUDIO_DAI_FORMAT_PDM }, }; static int audio_soc_probe(device_t dev); static int audio_soc_attach(device_t dev); static int audio_soc_detach(device_t dev); /* * Invert master/slave roles for CODEC side of the node */ static uint32_t audio_soc_reverse_clocks(uint32_t format) { int fmt, pol, clk; fmt = AUDIO_DAI_FORMAT_FORMAT(format); pol = AUDIO_DAI_FORMAT_POLARITY(format); clk = AUDIO_DAI_FORMAT_CLOCK(format); switch (clk) { case AUDIO_DAI_CLOCK_CBM_CFM: clk = AUDIO_DAI_CLOCK_CBS_CFS; break; case AUDIO_DAI_CLOCK_CBS_CFM: clk = AUDIO_DAI_CLOCK_CBM_CFS; break; case AUDIO_DAI_CLOCK_CBM_CFS: clk = AUDIO_DAI_CLOCK_CBS_CFM; break; case AUDIO_DAI_CLOCK_CBS_CFS: clk = AUDIO_DAI_CLOCK_CBM_CFM; break; } return AUDIO_DAI_FORMAT(fmt, pol, clk); } static uint32_t audio_soc_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksz) { return (blocksz); } static int audio_soc_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct audio_soc_softc *sc; struct audio_soc_channel *ausoc_chan; ausoc_chan = data; sc = ausoc_chan->sc; return AUDIO_DAI_SET_CHANFORMAT(sc->cpu_dev, format); } static uint32_t audio_soc_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct audio_soc_softc *sc; struct audio_soc_channel *ausoc_chan; uint32_t rate; struct audio_soc_aux_node *aux_node; ausoc_chan = data; sc = ausoc_chan->sc; if (sc->link_mclk_fs) { rate = speed * sc->link_mclk_fs; if (AUDIO_DAI_SET_SYSCLK(sc->cpu_dev, rate, AUDIO_DAI_CLOCK_IN)) device_printf(sc->dev, "failed to set sysclk for CPU node\n"); if (AUDIO_DAI_SET_SYSCLK(sc->codec_dev, rate, AUDIO_DAI_CLOCK_OUT)) device_printf(sc->dev, "failed to set sysclk for codec node\n"); SLIST_FOREACH(aux_node, &sc->aux_devs, link) { if (AUDIO_DAI_SET_SYSCLK(aux_node->dev, rate, AUDIO_DAI_CLOCK_OUT)) device_printf(sc->dev, "failed to set sysclk for aux node\n"); } } /* * Let CPU node determine speed */ speed = AUDIO_DAI_SET_CHANSPEED(sc->cpu_dev, speed); AUDIO_DAI_SET_CHANSPEED(sc->codec_dev, speed); SLIST_FOREACH(aux_node, &sc->aux_devs, link) { AUDIO_DAI_SET_CHANSPEED(aux_node->dev, speed); } return (speed); } static uint32_t audio_soc_chan_getptr(kobj_t obj, void *data) { struct audio_soc_softc *sc; struct audio_soc_channel *ausoc_chan; ausoc_chan = data; sc = ausoc_chan->sc; return AUDIO_DAI_GET_PTR(sc->cpu_dev, ausoc_chan->dir); } static void * audio_soc_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct audio_soc_channel *ausoc_chan; void *buffer; ausoc_chan = devinfo; buffer = malloc(AUDIO_BUFFER_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); if (sndbuf_setup(b, buffer, AUDIO_BUFFER_SIZE) != 0) { free(buffer, M_DEVBUF); return NULL; } ausoc_chan->dir = dir; ausoc_chan->buf = b; ausoc_chan->pcm = c; return (devinfo); } static int audio_soc_chan_trigger(kobj_t obj, void *data, int go) { struct audio_soc_softc *sc; struct audio_soc_channel *ausoc_chan; struct audio_soc_aux_node *aux_node; ausoc_chan = (struct audio_soc_channel *)data; sc = ausoc_chan->sc; AUDIO_DAI_TRIGGER(sc->codec_dev, go, ausoc_chan->dir); SLIST_FOREACH(aux_node, &sc->aux_devs, link) { AUDIO_DAI_TRIGGER(aux_node->dev, go, ausoc_chan->dir); } return AUDIO_DAI_TRIGGER(sc->cpu_dev, go, ausoc_chan->dir); } static int audio_soc_chan_free(kobj_t obj, void *data) { struct audio_soc_channel *ausoc_chan; void *buffer; ausoc_chan = (struct audio_soc_channel *)data; - buffer = sndbuf_getbuf(ausoc_chan->buf); + buffer = ausoc_chan->buf->buf; if (buffer) free(buffer, M_DEVBUF); return (0); } static struct pcmchan_caps * audio_soc_chan_getcaps(kobj_t obj, void *data) { struct audio_soc_softc *sc; struct audio_soc_channel *ausoc_chan; ausoc_chan = data; sc = ausoc_chan->sc; return AUDIO_DAI_GET_CAPS(sc->cpu_dev); } static kobj_method_t audio_soc_chan_methods[] = { KOBJMETHOD(channel_init, audio_soc_chan_init), KOBJMETHOD(channel_free, audio_soc_chan_free), KOBJMETHOD(channel_setformat, audio_soc_chan_setformat), KOBJMETHOD(channel_setspeed, audio_soc_chan_setspeed), KOBJMETHOD(channel_setblocksize,audio_soc_chan_setblocksize), KOBJMETHOD(channel_trigger, audio_soc_chan_trigger), KOBJMETHOD(channel_getptr, audio_soc_chan_getptr), KOBJMETHOD(channel_getcaps, audio_soc_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(audio_soc_chan); static void audio_soc_intr(void *arg) { struct audio_soc_softc *sc; int channel_intr_required; sc = (struct audio_soc_softc *)arg; channel_intr_required = AUDIO_DAI_INTR(sc->cpu_dev, sc->play_channel.buf, sc->rec_channel.buf); if (channel_intr_required & AUDIO_DAI_PLAY_INTR) chn_intr(sc->play_channel.pcm); if (channel_intr_required & AUDIO_DAI_REC_INTR) chn_intr(sc->rec_channel.pcm); } static int audio_soc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "simple-audio-card"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static void audio_soc_init(void *arg) { struct audio_soc_softc *sc; phandle_t node, child; device_t daidev, auxdev; uint32_t xref; uint32_t *aux_devs; int ncells, i; struct audio_soc_aux_node *aux_node; sc = (struct audio_soc_softc *)arg; config_intrhook_disestablish(&sc->init_hook); node = ofw_bus_get_node(sc->dev); /* TODO: handle multi-link nodes */ child = ofw_bus_find_child(node, "simple-audio-card,cpu"); if (child == 0) { device_printf(sc->dev, "cpu node is missing\n"); return; } if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) { device_printf(sc->dev, "missing sound-dai property in cpu node\n"); return; } daidev = OF_device_from_xref(xref); if (daidev == NULL) { device_printf(sc->dev, "no driver attached to cpu node\n"); return; } sc->cpu_dev = daidev; child = ofw_bus_find_child(node, "simple-audio-card,codec"); if (child == 0) { device_printf(sc->dev, "codec node is missing\n"); return; } if ((OF_getencprop(child, "sound-dai", &xref, sizeof(xref))) <= 0) { device_printf(sc->dev, "missing sound-dai property in codec node\n"); return; } daidev = OF_device_from_xref(xref); if (daidev == NULL) { device_printf(sc->dev, "no driver attached to codec node\n"); return; } sc->codec_dev = daidev; /* Add AUX devices */ aux_devs = NULL; ncells = OF_getencprop_alloc_multi(node, "simple-audio-card,aux-devs", sizeof(*aux_devs), (void **)&aux_devs); for (i = 0; i < ncells; i++) { auxdev = OF_device_from_xref(aux_devs[i]); if (auxdev == NULL) device_printf(sc->dev, "warning: no driver attached to aux node\n"); aux_node = malloc(sizeof(*aux_node), M_DEVBUF, M_NOWAIT); if (aux_node == NULL) { device_printf(sc->dev, "failed to allocate aux node struct\n"); return; } aux_node->dev = auxdev; SLIST_INSERT_HEAD(&sc->aux_devs, aux_node, link); } if (aux_devs) OF_prop_free(aux_devs); if (AUDIO_DAI_INIT(sc->cpu_dev, sc->format)) { device_printf(sc->dev, "failed to initialize cpu node\n"); return; } /* Reverse clock roles for CODEC */ if (AUDIO_DAI_INIT(sc->codec_dev, audio_soc_reverse_clocks(sc->format))) { device_printf(sc->dev, "failed to initialize codec node\n"); return; } SLIST_FOREACH(aux_node, &sc->aux_devs, link) { if (AUDIO_DAI_INIT(aux_node->dev, audio_soc_reverse_clocks(sc->format))) { device_printf(sc->dev, "failed to initialize aux node\n"); return; } } pcm_init(sc->dev, sc); sc->play_channel.sc = sc; sc->rec_channel.sc = sc; pcm_addchan(sc->dev, PCMDIR_PLAY, &audio_soc_chan_class, &sc->play_channel); pcm_addchan(sc->dev, PCMDIR_REC, &audio_soc_chan_class, &sc->rec_channel); if (pcm_register(sc->dev, "at simplebus")) { device_printf(sc->dev, "failed to register PCM\n"); return; } AUDIO_DAI_SETUP_INTR(sc->cpu_dev, audio_soc_intr, sc); AUDIO_DAI_SETUP_MIXER(sc->codec_dev, sc->dev); SLIST_FOREACH(aux_node, &sc->aux_devs, link) { AUDIO_DAI_SETUP_MIXER(aux_node->dev, sc->dev); } } static int audio_soc_attach(device_t dev) { struct audio_soc_softc *sc; char *name; phandle_t node, cpu_child; uint32_t xref; int i, ret; char tmp[32]; unsigned int fmt, pol, clk; bool frame_master, bitclock_master; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(dev); ret = OF_getprop_alloc(node, "name", (void **)&name); if (ret == -1) name = "SoC audio"; sc->name = strdup(name, M_DEVBUF); device_set_desc(dev, sc->name); if (ret != -1) OF_prop_free(name); SLIST_INIT(&sc->aux_devs); ret = OF_getprop(node, "simple-audio-card,format", tmp, sizeof(tmp)); if (ret == 0) { for (i = 0; i < nitems(ausoc_dai_formats); i++) { if (strcmp(tmp, ausoc_dai_formats[i].name) == 0) { fmt = ausoc_dai_formats[i].fmt; break; } } if (i == nitems(ausoc_dai_formats)) return (EINVAL); } else fmt = AUDIO_DAI_FORMAT_I2S; if (OF_getencprop(node, "simple-audio-card,mclk-fs", &sc->link_mclk_fs, sizeof(sc->link_mclk_fs)) <= 0) sc->link_mclk_fs = 0; /* Unless specified otherwise, CPU node is the master */ frame_master = bitclock_master = true; cpu_child = ofw_bus_find_child(node, "simple-audio-card,cpu"); if ((OF_getencprop(node, "simple-audio-card,frame-master", &xref, sizeof(xref))) > 0) frame_master = cpu_child == OF_node_from_xref(xref); if ((OF_getencprop(node, "simple-audio-card,bitclock-master", &xref, sizeof(xref))) > 0) bitclock_master = cpu_child == OF_node_from_xref(xref); if (frame_master) { clk = bitclock_master ? AUDIO_DAI_CLOCK_CBM_CFM : AUDIO_DAI_CLOCK_CBS_CFM; } else { clk = bitclock_master ? AUDIO_DAI_CLOCK_CBM_CFS : AUDIO_DAI_CLOCK_CBS_CFS; } bool bitclock_inversion = OF_hasprop(node, "simple-audio-card,bitclock-inversion"); bool frame_inversion = OF_hasprop(node, "simple-audio-card,frame-inversion"); if (bitclock_inversion) { pol = frame_inversion ? AUDIO_DAI_POLARITY_IB_IF : AUDIO_DAI_POLARITY_IB_NF; } else { pol = frame_inversion ? AUDIO_DAI_POLARITY_NB_IF : AUDIO_DAI_POLARITY_NB_NF; } sc->format = AUDIO_DAI_FORMAT(fmt, pol, clk); sc->init_hook.ich_func = audio_soc_init; sc->init_hook.ich_arg = sc; if (config_intrhook_establish(&sc->init_hook) != 0) return (ENOMEM); return (0); } static int audio_soc_detach(device_t dev) { struct audio_soc_softc *sc; struct audio_soc_aux_node *aux; sc = device_get_softc(dev); if (sc->name) free(sc->name, M_DEVBUF); while ((aux = SLIST_FIRST(&sc->aux_devs)) != NULL) { SLIST_REMOVE_HEAD(&sc->aux_devs, link); free(aux, M_DEVBUF); } return (0); } static device_method_t audio_soc_methods[] = { /* device_if methods */ DEVMETHOD(device_probe, audio_soc_probe), DEVMETHOD(device_attach, audio_soc_attach), DEVMETHOD(device_detach, audio_soc_detach), DEVMETHOD_END, }; static driver_t audio_soc_driver = { "pcm", audio_soc_methods, sizeof(struct audio_soc_softc), }; DRIVER_MODULE(audio_soc, simplebus, audio_soc_driver, NULL, NULL); MODULE_VERSION(audio_soc, 1); diff --git a/sys/dev/sound/macio/aoa.c b/sys/dev/sound/macio/aoa.c index 9861bbd92a0c..e0a6206c19a6 100644 --- a/sys/dev/sound/macio/aoa.c +++ b/sys/dev/sound/macio/aoa.c @@ -1,386 +1,386 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2008 by Marco Trillo. All rights reserved. * * 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 AUTHOR ``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 AUTHOR 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. */ /* * Apple Onboard Audio (AOA). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "mixer_if.h" struct aoa_dma { struct mtx mutex; struct resource *reg; /* DBDMA registers */ dbdma_channel_t *channel; /* DBDMA channel */ bus_dma_tag_t tag; /* bus_dma tag */ struct pcm_channel *pcm; /* PCM channel */ struct snd_dbuf *buf; /* PCM buffer */ u_int slots; /* # of slots */ u_int slot; /* current slot */ u_int bufsz; /* buffer size */ u_int blksz; /* block size */ int running; }; static void aoa_dma_set_program(struct aoa_dma *dma) { u_int32_t addr; int i; - addr = (u_int32_t) sndbuf_getbufaddr(dma->buf); - KASSERT(dma->bufsz == sndbuf_getsize(dma->buf), ("bad size")); + addr = (u_int32_t)dma->buf->buf_addr; + KASSERT(dma->bufsz == dma->buf->bufsize, ("bad size")); dma->slots = dma->bufsz / dma->blksz; for (i = 0; i < dma->slots; ++i) { dbdma_insert_command(dma->channel, i, /* slot */ DBDMA_OUTPUT_MORE, /* command */ 0, /* stream */ addr, /* data */ dma->blksz, /* count */ DBDMA_ALWAYS, /* interrupt */ DBDMA_COND_TRUE, /* branch */ DBDMA_NEVER, /* wait */ dma->slots + 1 /* branch_slot */ ); addr += dma->blksz; } /* Branch back to beginning. */ dbdma_insert_branch(dma->channel, dma->slots, 0); /* STOP command to branch when S0 is asserted. */ dbdma_insert_stop(dma->channel, dma->slots + 1); /* Set S0 as the condition to branch to STOP. */ dbdma_set_branch_selector(dma->channel, 1 << 0, 1 << 0); dbdma_set_device_status(dma->channel, 1 << 0, 0); dbdma_sync_commands(dma->channel, BUS_DMASYNC_PREWRITE); } #define AOA_BUFFER_SIZE 65536 static struct aoa_dma * aoa_dma_create(struct aoa_softc *sc) { struct aoa_dma *dma; bus_dma_tag_t tag; int err; device_t self; self = sc->sc_dev; err = bus_dma_tag_create(bus_get_dma_tag(self), 4, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, AOA_BUFFER_SIZE, 1, AOA_BUFFER_SIZE, 0, NULL, NULL, &tag); if (err != 0) return (NULL); dma = malloc(sizeof(*dma), M_DEVBUF, M_WAITOK | M_ZERO); dma->tag = tag; dma->bufsz = AOA_BUFFER_SIZE; dma->blksz = PAGE_SIZE; /* initial blocksize */ mtx_init(&dma->mutex, "AOA", NULL, MTX_DEF); sc->sc_intrp = dma; return (dma); } static void aoa_dma_delete(struct aoa_dma *dma) { bus_dma_tag_destroy(dma->tag); mtx_destroy(&dma->mutex); free(dma, M_DEVBUF); } static u_int32_t aoa_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksz) { struct aoa_dma *dma = data; int err, lz; DPRINTF(("aoa_chan_setblocksize: blocksz = %u, dma->blksz = %u\n", blocksz, dma->blksz)); KASSERT(!dma->running, ("dma is running")); KASSERT(blocksz > 0, ("bad blocksz")); /* Round blocksz down to a power of two... */ __asm volatile ("cntlzw %0,%1" : "=r"(lz) : "r"(blocksz)); blocksz = 1 << (31 - lz); DPRINTF(("blocksz = %u\n", blocksz)); /* ...but no more than the buffer. */ if (blocksz > dma->bufsz) blocksz = dma->bufsz; err = sndbuf_resize(dma->buf, dma->bufsz / blocksz, blocksz); if (err != 0) { DPRINTF(("sndbuf_resize returned %d\n", err)); return (0); } if (blocksz == dma->blksz) return (dma->blksz); /* One slot per block plus branch to 0 plus STOP. */ err = dbdma_resize_channel(dma->channel, 2 + dma->bufsz / blocksz); if (err != 0) { DPRINTF(("dbdma_resize_channel returned %d\n", err)); return (0); } /* Set the new blocksize. */ dma->blksz = blocksz; aoa_dma_set_program(dma); return (dma->blksz); } static int aoa_chan_setformat(kobj_t obj, void *data, u_int32_t format) { DPRINTF(("aoa_chan_setformat: format = %u\n", format)); if (format != SND_FORMAT(AFMT_S16_BE, 2, 0)) return (EINVAL); return (0); } static u_int32_t aoa_chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { DPRINTF(("aoa_chan_setspeed: speed = %u\n", speed)); return (44100); } static u_int32_t aoa_chan_getptr(kobj_t obj, void *data) { struct aoa_dma *dma = data; if (!dma->running) return (0); return (dma->slot * dma->blksz); } static void * aoa_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct aoa_softc *sc = devinfo; struct aoa_dma *dma; int max_slots, err; KASSERT(dir == PCMDIR_PLAY, ("bad dir")); dma = aoa_dma_create(sc); if (!dma) return (NULL); dma->pcm = c; dma->buf = b; dma->reg = sc->sc_odma; /* One slot per block, plus branch to 0 plus STOP. */ max_slots = 2 + dma->bufsz / dma->blksz; err = dbdma_allocate_channel(dma->reg, 0, bus_get_dma_tag(sc->sc_dev), max_slots, &dma->channel ); if (err != 0) { aoa_dma_delete(dma); return (NULL); } if (sndbuf_alloc(dma->buf, dma->tag, 0, dma->bufsz) != 0) { dbdma_free_channel(dma->channel); aoa_dma_delete(dma); return (NULL); } aoa_dma_set_program(dma); return (dma); } static int aoa_chan_trigger(kobj_t obj, void *data, int go) { struct aoa_dma *dma = data; int i; switch (go) { case PCMTRIG_START: /* Start the DMA. */ dma->running = 1; dma->slot = 0; dbdma_set_current_cmd(dma->channel, dma->slot); dbdma_run(dma->channel); return (0); case PCMTRIG_STOP: case PCMTRIG_ABORT: mtx_lock(&dma->mutex); dma->running = 0; /* Make it branch to the STOP command. */ dbdma_set_device_status(dma->channel, 1 << 0, 1 << 0); /* XXX should wait for DBDMA_ACTIVE to clear. */ DELAY(40000); /* Reset the DMA. */ dbdma_stop(dma->channel); dbdma_set_device_status(dma->channel, 1 << 0, 0); for (i = 0; i < dma->slots; ++i) dbdma_clear_cmd_status(dma->channel, i); mtx_unlock(&dma->mutex); return (0); } return (0); } static int aoa_chan_free(kobj_t obj, void *data) { struct aoa_dma *dma = data; sndbuf_free(dma->buf); dbdma_free_channel(dma->channel); aoa_dma_delete(dma); return (0); } void aoa_interrupt(void *xsc) { struct aoa_softc *sc = xsc; struct aoa_dma *dma; if (!(dma = sc->sc_intrp) || !dma->running) return; mtx_lock(&dma->mutex); while (dbdma_get_cmd_status(dma->channel, dma->slot)) { dbdma_clear_cmd_status(dma->channel, dma->slot); dma->slot = (dma->slot + 1) % dma->slots; mtx_unlock(&dma->mutex); chn_intr(dma->pcm); mtx_lock(&dma->mutex); } mtx_unlock(&dma->mutex); } static u_int32_t sc_fmt[] = { SND_FORMAT(AFMT_S16_BE, 2, 0), 0 }; static struct pcmchan_caps aoa_caps = {44100, 44100, sc_fmt, 0}; static struct pcmchan_caps * aoa_chan_getcaps(kobj_t obj, void *data) { return (&aoa_caps); } static kobj_method_t aoa_chan_methods[] = { KOBJMETHOD(channel_init, aoa_chan_init), KOBJMETHOD(channel_free, aoa_chan_free), KOBJMETHOD(channel_setformat, aoa_chan_setformat), KOBJMETHOD(channel_setspeed, aoa_chan_setspeed), KOBJMETHOD(channel_setblocksize,aoa_chan_setblocksize), KOBJMETHOD(channel_trigger, aoa_chan_trigger), KOBJMETHOD(channel_getptr, aoa_chan_getptr), KOBJMETHOD(channel_getcaps, aoa_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(aoa_chan); int aoa_attach(void *xsc) { char status[SND_STATUSLEN]; struct aoa_softc *sc; device_t self; int err __unused; sc = xsc; self = sc->sc_dev; pcm_init(self, sc); err = pcm_getbuffersize(self, AOA_BUFFER_SIZE, AOA_BUFFER_SIZE, AOA_BUFFER_SIZE); DPRINTF(("pcm_getbuffersize returned %d\n", err)); pcm_addchan(self, PCMDIR_PLAY, &aoa_chan_class, sc); snprintf(status, sizeof(status), "at %s", ofw_bus_get_name(self)); return (pcm_register(self, status)); } diff --git a/sys/dev/sound/pci/als4000.c b/sys/dev/sound/pci/als4000.c index 9d86713b379e..75fbefe27633 100644 --- a/sys/dev/sound/pci/als4000.c +++ b/sys/dev/sound/pci/als4000.c @@ -1,941 +1,941 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Orion Hodson * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * als4000.c - driver for the Avance Logic ALS 4000 chipset. * * The ALS4000 is effectively an SB16 with a PCI interface. * * This driver derives from ALS4000a.PDF, Bart Hartgers alsa driver, and * SB16 register descriptions. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include "mixer_if.h" /* Debugging macro's */ #undef DEB #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ #define ALS_DEFAULT_BUFSZ 16384 /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t format, speed, phys_buf, bps; u_int32_t dma_active:1, dma_was_active:1; u_int8_t gcr_fifo_status; int dir; }; struct sc_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regid, irqid; void *ih; struct mtx *lock; unsigned int bufsz; struct sc_chinfo pch, rch; }; /* Channel caps */ static u_int32_t als_format[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; /* * I don't believe this rotten soundcard can do 48k, really, * trust me. */ static struct pcmchan_caps als_caps = { 4000, 44100, als_format, 0 }; /* ------------------------------------------------------------------------- */ /* Register Utilities */ static u_int32_t als_gcr_rd(struct sc_info *sc, int index) { bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); return bus_space_read_4(sc->st, sc->sh, ALS_GCR_DATA); } static void als_gcr_wr(struct sc_info *sc, int index, int data) { bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); bus_space_write_4(sc->st, sc->sh, ALS_GCR_DATA, data); } static u_int8_t als_intr_rd(struct sc_info *sc) { return bus_space_read_1(sc->st, sc->sh, ALS_SB_MPU_IRQ); } static void als_intr_wr(struct sc_info *sc, u_int8_t data) { bus_space_write_1(sc->st, sc->sh, ALS_SB_MPU_IRQ, data); } static u_int8_t als_mix_rd(struct sc_info *sc, u_int8_t index) { bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); return bus_space_read_1(sc->st, sc->sh, ALS_MIXER_DATA); } static void als_mix_wr(struct sc_info *sc, u_int8_t index, u_int8_t data) { bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); bus_space_write_1(sc->st, sc->sh, ALS_MIXER_DATA, data); } static void als_esp_wr(struct sc_info *sc, u_int8_t data) { u_int32_t tries, v; tries = 1000; do { v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_WR_STATUS); if (~v & 0x80) break; DELAY(20); } while (--tries != 0); if (tries == 0) device_printf(sc->dev, "als_esp_wr timeout"); bus_space_write_1(sc->st, sc->sh, ALS_ESP_WR_DATA, data); } static int als_esp_reset(struct sc_info *sc) { u_int32_t tries, u, v; bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 1); DELAY(10); bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 0); DELAY(30); tries = 1000; do { u = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_STATUS8); if (u & 0x80) { v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_DATA); if (v == 0xaa) return 0; else break; } DELAY(20); } while (--tries != 0); if (tries == 0) device_printf(sc->dev, "als_esp_reset timeout"); return 1; } static u_int8_t als_ack_read(struct sc_info *sc, u_int8_t addr) { u_int8_t r = bus_space_read_1(sc->st, sc->sh, addr); return r; } /* ------------------------------------------------------------------------- */ /* Common pcm channel implementation */ static void * alschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; snd_mtxlock(sc->lock); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->gcr_fifo_status = ALS_GCR_FIFO0_STATUS; } else { ch = &sc->rch; ch->gcr_fifo_status = ALS_GCR_FIFO1_STATUS; } ch->dir = dir; ch->parent = sc; ch->channel = c; ch->bps = 1; ch->format = SND_FORMAT(AFMT_U8, 1, 0); ch->speed = DSP_DEFAULT_SPEED; ch->buffer = b; snd_mtxunlock(sc->lock); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; return ch; } static int alschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->format = format; return 0; } static u_int32_t alschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data, *other; struct sc_info *sc = ch->parent; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; /* Deny request if other dma channel is active */ if (other->dma_active) { ch->speed = other->speed; return other->speed; } ch->speed = speed; return speed; } static u_int32_t alschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (blocksize > sc->bufsz / 2) { blocksize = sc->bufsz / 2; } sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static u_int32_t alschan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int32_t pos, sz; snd_mtxlock(sc->lock); pos = als_gcr_rd(ch->parent, ch->gcr_fifo_status) & 0xffff; snd_mtxunlock(sc->lock); - sz = sndbuf_getsize(ch->buffer); + sz = ch->buffer->bufsize; return (2 * sz - pos - 1) % sz; } static struct pcmchan_caps* alschan_getcaps(kobj_t obj, void *data) { return &als_caps; } static void als_set_speed(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; struct sc_chinfo *other; other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; if (other->dma_active == 0) { als_esp_wr(sc, ALS_ESP_SAMPLE_RATE); als_esp_wr(sc, ch->speed >> 8); als_esp_wr(sc, ch->speed & 0xff); } else { DEB(printf("speed locked at %d (tried %d)\n", other->speed, ch->speed)); } } /* ------------------------------------------------------------------------- */ /* Playback channel implementation */ #define ALS_8BIT_CMD(x, y) { (x), (y), DSP_DMA8, DSP_CMD_DMAPAUSE_8 } #define ALS_16BIT_CMD(x, y) { (x), (y), DSP_DMA16, DSP_CMD_DMAPAUSE_16 } struct playback_command { u_int32_t pcm_format; /* newpcm format */ u_int8_t format_val; /* sb16 format value */ u_int8_t dma_prog; /* sb16 dma program */ u_int8_t dma_stop; /* sb16 stop register */ } static const playback_cmds[] = { ALS_8BIT_CMD(SND_FORMAT(AFMT_U8, 1, 0), DSP_MODE_U8MONO), ALS_8BIT_CMD(SND_FORMAT(AFMT_U8, 2, 0), DSP_MODE_U8STEREO), ALS_16BIT_CMD(SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_MODE_S16MONO), ALS_16BIT_CMD(SND_FORMAT(AFMT_S16_LE, 2, 0), DSP_MODE_S16STEREO), }; static const struct playback_command* als_get_playback_command(u_int32_t format) { u_int32_t i, n; n = sizeof(playback_cmds) / sizeof(playback_cmds[0]); for (i = 0; i < n; i++) { if (playback_cmds[i].pcm_format == format) { return &playback_cmds[i]; } } DEB(printf("als_get_playback_command: invalid format 0x%08x\n", format)); return &playback_cmds[0]; } static void als_playback_start(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; - buf = sndbuf_getbufaddr(ch->buffer); - bufsz = sndbuf_getsize(ch->buffer); + buf = ch->buffer->buf_addr; + bufsz = ch->buffer->bufsize; count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_DMA0_START, buf); als_gcr_wr(sc, ALS_GCR_DMA0_MODE, (bufsz - 1) | 0x180000); p = als_get_playback_command(ch->format); dma_prog = p->dma_prog | DSP_F16_DAC | DSP_F16_AUTO | DSP_F16_FIFO_ON; als_esp_wr(sc, dma_prog); als_esp_wr(sc, p->format_val); als_esp_wr(sc, count & 0xff); als_esp_wr(sc, count >> 8); ch->dma_active = 1; } static int als_playback_stop(struct sc_chinfo *ch) { const struct playback_command *p; struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { p = als_get_playback_command(ch->format); als_esp_wr(sc, p->dma_stop); } ch->dma_active = 0; return active; } static int alspchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_playback_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: als_playback_stop(ch); break; default: break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alspchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alspchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(alspchan); /* ------------------------------------------------------------------------- */ /* Capture channel implementation */ static u_int8_t als_get_fifo_format(struct sc_info *sc, u_int32_t format) { switch (format) { case SND_FORMAT(AFMT_U8, 1, 0): return ALS_FIFO1_8BIT; case SND_FORMAT(AFMT_U8, 2, 0): return ALS_FIFO1_8BIT | ALS_FIFO1_STEREO; case SND_FORMAT(AFMT_S16_LE, 1, 0): return ALS_FIFO1_SIGNED; case SND_FORMAT(AFMT_S16_LE, 2, 0): return ALS_FIFO1_SIGNED | ALS_FIFO1_STEREO; } device_printf(sc->dev, "format not found: 0x%08x\n", format); return ALS_FIFO1_8BIT; } static void als_capture_start(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t buf, bufsz, count, dma_prog; - buf = sndbuf_getbufaddr(ch->buffer); - bufsz = sndbuf_getsize(ch->buffer); + buf = ch->buffer->buf_addr; + bufsz = ch->buffer->bufsize; count = bufsz / 2; if (ch->format & AFMT_16BIT) count /= 2; count--; als_esp_wr(sc, DSP_CMD_SPKON); als_set_speed(ch); als_gcr_wr(sc, ALS_GCR_FIFO1_START, buf); als_gcr_wr(sc, ALS_GCR_FIFO1_COUNT, (bufsz - 1)); als_mix_wr(sc, ALS_FIFO1_LENGTH_LO, count & 0xff); als_mix_wr(sc, ALS_FIFO1_LENGTH_HI, count >> 8); dma_prog = ALS_FIFO1_RUN | als_get_fifo_format(sc, ch->format); als_mix_wr(sc, ALS_FIFO1_CONTROL, dma_prog); ch->dma_active = 1; } static int als_capture_stop(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t active; active = ch->dma_active; if (active) { als_mix_wr(sc, ALS_FIFO1_CONTROL, ALS_FIFO1_STOP); } ch->dma_active = 0; return active; } static int alsrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_capture_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: als_capture_stop(ch); break; } snd_mtxunlock(sc->lock); return 0; } static kobj_method_t alsrchan_methods[] = { KOBJMETHOD(channel_init, alschan_init), KOBJMETHOD(channel_setformat, alschan_setformat), KOBJMETHOD(channel_setspeed, alschan_setspeed), KOBJMETHOD(channel_setblocksize, alschan_setblocksize), KOBJMETHOD(channel_trigger, alsrchan_trigger), KOBJMETHOD(channel_getptr, alschan_getptr), KOBJMETHOD(channel_getcaps, alschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(alsrchan); /* ------------------------------------------------------------------------- */ /* Mixer related */ /* * ALS4000 has an sb16 mixer, with some additional controls that we do * not yet a means to support. */ struct sb16props { u_int8_t lreg; u_int8_t rreg; u_int8_t bits; u_int8_t oselect; u_int8_t iselect; /* left input mask */ } static const amt[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { 0x30, 0x31, 5, 0x00, 0x00 }, [SOUND_MIXER_PCM] = { 0x32, 0x33, 5, 0x00, 0x00 }, [SOUND_MIXER_SYNTH] = { 0x34, 0x35, 5, 0x60, 0x40 }, [SOUND_MIXER_CD] = { 0x36, 0x37, 5, 0x06, 0x04 }, [SOUND_MIXER_LINE] = { 0x38, 0x39, 5, 0x18, 0x10 }, [SOUND_MIXER_MIC] = { 0x3a, 0x00, 5, 0x01, 0x01 }, [SOUND_MIXER_SPEAKER] = { 0x3b, 0x00, 2, 0x00, 0x00 }, [SOUND_MIXER_IGAIN] = { 0x3f, 0x40, 2, 0x00, 0x00 }, [SOUND_MIXER_OGAIN] = { 0x41, 0x42, 2, 0x00, 0x00 }, /* The following have register values but no h/w implementation */ [SOUND_MIXER_TREBLE] = { 0x44, 0x45, 4, 0x00, 0x00 }, [SOUND_MIXER_BASS] = { 0x46, 0x47, 4, 0x00, 0x00 } }; static int alsmix_init(struct snd_mixer *m) { u_int32_t i, v; for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (amt[i].bits) v |= 1 << i; } mix_setdevs(m, v); for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (amt[i].iselect) v |= 1 << i; } mix_setrecdevs(m, v); return 0; } static int alsmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t r, l, v, mask; /* Fill upper n bits in mask with 1's */ mask = ((1 << amt[dev].bits) - 1) << (8 - amt[dev].bits); l = (left * mask / 100) & mask; v = als_mix_rd(sc, amt[dev].lreg) & ~mask; als_mix_wr(sc, amt[dev].lreg, l | v); if (amt[dev].rreg) { r = (right * mask / 100) & mask; v = als_mix_rd(sc, amt[dev].rreg) & ~mask; als_mix_wr(sc, amt[dev].rreg, r | v); } else { r = 0; } /* Zero gain does not mute channel from output, but this does. */ v = als_mix_rd(sc, SB16_OMASK); if (l == 0 && r == 0) { v &= ~amt[dev].oselect; } else { v |= amt[dev].oselect; } als_mix_wr(sc, SB16_OMASK, v); return 0; } static u_int32_t alsmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, l, r; for (i = l = r = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (src & (1 << i)) { if (amt[i].iselect == 1) { /* microphone */ l |= amt[i].iselect; r |= amt[i].iselect; } else { l |= amt[i].iselect; r |= amt[i].iselect >> 1; } } } als_mix_wr(sc, SB16_IMASK_L, l); als_mix_wr(sc, SB16_IMASK_R, r); return src; } static kobj_method_t als_mixer_methods[] = { KOBJMETHOD(mixer_init, alsmix_init), KOBJMETHOD(mixer_set, alsmix_set), KOBJMETHOD(mixer_setrecsrc, alsmix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(als_mixer); /* ------------------------------------------------------------------------- */ /* Interrupt Handler */ static void als_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int8_t intr, sb_status; snd_mtxlock(sc->lock); intr = als_intr_rd(sc); if (intr & 0x80) { snd_mtxunlock(sc->lock); chn_intr(sc->pch.channel); snd_mtxlock(sc->lock); } if (intr & 0x40) { snd_mtxunlock(sc->lock); chn_intr(sc->rch.channel); snd_mtxlock(sc->lock); } /* ACK interrupt in PCI core */ als_intr_wr(sc, intr); /* ACK interrupt in SB core */ sb_status = als_mix_rd(sc, IRQ_STAT); if (sb_status & ALS_IRQ_STATUS8) als_ack_read(sc, ALS_ESP_RD_STATUS8); if (sb_status & ALS_IRQ_STATUS16) als_ack_read(sc, ALS_ESP_RD_STATUS16); if (sb_status & ALS_IRQ_MPUIN) als_ack_read(sc, ALS_MIDI_DATA); if (sb_status & ALS_IRQ_CR1E) als_ack_read(sc, ALS_CR1E_ACK_PORT); snd_mtxunlock(sc->lock); return; } /* ------------------------------------------------------------------------- */ /* H/W initialization */ static int als_init(struct sc_info *sc) { u_int32_t i, v; /* Reset Chip */ if (als_esp_reset(sc)) { return 1; } /* Enable write on DMA_SETUP register */ v = als_mix_rd(sc, ALS_SB16_CONFIG); als_mix_wr(sc, ALS_SB16_CONFIG, v | 0x80); /* Select DMA0 */ als_mix_wr(sc, ALS_SB16_DMA_SETUP, 0x01); /* Disable write on DMA_SETUP register */ als_mix_wr(sc, ALS_SB16_CONFIG, v & 0x7f); /* Enable interrupts */ v = als_gcr_rd(sc, ALS_GCR_MISC); als_gcr_wr(sc, ALS_GCR_MISC, v | 0x28000); /* Black out GCR DMA registers */ for (i = 0x91; i <= 0x96; i++) { als_gcr_wr(sc, i, 0); } /* Emulation mode */ v = als_gcr_rd(sc, ALS_GCR_DMA_EMULATION); als_gcr_wr(sc, ALS_GCR_DMA_EMULATION, v); DEB(printf("GCR_DMA_EMULATION 0x%08x\n", v)); return 0; } static void als_uninit(struct sc_info *sc) { /* Disable interrupts */ als_gcr_wr(sc, ALS_GCR_MISC, 0); } /* ------------------------------------------------------------------------- */ /* Probe and attach card */ static int als_pci_probe(device_t dev) { if (pci_get_devid(dev) == ALS_PCI_ID0) { device_set_desc(dev, "Avance Logic ALS4000"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void als_resource_free(device_t dev, struct sc_info *sc) { if (sc->reg) { bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); sc->reg = NULL; } if (sc->ih) { bus_teardown_intr(dev, sc->irq, sc->ih); sc->ih = NULL; } if (sc->irq) { bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } } static int als_resource_grab(device_t dev, struct sc_info *sc) { sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->regid, RF_ACTIVE); if (sc->reg == NULL) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (sc->irq == NULL) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, als_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, ALS_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } return 0; bad: als_resource_free(dev, sc); return ENXIO; } static int als_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_als4000 softc"); sc->dev = dev; pci_enable_busmaster(dev); /* * By default the power to the various components on the * ALS4000 is entirely controlled by the pci powerstate. We * could attempt finer grained control by setting GCR6.31. */ if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } if (als_resource_grab(dev, sc)) { device_printf(dev, "failed to allocate resources\n"); goto bad_attach; } if (als_init(sc)) { device_printf(dev, "failed to initialize hardware\n"); goto bad_attach; } if (mixer_init(dev, &als_mixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad_attach; } pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &alspchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &alsrchan_class, sc); snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) { device_printf(dev, "failed to register pcm entries\n"); goto bad_attach; } return 0; bad_attach: als_resource_free(dev, sc); free(sc, M_DEVBUF); return ENXIO; } static int als_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); als_uninit(sc); als_resource_free(dev, sc); free(sc, M_DEVBUF); return 0; } static int als_pci_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = als_playback_stop(&sc->pch); sc->rch.dma_was_active = als_capture_stop(&sc->rch); als_uninit(sc); snd_mtxunlock(sc->lock); return 0; } static int als_pci_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); if (als_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) != 0) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { als_playback_start(&sc->pch); } if (sc->rch.dma_was_active) { als_capture_start(&sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t als_methods[] = { /* Device interface */ DEVMETHOD(device_probe, als_pci_probe), DEVMETHOD(device_attach, als_pci_attach), DEVMETHOD(device_detach, als_pci_detach), DEVMETHOD(device_suspend, als_pci_suspend), DEVMETHOD(device_resume, als_pci_resume), { 0, 0 } }; static driver_t als_driver = { "pcm", als_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_als4000, pci, als_driver, 0, 0); MODULE_DEPEND(snd_als4000, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_als4000, 1); diff --git a/sys/dev/sound/pci/atiixp.c b/sys/dev/sound/pci/atiixp.c index 90e5742e6523..30f061fd9388 100644 --- a/sys/dev/sound/pci/atiixp.c +++ b/sys/dev/sound/pci/atiixp.c @@ -1,1420 +1,1419 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * FreeBSD pcm driver for ATI IXP 150/200/250/300 AC97 controllers * * Features * * 16bit playback / recording * * 32bit native playback - yay! * * 32bit native recording (seems broken on few hardwares) * * Issues / TODO: * * SPDIF * * Support for more than 2 channels. * * VRA ? VRM ? DRA ? * * 32bit native recording seems broken on few hardwares, most * probably because of incomplete VRA/DRA cleanup. * * * Thanks goes to: * * Shaharil @ SCAN Associates whom relentlessly providing me the * mind blowing Acer Ferrari 4002 WLMi with this ATI IXP hardware. * * Reinoud Zandijk (auixp), which this driver is * largely based upon although large part of it has been reworked. His * driver is the primary reference and pretty much well documented. * * Takashi Iwai (ALSA snd-atiixp), for register definitions and some * random ninja hackery. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #define ATI_IXP_DMA_RETRY_MAX 100 #define ATI_IXP_BUFSZ_MIN 4096 #define ATI_IXP_BUFSZ_MAX 65536 #define ATI_IXP_BUFSZ_DEFAULT 16384 #define ATI_IXP_BLK_MIN 32 #define ATI_IXP_BLK_ALIGN (~(ATI_IXP_BLK_MIN - 1)) #define ATI_IXP_CHN_RUNNING 0x00000001 #define ATI_IXP_CHN_SUSPEND 0x00000002 struct atiixp_dma_op { volatile uint32_t addr; volatile uint16_t status; volatile uint16_t size; volatile uint32_t next; }; struct atiixp_info; struct atiixp_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct atiixp_info *parent; struct atiixp_dma_op *sgd_table; bus_addr_t sgd_addr; uint32_t enable_bit, flush_bit, linkptr_bit, dt_cur_bit; uint32_t blksz, blkcnt; uint32_t ptr, prevptr; uint32_t fmt; uint32_t flags; int caps_32bit, dir; }; struct atiixp_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; struct ac97_info *codec; struct atiixp_chinfo pch; struct atiixp_chinfo rch; struct atiixp_dma_op *sgd_table; struct intr_config_hook delayed_attach; uint32_t bufsz; uint32_t codec_not_ready_bits, codec_idx, codec_found; uint32_t blkcnt; int registered_channels; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; #define atiixp_rd(_sc, _reg) \ bus_space_read_4((_sc)->st, (_sc)->sh, _reg) #define atiixp_wr(_sc, _reg, _val) \ bus_space_write_4((_sc)->st, (_sc)->sh, _reg, _val) #define atiixp_lock(_sc) snd_mtxlock((_sc)->lock) #define atiixp_unlock(_sc) snd_mtxunlock((_sc)->lock) #define atiixp_assert(_sc) snd_mtxassert((_sc)->lock) static uint32_t atiixp_fmt_32bit[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static uint32_t atiixp_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps atiixp_caps_32bit = { ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt_32bit, 0 }; static struct pcmchan_caps atiixp_caps = { ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt, 0 }; static const struct { uint16_t vendor; uint16_t devid; char *desc; } atiixp_hw[] = { { ATI_VENDOR_ID, ATI_IXP_200_ID, "ATI IXP 200" }, { ATI_VENDOR_ID, ATI_IXP_300_ID, "ATI IXP 300" }, { ATI_VENDOR_ID, ATI_IXP_400_ID, "ATI IXP 400" }, { ATI_VENDOR_ID, ATI_IXP_SB600_ID, "ATI IXP SB600" }, }; static void atiixp_enable_interrupts(struct atiixp_info *); static void atiixp_disable_interrupts(struct atiixp_info *); static void atiixp_reset_aclink(struct atiixp_info *); static void atiixp_flush_dma(struct atiixp_chinfo *); static void atiixp_enable_dma(struct atiixp_chinfo *); static void atiixp_disable_dma(struct atiixp_chinfo *); static int atiixp_waitready_codec(struct atiixp_info *); static int atiixp_rdcd(kobj_t, void *, int); static int atiixp_wrcd(kobj_t, void *, int, uint32_t); static void *atiixp_chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int atiixp_chan_setformat(kobj_t, void *, uint32_t); static uint32_t atiixp_chan_setspeed(kobj_t, void *, uint32_t); static int atiixp_chan_setfragments(kobj_t, void *, uint32_t, uint32_t); static uint32_t atiixp_chan_setblocksize(kobj_t, void *, uint32_t); static void atiixp_buildsgdt(struct atiixp_chinfo *); static int atiixp_chan_trigger(kobj_t, void *, int); static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *); static uint32_t atiixp_chan_getptr(kobj_t, void *); static struct pcmchan_caps *atiixp_chan_getcaps(kobj_t, void *); static void atiixp_intr(void *); static void atiixp_dma_cb(void *, bus_dma_segment_t *, int, int); static void atiixp_chip_pre_init(struct atiixp_info *); static void atiixp_chip_post_init(void *); static void atiixp_release_resource(struct atiixp_info *); static int atiixp_pci_probe(device_t); static int atiixp_pci_attach(device_t); static int atiixp_pci_detach(device_t); static int atiixp_pci_suspend(device_t); static int atiixp_pci_resume(device_t); /* * ATI IXP helper functions */ static void atiixp_enable_interrupts(struct atiixp_info *sc) { uint32_t value; /* clear all pending */ atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); /* enable all relevant interrupt sources we can handle */ value = atiixp_rd(sc, ATI_REG_IER); value |= ATI_REG_IER_IO_STATUS_EN; /* * Disable / ignore internal xrun/spdf interrupt flags * since it doesn't interest us (for now). */ #if 1 value &= ~(ATI_REG_IER_IN_XRUN_EN | ATI_REG_IER_OUT_XRUN_EN | ATI_REG_IER_SPDF_XRUN_EN | ATI_REG_IER_SPDF_STATUS_EN); #else value |= ATI_REG_IER_IN_XRUN_EN; value |= ATI_REG_IER_OUT_XRUN_EN; value |= ATI_REG_IER_SPDF_XRUN_EN; value |= ATI_REG_IER_SPDF_STATUS_EN; #endif atiixp_wr(sc, ATI_REG_IER, value); } static void atiixp_disable_interrupts(struct atiixp_info *sc) { /* disable all interrupt sources */ atiixp_wr(sc, ATI_REG_IER, 0); /* clear all pending */ atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); } static void atiixp_reset_aclink(struct atiixp_info *sc) { uint32_t value, timeout; /* if power is down, power it up */ value = atiixp_rd(sc, ATI_REG_CMD); if (value & ATI_REG_CMD_POWERDOWN) { /* explicitly enable power */ value &= ~ATI_REG_CMD_POWERDOWN; atiixp_wr(sc, ATI_REG_CMD, value); /* have to wait at least 10 usec for it to initialise */ DELAY(20); } /* perform a soft reset */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_SOFT_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* need to read the CMD reg and wait aprox. 10 usec to init */ value = atiixp_rd(sc, ATI_REG_CMD); DELAY(20); /* clear soft reset flag again */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_AC_SOFT_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* check if the ac-link is working; reset device otherwise */ timeout = 10; value = atiixp_rd(sc, ATI_REG_CMD); while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) && --timeout) { #if 0 device_printf(sc->dev, "not up; resetting aclink hardware\n"); #endif /* dip aclink reset but keep the acsync */ value &= ~ATI_REG_CMD_AC_RESET; value |= ATI_REG_CMD_AC_SYNC; atiixp_wr(sc, ATI_REG_CMD, value); /* need to read CMD again and wait again (clocking in issue?) */ value = atiixp_rd(sc, ATI_REG_CMD); DELAY(20); /* assert aclink reset again */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, value); /* check if its active now */ value = atiixp_rd(sc, ATI_REG_CMD); } if (timeout == 0) device_printf(sc->dev, "giving up aclink reset\n"); #if 0 if (timeout != 10) device_printf(sc->dev, "aclink hardware reset successful\n"); #endif /* assert reset and sync for safety */ value = atiixp_rd(sc, ATI_REG_CMD); value |= ATI_REG_CMD_AC_SYNC | ATI_REG_CMD_AC_RESET; atiixp_wr(sc, ATI_REG_CMD, value); } static void atiixp_flush_dma(struct atiixp_chinfo *ch) { atiixp_wr(ch->parent, ATI_REG_FIFO_FLUSH, ch->flush_bit); } static void atiixp_enable_dma(struct atiixp_chinfo *ch) { uint32_t value; value = atiixp_rd(ch->parent, ATI_REG_CMD); if (!(value & ch->enable_bit)) { value |= ch->enable_bit; atiixp_wr(ch->parent, ATI_REG_CMD, value); } } static void atiixp_disable_dma(struct atiixp_chinfo *ch) { uint32_t value; value = atiixp_rd(ch->parent, ATI_REG_CMD); if (value & ch->enable_bit) { value &= ~ch->enable_bit; atiixp_wr(ch->parent, ATI_REG_CMD, value); } } /* * AC97 interface */ static int atiixp_waitready_codec(struct atiixp_info *sc) { int timeout = 500; do { if ((atiixp_rd(sc, ATI_REG_PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) == 0) return (0); DELAY(1); } while (--timeout); return (-1); } static int atiixp_rdcd(kobj_t obj, void *devinfo, int reg) { struct atiixp_info *sc = devinfo; uint32_t data; int timeout; if (atiixp_waitready_codec(sc)) return (-1); data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); if (atiixp_waitready_codec(sc)) return (-1); timeout = 500; do { data = atiixp_rd(sc, ATI_REG_PHYS_IN_ADDR); if (data & ATI_REG_PHYS_IN_READ_FLAG) return (data >> ATI_REG_PHYS_IN_DATA_SHIFT); DELAY(1); } while (--timeout); if (reg < 0x7c) device_printf(sc->dev, "codec read timeout! (reg 0x%x)\n", reg); return (-1); } static int atiixp_wrcd(kobj_t obj, void *devinfo, int reg, uint32_t data) { struct atiixp_info *sc = devinfo; if (atiixp_waitready_codec(sc)) return (-1); data = (data << ATI_REG_PHYS_OUT_DATA_SHIFT) | (((uint32_t)reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); return (0); } static kobj_method_t atiixp_ac97_methods[] = { KOBJMETHOD(ac97_read, atiixp_rdcd), KOBJMETHOD(ac97_write, atiixp_wrcd), KOBJMETHOD_END }; AC97_DECLARE(atiixp_ac97); /* * Playback / Record channel interface */ static void * atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct atiixp_info *sc = devinfo; struct atiixp_chinfo *ch; int num; atiixp_lock(sc); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->linkptr_bit = ATI_REG_OUT_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SEND_EN; ch->flush_bit = ATI_REG_FIFO_OUT_FLUSH; ch->dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; /* Native 32bit playback working properly */ ch->caps_32bit = 1; } else { ch = &sc->rch; ch->linkptr_bit = ATI_REG_IN_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; ch->flush_bit = ATI_REG_FIFO_IN_FLUSH; ch->dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; /* XXX Native 32bit recording appear to be broken */ ch->caps_32bit = 1; } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->dir = dir; ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; atiixp_unlock(sc); if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) == -1) return (NULL); atiixp_lock(sc); num = sc->registered_channels++; ch->sgd_table = &sc->sgd_table[num * ATI_IXP_DMA_CHSEGS_MAX]; ch->sgd_addr = sc->sgd_addr + (num * ATI_IXP_DMA_CHSEGS_MAX * sizeof(struct atiixp_dma_op)); atiixp_disable_dma(ch); atiixp_unlock(sc); return (ch); } static int atiixp_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; atiixp_lock(sc); if (ch->dir == PCMDIR_REC) { value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_INTERLEAVE_IN; if ((format & AFMT_32BIT) == 0) value |= ATI_REG_CMD_INTERLEAVE_IN; atiixp_wr(sc, ATI_REG_CMD, value); } else { value = atiixp_rd(sc, ATI_REG_OUT_DMA_SLOT); value &= ~ATI_REG_OUT_DMA_SLOT_MASK; /* We do not have support for more than 2 channels, _yet_. */ value |= ATI_REG_OUT_DMA_SLOT_BIT(3) | ATI_REG_OUT_DMA_SLOT_BIT(4); value |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; atiixp_wr(sc, ATI_REG_OUT_DMA_SLOT, value); value = atiixp_rd(sc, ATI_REG_CMD); value &= ~ATI_REG_CMD_INTERLEAVE_OUT; if ((format & AFMT_32BIT) == 0) value |= ATI_REG_CMD_INTERLEAVE_OUT; atiixp_wr(sc, ATI_REG_CMD, value); value = atiixp_rd(sc, ATI_REG_6CH_REORDER); value &= ~ATI_REG_6CH_REORDER_EN; atiixp_wr(sc, ATI_REG_6CH_REORDER, value); } ch->fmt = format; atiixp_unlock(sc); return (0); } static uint32_t atiixp_chan_setspeed(kobj_t obj, void *data, uint32_t spd) { /* XXX We're supposed to do VRA/DRA processing right here */ return (ATI_IXP_BASE_RATE); } static int atiixp_chan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; blksz &= ATI_IXP_BLK_ALIGN; - if (blksz > (sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN)) - blksz = sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN; + if (blksz > (ch->buffer->maxsize / ATI_IXP_DMA_CHSEGS_MIN)) + blksz = ch->buffer->maxsize / ATI_IXP_DMA_CHSEGS_MIN; if (blksz < ATI_IXP_BLK_MIN) blksz = ATI_IXP_BLK_MIN; if (blkcnt > ATI_IXP_DMA_CHSEGS_MAX) blkcnt = ATI_IXP_DMA_CHSEGS_MAX; if (blkcnt < ATI_IXP_DMA_CHSEGS_MIN) blkcnt = ATI_IXP_DMA_CHSEGS_MIN; - while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + while ((blksz * blkcnt) > ch->buffer->maxsize) { if ((blkcnt >> 1) >= ATI_IXP_DMA_CHSEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ATI_IXP_BLK_MIN) blksz >>= 1; else break; } - if ((sndbuf_getblksz(ch->buffer) != blksz || - sndbuf_getblkcnt(ch->buffer) != blkcnt) && + if ((ch->buffer->blksz != blksz || + ch->buffer->blkcnt != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); - ch->blksz = sndbuf_getblksz(ch->buffer); - ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + ch->blksz = ch->buffer->blksz; + ch->blkcnt = ch->buffer->blkcnt; return (0); } static uint32_t atiixp_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; atiixp_chan_setfragments(obj, data, blksz, sc->blkcnt); return (ch->blksz); } static void atiixp_buildsgdt(struct atiixp_chinfo *ch) { struct atiixp_info *sc = ch->parent; uint32_t addr, blksz, blkcnt; int i; - addr = sndbuf_getbufaddr(ch->buffer); + addr = ch->buffer->buf_addr; if (sc->polling != 0) { blksz = ch->blksz * ch->blkcnt; blkcnt = 1; } else { blksz = ch->blksz; blkcnt = ch->blkcnt; } for (i = 0; i < blkcnt; i++) { ch->sgd_table[i].addr = htole32(addr + (i * blksz)); ch->sgd_table[i].status = htole16(0); ch->sgd_table[i].size = htole16(blksz >> 2); ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + (((i + 1) % blkcnt) * sizeof(struct atiixp_dma_op))); } } static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *ch) { struct atiixp_info *sc = ch->parent; uint32_t reg, addr, sz, retry; volatile uint32_t ptr; reg = ch->dt_cur_bit; - addr = sndbuf_getbufaddr(ch->buffer); + addr = ch->buffer->buf_addr; sz = ch->blkcnt * ch->blksz; retry = ATI_IXP_DMA_RETRY_MAX; do { ptr = atiixp_rd(sc, reg); if (ptr < addr) continue; ptr -= addr; if (ptr < sz) { #if 0 #ifdef ATI_IXP_DEBUG if ((ptr & ~(ch->blksz - 1)) != ch->ptr) { uint32_t delta; delta = (sz + ptr - ch->prevptr) % sz; #ifndef ATI_IXP_DEBUG_VERBOSE if (delta < ch->blksz) #endif device_printf(sc->dev, "PCMDIR_%s: incoherent DMA " "prevptr=%u ptr=%u " "ptr=%u blkcnt=%u " "[delta=%u != blksz=%u] " "(%s)\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->prevptr, ptr, ch->ptr, ch->blkcnt, delta, ch->blksz, (delta < ch->blksz) ? "OVERLAPPED!" : "Ok"); ch->ptr = ptr & ~(ch->blksz - 1); } ch->prevptr = ptr; #endif #endif return (ptr); } } while (--retry); device_printf(sc->dev, "PCMDIR_%s: invalid DMA pointer ptr=%u\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); return (0); } static __inline int atiixp_poll_channel(struct atiixp_chinfo *ch) { uint32_t sz, delta; volatile uint32_t ptr; if (!(ch->flags & ATI_IXP_CHN_RUNNING)) return (0); sz = ch->blksz * ch->blkcnt; ptr = atiixp_dmapos(ch); ch->ptr = ptr; ptr %= sz; ptr &= ~(ch->blksz - 1); delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } #define atiixp_chan_active(sc) (((sc)->pch.flags | (sc)->rch.flags) & \ ATI_IXP_CHN_RUNNING) static void atiixp_poll_callback(void *arg) { struct atiixp_info *sc = arg; uint32_t trigger = 0; if (sc == NULL) return; atiixp_lock(sc); if (sc->polling == 0 || atiixp_chan_active(sc) == 0) { atiixp_unlock(sc); return; } trigger |= (atiixp_poll_channel(&sc->pch) != 0) ? 1 : 0; trigger |= (atiixp_poll_channel(&sc->rch) != 0) ? 2 : 0; /* XXX */ callout_reset(&sc->poll_timer, 1/*sc->poll_ticks*/, atiixp_poll_callback, sc); atiixp_unlock(sc); if (trigger & 1) chn_intr(sc->pch.channel); if (trigger & 2) chn_intr(sc->rch.channel); } static int atiixp_chan_trigger(kobj_t obj, void *data, int go) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; int pollticks; if (!PCMTRIG_COMMON(go)) return (0); atiixp_lock(sc); switch (go) { case PCMTRIG_START: atiixp_flush_dma(ch); atiixp_buildsgdt(ch); atiixp_wr(sc, ch->linkptr_bit, 0); atiixp_enable_dma(ch); atiixp_wr(sc, ch->linkptr_bit, (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); if (sc->polling != 0) { ch->ptr = 0; ch->prevptr = 0; pollticks = ((uint64_t)hz * ch->blksz) / - ((uint64_t)sndbuf_getalign(ch->buffer) * - sndbuf_getspd(ch->buffer)); + ((uint64_t)ch->buffer->align * ch->buffer->spd); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (atiixp_chan_active(sc) == 0 || pollticks < sc->poll_ticks) { if (bootverbose) { if (atiixp_chan_active(sc) == 0) device_printf(sc->dev, "%s: pollticks=%d\n", __func__, pollticks); else device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); } sc->poll_ticks = pollticks; callout_reset(&sc->poll_timer, 1, atiixp_poll_callback, sc); } } ch->flags |= ATI_IXP_CHN_RUNNING; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: atiixp_disable_dma(ch); atiixp_flush_dma(ch); ch->flags &= ~ATI_IXP_CHN_RUNNING; if (sc->polling != 0) { if (atiixp_chan_active(sc) == 0) { callout_stop(&sc->poll_timer); sc->poll_ticks = 1; } else { if (sc->pch.flags & ATI_IXP_CHN_RUNNING) ch = &sc->pch; else ch = &sc->rch; pollticks = ((uint64_t)hz * ch->blksz) / - ((uint64_t)sndbuf_getalign(ch->buffer) * - sndbuf_getspd(ch->buffer)); + ((uint64_t)ch->buffer->align * + ch->buffer->spd); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks > sc->poll_ticks) { if (bootverbose) device_printf(sc->dev, "%s: pollticks %d -> %d\n", __func__, sc->poll_ticks, pollticks); sc->poll_ticks = pollticks; callout_reset(&sc->poll_timer, 1, atiixp_poll_callback, sc); } } } break; default: atiixp_unlock(sc); return (0); break; } /* Update bus busy status */ value = atiixp_rd(sc, ATI_REG_IER); if (atiixp_rd(sc, ATI_REG_CMD) & (ATI_REG_CMD_SEND_EN | ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) value |= ATI_REG_IER_SET_BUS_BUSY; else value &= ~ATI_REG_IER_SET_BUS_BUSY; atiixp_wr(sc, ATI_REG_IER, value); atiixp_unlock(sc); return (0); } static uint32_t atiixp_chan_getptr(kobj_t obj, void *data) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t ptr; atiixp_lock(sc); if (sc->polling != 0) ptr = ch->ptr; else ptr = atiixp_dmapos(ch); atiixp_unlock(sc); return (ptr); } static struct pcmchan_caps * atiixp_chan_getcaps(kobj_t obj, void *data) { struct atiixp_chinfo *ch = data; if (ch->caps_32bit) return (&atiixp_caps_32bit); return (&atiixp_caps); } static kobj_method_t atiixp_chan_methods[] = { KOBJMETHOD(channel_init, atiixp_chan_init), KOBJMETHOD(channel_setformat, atiixp_chan_setformat), KOBJMETHOD(channel_setspeed, atiixp_chan_setspeed), KOBJMETHOD(channel_setblocksize, atiixp_chan_setblocksize), KOBJMETHOD(channel_setfragments, atiixp_chan_setfragments), KOBJMETHOD(channel_trigger, atiixp_chan_trigger), KOBJMETHOD(channel_getptr, atiixp_chan_getptr), KOBJMETHOD(channel_getcaps, atiixp_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(atiixp_chan); /* * PCI driver interface */ static void atiixp_intr(void *p) { struct atiixp_info *sc = p; uint32_t status, enable, detected_codecs; uint32_t trigger = 0; atiixp_lock(sc); if (sc->polling != 0) { atiixp_unlock(sc); return; } status = atiixp_rd(sc, ATI_REG_ISR); if (status == 0) { atiixp_unlock(sc); return; } if ((status & ATI_REG_ISR_OUT_STATUS) && (sc->pch.flags & ATI_IXP_CHN_RUNNING)) trigger |= 1; if ((status & ATI_REG_ISR_IN_STATUS) && (sc->rch.flags & ATI_IXP_CHN_RUNNING)) trigger |= 2; #if 0 if (status & ATI_REG_ISR_IN_XRUN) { device_printf(sc->dev, "Recieve IN XRUN interrupt\n"); } if (status & ATI_REG_ISR_OUT_XRUN) { device_printf(sc->dev, "Recieve OUT XRUN interrupt\n"); } #endif if (status & CODEC_CHECK_BITS) { /* mark missing codecs as not ready */ detected_codecs = status & CODEC_CHECK_BITS; sc->codec_not_ready_bits |= detected_codecs; /* disable detected interrupt sources */ enable = atiixp_rd(sc, ATI_REG_IER); enable &= ~detected_codecs; atiixp_wr(sc, ATI_REG_IER, enable); wakeup(sc); } /* acknowledge */ atiixp_wr(sc, ATI_REG_ISR, status); atiixp_unlock(sc); if (trigger & 1) chn_intr(sc->pch.channel); if (trigger & 2) chn_intr(sc->rch.channel); } static void atiixp_dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct atiixp_info *sc = (struct atiixp_info *)p; sc->sgd_addr = bds->ds_addr; } static void atiixp_chip_pre_init(struct atiixp_info *sc) { uint32_t value; atiixp_lock(sc); /* disable interrupts */ atiixp_disable_interrupts(sc); /* clear all DMA enables (preserving rest of settings) */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~(ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SPDF_OUT_EN ); atiixp_wr(sc, ATI_REG_CMD, value); /* reset aclink */ atiixp_reset_aclink(sc); sc->codec_not_ready_bits = 0; /* enable all codecs to interrupt as well as the new frame interrupt */ atiixp_wr(sc, ATI_REG_IER, CODEC_CHECK_BITS); atiixp_unlock(sc); } static int sysctl_atiixp_polling(SYSCTL_HANDLER_ARGS) { struct atiixp_info *sc; device_t dev; int err, val; dev = oidp->oid_arg1; sc = pcm_getdevinfo(dev); if (sc == NULL) return (EINVAL); atiixp_lock(sc); val = sc->polling; atiixp_unlock(sc); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); atiixp_lock(sc); if (val != sc->polling) { if (atiixp_chan_active(sc) != 0) err = EBUSY; else if (val == 0) { atiixp_enable_interrupts(sc); sc->polling = 0; DELAY(1000); } else { atiixp_disable_interrupts(sc); sc->polling = 1; DELAY(1000); } } atiixp_unlock(sc); return (err); } static void atiixp_chip_post_init(void *arg) { struct atiixp_info *sc = (struct atiixp_info *)arg; uint32_t subdev; int i, timeout, found, polling; char status[SND_STATUSLEN]; atiixp_lock(sc); if (sc->delayed_attach.ich_func) { config_intrhook_disestablish(&sc->delayed_attach); sc->delayed_attach.ich_func = NULL; } polling = sc->polling; sc->polling = 0; timeout = 10; if (sc->codec_not_ready_bits == 0) { /* wait for the interrupts to happen */ do { msleep(sc, sc->lock, PWAIT, "ixpslp", max(hz / 10, 1)); if (sc->codec_not_ready_bits != 0) break; } while (--timeout); } sc->polling = polling; atiixp_disable_interrupts(sc); if (sc->codec_not_ready_bits == 0 && timeout == 0) { device_printf(sc->dev, "WARNING: timeout during codec detection; " "codecs might be present but haven't interrupted\n"); atiixp_unlock(sc); goto postinitbad; } found = 0; /* * ATI IXP can have upto 3 codecs, but single codec should be * suffice for now. */ if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC0_NOT_READY)) { /* codec 0 present */ sc->codec_found++; sc->codec_idx = 0; found++; } if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC1_NOT_READY)) { /* codec 1 present */ sc->codec_found++; } if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) { /* codec 2 present */ sc->codec_found++; } atiixp_unlock(sc); if (found == 0) goto postinitbad; /* create/init mixer */ sc->codec = AC97_CREATE(sc->dev, sc, atiixp_ac97); if (sc->codec == NULL) goto postinitbad; subdev = (pci_get_subdevice(sc->dev) << 16) | pci_get_subvendor(sc->dev); switch (subdev) { case 0x11831043: /* ASUS A6R */ case 0x2043161f: /* Maxselect x710s - http://maxselect.ru/ */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(sc->dev, ac97_getmixerclass(), sc->codec); pcm_init(sc->dev, sc); for (i = 0; i < ATI_IXP_NPCHAN; i++) pcm_addchan(sc->dev, PCMDIR_PLAY, &atiixp_chan_class, sc); for (i = 0; i < ATI_IXP_NRCHAN; i++) pcm_addchan(sc->dev, PCMDIR_REC, &atiixp_chan_class, sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc->dev, sizeof(sc->dev), sysctl_atiixp_polling, "I", "Enable polling mode"); snprintf(status, SND_STATUSLEN, "mem 0x%jx irq %jd on %s", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(sc->dev))); if (pcm_register(sc->dev, status)) goto postinitbad; atiixp_lock(sc); if (sc->polling == 0) atiixp_enable_interrupts(sc); atiixp_unlock(sc); return; postinitbad: atiixp_release_resource(sc); } static void atiixp_release_resource(struct atiixp_info *sc) { if (sc == NULL) return; if (sc->registered_channels != 0) { atiixp_lock(sc); sc->polling = 0; callout_stop(&sc->poll_timer); atiixp_unlock(sc); callout_drain(&sc->poll_timer); } if (sc->codec) { ac97_destroy(sc->codec); sc->codec = NULL; } if (sc->ih) { bus_teardown_intr(sc->dev, sc->irq, sc->ih); sc->ih = NULL; } if (sc->reg) { bus_release_resource(sc->dev, sc->regtype, sc->regid, sc->reg); sc->reg = NULL; } if (sc->irq) { bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irqid, sc->irq); sc->irq = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = NULL; } if (sc->sgd_addr) { bus_dmamap_unload(sc->sgd_dmat, sc->sgd_dmamap); sc->sgd_addr = 0; } if (sc->sgd_table) { bus_dmamem_free(sc->sgd_dmat, sc->sgd_table, sc->sgd_dmamap); sc->sgd_table = NULL; } if (sc->sgd_dmat) { bus_dma_tag_destroy(sc->sgd_dmat); sc->sgd_dmat = NULL; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } free(sc, M_DEVBUF); } static int atiixp_pci_probe(device_t dev) { size_t i; uint16_t devid, vendor; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < nitems(atiixp_hw); i++) { if (vendor == atiixp_hw[i].vendor && devid == atiixp_hw[i].devid) { device_set_desc(dev, atiixp_hw[i].desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int atiixp_pci_attach(device_t dev) { struct atiixp_info *sc; int i; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_atiixp softc"); sc->dev = dev; callout_init(&sc->poll_timer, 1); sc->poll_ticks = 1; if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) sc->polling = 1; else sc->polling = 0; pci_enable_busmaster(dev); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, ATI_IXP_BUFSZ_MIN, ATI_IXP_BUFSZ_DEFAULT, ATI_IXP_BUFSZ_MAX); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, atiixp_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } /* * Let the user choose the best DMA segments. */ if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= ATI_IXP_BLK_ALIGN; if (i < ATI_IXP_BLK_MIN) i = ATI_IXP_BLK_MIN; sc->blkcnt = sc->bufsz / i; i = 0; while (sc->blkcnt >> i) i++; sc->blkcnt = 1 << (i - 1); if (sc->blkcnt < ATI_IXP_DMA_CHSEGS_MIN) sc->blkcnt = ATI_IXP_DMA_CHSEGS_MIN; else if (sc->blkcnt > ATI_IXP_DMA_CHSEGS_MAX) sc->blkcnt = ATI_IXP_DMA_CHSEGS_MAX; } else sc->blkcnt = ATI_IXP_DMA_CHSEGS; /* * DMA tag for scatter-gather buffers and link pointers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) goto bad; if (bus_dmamap_load(sc->sgd_dmat, sc->sgd_dmamap, sc->sgd_table, ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), atiixp_dma_cb, sc, 0)) goto bad; atiixp_chip_pre_init(sc); sc->delayed_attach.ich_func = atiixp_chip_post_init; sc->delayed_attach.ich_arg = sc; if (cold == 0 || config_intrhook_establish(&sc->delayed_attach) != 0) { sc->delayed_attach.ich_func = NULL; atiixp_chip_post_init(sc); } return (0); bad: atiixp_release_resource(sc); return (ENXIO); } static int atiixp_pci_detach(device_t dev) { int r; struct atiixp_info *sc; sc = pcm_getdevinfo(dev); if (sc != NULL) { if (sc->codec != NULL) { r = pcm_unregister(dev); if (r) return (r); } sc->codec = NULL; if (sc->st != 0 && sc->sh != 0) atiixp_disable_interrupts(sc); atiixp_release_resource(sc); } return (0); } static int atiixp_pci_suspend(device_t dev) { struct atiixp_info *sc = pcm_getdevinfo(dev); /* quickly disable interrupts and save channels active state */ atiixp_lock(sc); atiixp_disable_interrupts(sc); atiixp_unlock(sc); /* stop everything */ if (sc->pch.flags & ATI_IXP_CHN_RUNNING) { atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); sc->pch.flags |= ATI_IXP_CHN_SUSPEND; } if (sc->rch.flags & ATI_IXP_CHN_RUNNING) { atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); sc->rch.flags |= ATI_IXP_CHN_SUSPEND; } /* power down aclink and pci bus */ atiixp_lock(sc); atiixp_wr(sc, ATI_REG_CMD, ATI_REG_CMD_POWERDOWN); atiixp_unlock(sc); return (0); } static int atiixp_pci_resume(device_t dev) { struct atiixp_info *sc = pcm_getdevinfo(dev); atiixp_lock(sc); /* reset / power up aclink */ atiixp_reset_aclink(sc); atiixp_unlock(sc); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } /* * Resume channel activities. Reset channel format regardless * of its previous state. */ if (sc->pch.channel != NULL) { if (sc->pch.fmt != 0) atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); if (sc->pch.flags & ATI_IXP_CHN_SUSPEND) { sc->pch.flags &= ~ATI_IXP_CHN_SUSPEND; atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); } } if (sc->rch.channel != NULL) { if (sc->rch.fmt != 0) atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); if (sc->rch.flags & ATI_IXP_CHN_SUSPEND) { sc->rch.flags &= ~ATI_IXP_CHN_SUSPEND; atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); } } /* enable interrupts */ atiixp_lock(sc); if (sc->polling == 0) atiixp_enable_interrupts(sc); atiixp_unlock(sc); return (0); } static device_method_t atiixp_methods[] = { DEVMETHOD(device_probe, atiixp_pci_probe), DEVMETHOD(device_attach, atiixp_pci_attach), DEVMETHOD(device_detach, atiixp_pci_detach), DEVMETHOD(device_suspend, atiixp_pci_suspend), DEVMETHOD(device_resume, atiixp_pci_resume), { 0, 0 } }; static driver_t atiixp_driver = { "pcm", atiixp_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_atiixp, pci, atiixp_driver, 0, 0); MODULE_DEPEND(snd_atiixp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_atiixp, 1); diff --git a/sys/dev/sound/pci/cmi.c b/sys/dev/sound/pci/cmi.c index 22f1e76a4d1f..a1356f58b567 100644 --- a/sys/dev/sound/pci/cmi.c +++ b/sys/dev/sound/pci/cmi.c @@ -1,1111 +1,1111 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Orion Hodson * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * This driver exists largely as a result of other people's efforts. * Much of register handling is based on NetBSD CMI8x38 audio driver * by Takuya Shiozaki . Chen-Li Tien * clarified points regarding the DMA related * registers and the 8738 mixer devices. His Linux driver was also a * useful reference point. * * TODO: MIDI * * SPDIF contributed by Gerhard Gonter . * * This card/code does not always manage to sample at 44100 - actual * rate drifts slightly between recordings (usually 0-3%). No * differences visible in register dumps between times that work and * those that don't. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include "mixer_if.h" #include "mpufoi_if.h" /* Supported chip ID's */ #define CMI8338A_PCI_ID 0x010013f6 #define CMI8338B_PCI_ID 0x010113f6 #define CMI8738_PCI_ID 0x011113f6 #define CMI8738B_PCI_ID 0x011213f6 #define CMI120_USB_ID 0x01030d8c /* Buffer size max is 64k for permitted DMA boundaries */ #define CMI_DEFAULT_BUFSZ 16384 /* Interrupts per length of buffer */ #define CMI_INTR_PER_BUFFER 2 /* Clarify meaning of named defines in cmireg.h */ #define CMPCI_REG_DMA0_MAX_SAMPLES CMPCI_REG_DMA0_BYTES #define CMPCI_REG_DMA0_INTR_SAMPLES CMPCI_REG_DMA0_SAMPLES #define CMPCI_REG_DMA1_MAX_SAMPLES CMPCI_REG_DMA1_BYTES #define CMPCI_REG_DMA1_INTR_SAMPLES CMPCI_REG_DMA1_SAMPLES /* Our indication of custom mixer control */ #define CMPCI_NON_SB16_CONTROL 0xff /* Debugging macro's */ #undef DEB #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ #ifndef DEBMIX #define DEBMIX(x) /* x */ #endif /* DEBMIX */ /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t fmt, spd, phys_buf, bps; u_int32_t dma_active:1, dma_was_active:1; int dir; }; struct sc_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regid, irqid; void *ih; struct mtx *lock; int spdif_enabled; unsigned int bufsz; struct sc_chinfo pch, rch; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; struct resource *mpu_reg; int mpu_regid; bus_space_tag_t mpu_bt; bus_space_handle_t mpu_bh; }; /* Channel caps */ static u_int32_t cmi_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps cmi_caps = {5512, 48000, cmi_fmt, 0}; /* ------------------------------------------------------------------------- */ /* Register Utilities */ static u_int32_t cmi_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: DEB(printf("cmi_rd: failed 0x%04x %d\n", regno, size)); return 0xFFFFFFFF; } } static void cmi_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->st, sc->sh, regno, data); break; case 2: bus_space_write_2(sc->st, sc->sh, regno, data); break; case 4: bus_space_write_4(sc->st, sc->sh, regno, data); break; } } static void cmi_partial_wr4(struct sc_info *sc, int reg, int shift, u_int32_t mask, u_int32_t val) { u_int32_t r; r = cmi_rd(sc, reg, 4); r &= ~(mask << shift); r |= val << shift; cmi_wr(sc, reg, r, 4); } static void cmi_clr4(struct sc_info *sc, int reg, u_int32_t mask) { u_int32_t r; r = cmi_rd(sc, reg, 4); r &= ~mask; cmi_wr(sc, reg, r, 4); } static void cmi_set4(struct sc_info *sc, int reg, u_int32_t mask) { u_int32_t r; r = cmi_rd(sc, reg, 4); r |= mask; cmi_wr(sc, reg, r, 4); } /* ------------------------------------------------------------------------- */ /* Rate Mapping */ static int cmi_rates[] = {5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000}; #define NUM_CMI_RATES (sizeof(cmi_rates)/sizeof(cmi_rates[0])) /* cmpci_rate_to_regvalue returns sampling freq selector for FCR1 * register - reg order is 5k,11k,22k,44k,8k,16k,32k,48k */ static u_int32_t cmpci_rate_to_regvalue(int rate) { int i, r; for(i = 0; i < NUM_CMI_RATES - 1; i++) { if (rate < ((cmi_rates[i] + cmi_rates[i + 1]) / 2)) { break; } } DEB(printf("cmpci_rate_to_regvalue: %d -> %d\n", rate, cmi_rates[i])); r = ((i >> 1) | (i << 2)) & 0x07; return r; } static int cmpci_regvalue_to_rate(u_int32_t r) { int i; i = ((r << 1) | (r >> 2)) & 0x07; DEB(printf("cmpci_regvalue_to_rate: %d -> %d\n", r, i)); return cmi_rates[i]; } /* ------------------------------------------------------------------------- */ /* ADC/DAC control - there are 2 dma channels on 8738, either can be * playback or capture. We use ch0 for playback and ch1 for capture. */ static void cmi_dma_prog(struct sc_info *sc, struct sc_chinfo *ch, u_int32_t base) { u_int32_t s, i, sz; - ch->phys_buf = sndbuf_getbufaddr(ch->buffer); + ch->phys_buf = ch->buffer->buf_addr; cmi_wr(sc, base, ch->phys_buf, 4); - sz = (u_int32_t)sndbuf_getsize(ch->buffer); + sz = (u_int32_t)ch->buffer->bufsize; s = sz / ch->bps - 1; cmi_wr(sc, base + 4, s, 2); i = sz / (ch->bps * CMI_INTR_PER_BUFFER) - 1; cmi_wr(sc, base + 6, i, 2); } static void cmi_ch0_start(struct sc_info *sc, struct sc_chinfo *ch) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA0_BASE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE); cmi_set4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); ch->dma_active = 1; } static u_int32_t cmi_ch0_stop(struct sc_info *sc, struct sc_chinfo *ch) { u_int32_t r = ch->dma_active; cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_RESET); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_RESET); ch->dma_active = 0; return r; } static void cmi_ch1_start(struct sc_info *sc, struct sc_chinfo *ch) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA1_BASE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_ENABLE); /* Enable Interrupts */ cmi_set4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); DEB(printf("cmi_ch1_start: dma prog\n")); ch->dma_active = 1; } static u_int32_t cmi_ch1_stop(struct sc_info *sc, struct sc_chinfo *ch) { u_int32_t r = ch->dma_active; cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_ENABLE); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_RESET); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_RESET); ch->dma_active = 0; return r; } static void cmi_spdif_speed(struct sc_info *sc, int speed) { u_int32_t fcr1, lcr, mcr; if (speed >= 44100) { fcr1 = CMPCI_REG_SPDIF0_ENABLE; lcr = CMPCI_REG_XSPDIF_ENABLE; mcr = (speed == 48000) ? CMPCI_REG_W_SPDIF_48L | CMPCI_REG_SPDIF_48K : 0; } else { fcr1 = mcr = lcr = 0; } cmi_partial_wr4(sc, CMPCI_REG_MISC, 0, CMPCI_REG_W_SPDIF_48L | CMPCI_REG_SPDIF_48K, mcr); cmi_partial_wr4(sc, CMPCI_REG_FUNC_1, 0, CMPCI_REG_SPDIF0_ENABLE, fcr1); cmi_partial_wr4(sc, CMPCI_REG_LEGACY_CTRL, 0, CMPCI_REG_XSPDIF_ENABLE, lcr); } /* ------------------------------------------------------------------------- */ /* Channel Interface implementation */ static void * cmichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->bps = 1; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->buffer = b; ch->dma_active = 0; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("cmichan_init failed\n")); return NULL; } ch->dir = dir; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { cmi_dma_prog(sc, ch, CMPCI_REG_DMA0_BASE); } else { cmi_dma_prog(sc, ch, CMPCI_REG_DMA1_BASE); } snd_mtxunlock(sc->lock); return ch; } static int cmichan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t f; if (format & AFMT_S16_LE) { f = CMPCI_REG_FORMAT_16BIT; ch->bps = 2; } else { f = CMPCI_REG_FORMAT_8BIT; ch->bps = 1; } if (AFMT_CHANNEL(format) > 1) { f |= CMPCI_REG_FORMAT_STEREO; ch->bps *= 2; } else { f |= CMPCI_REG_FORMAT_MONO; } snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { cmi_partial_wr4(ch->parent, CMPCI_REG_CHANNEL_FORMAT, CMPCI_REG_CH0_FORMAT_SHIFT, CMPCI_REG_CH0_FORMAT_MASK, f); } else { cmi_partial_wr4(ch->parent, CMPCI_REG_CHANNEL_FORMAT, CMPCI_REG_CH1_FORMAT_SHIFT, CMPCI_REG_CH1_FORMAT_MASK, f); } snd_mtxunlock(sc->lock); ch->fmt = format; return 0; } static u_int32_t cmichan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t r, rsp __unused; r = cmpci_rate_to_regvalue(speed); snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (speed < 44100) { /* disable if req before rate change */ cmi_spdif_speed(ch->parent, speed); } cmi_partial_wr4(ch->parent, CMPCI_REG_FUNC_1, CMPCI_REG_DAC_FS_SHIFT, CMPCI_REG_DAC_FS_MASK, r); if (speed >= 44100 && ch->parent->spdif_enabled) { /* enable if req after rate change */ cmi_spdif_speed(ch->parent, speed); } rsp = cmi_rd(ch->parent, CMPCI_REG_FUNC_1, 4); rsp >>= CMPCI_REG_DAC_FS_SHIFT; rsp &= CMPCI_REG_DAC_FS_MASK; } else { cmi_partial_wr4(ch->parent, CMPCI_REG_FUNC_1, CMPCI_REG_ADC_FS_SHIFT, CMPCI_REG_ADC_FS_MASK, r); rsp = cmi_rd(ch->parent, CMPCI_REG_FUNC_1, 4); rsp >>= CMPCI_REG_ADC_FS_SHIFT; rsp &= CMPCI_REG_ADC_FS_MASK; } snd_mtxunlock(sc->lock); ch->spd = cmpci_regvalue_to_rate(r); DEB(printf("cmichan_setspeed (%s) %d -> %d (%d)\n", (ch->dir == PCMDIR_PLAY) ? "play" : "rec", speed, ch->spd, cmpci_regvalue_to_rate(rsp))); return ch->spd; } static u_int32_t cmichan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; /* user has requested interrupts every blocksize bytes */ if (blocksize > sc->bufsz / CMI_INTR_PER_BUFFER) { blocksize = sc->bufsz / CMI_INTR_PER_BUFFER; } sndbuf_resize(ch->buffer, CMI_INTR_PER_BUFFER, blocksize); return blocksize; } static int cmichan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { switch(go) { case PCMTRIG_START: cmi_ch0_start(sc, ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch0_stop(sc, ch); break; } } else { switch(go) { case PCMTRIG_START: cmi_ch1_start(sc, ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch1_stop(sc, ch); break; } } snd_mtxunlock(sc->lock); return 0; } static u_int32_t cmichan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t physptr, bufptr, sz; snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { physptr = cmi_rd(sc, CMPCI_REG_DMA0_BASE, 4); } else { physptr = cmi_rd(sc, CMPCI_REG_DMA1_BASE, 4); } snd_mtxunlock(sc->lock); - sz = sndbuf_getsize(ch->buffer); + sz = ch->buffer->bufsize; bufptr = (physptr - ch->phys_buf + sz - ch->bps) % sz; return bufptr; } static void cmi_intr(void *data) { struct sc_info *sc = data; u_int32_t intrstat; u_int32_t toclear; snd_mtxlock(sc->lock); intrstat = cmi_rd(sc, CMPCI_REG_INTR_STATUS, 4); if ((intrstat & CMPCI_REG_ANY_INTR) != 0) { toclear = 0; if (intrstat & CMPCI_REG_CH0_INTR) { toclear |= CMPCI_REG_CH0_INTR_ENABLE; //cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE); } if (intrstat & CMPCI_REG_CH1_INTR) { toclear |= CMPCI_REG_CH1_INTR_ENABLE; //cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH1_INTR_ENABLE); } if (toclear) { cmi_clr4(sc, CMPCI_REG_INTR_CTRL, toclear); snd_mtxunlock(sc->lock); /* Signal interrupts to channel */ if (intrstat & CMPCI_REG_CH0_INTR) { chn_intr(sc->pch.channel); } if (intrstat & CMPCI_REG_CH1_INTR) { chn_intr(sc->rch.channel); } snd_mtxlock(sc->lock); cmi_set4(sc, CMPCI_REG_INTR_CTRL, toclear); } } if(sc->mpu_intr) { (sc->mpu_intr)(sc->mpu); } snd_mtxunlock(sc->lock); return; } static struct pcmchan_caps * cmichan_getcaps(kobj_t obj, void *data) { return &cmi_caps; } static kobj_method_t cmichan_methods[] = { KOBJMETHOD(channel_init, cmichan_init), KOBJMETHOD(channel_setformat, cmichan_setformat), KOBJMETHOD(channel_setspeed, cmichan_setspeed), KOBJMETHOD(channel_setblocksize, cmichan_setblocksize), KOBJMETHOD(channel_trigger, cmichan_trigger), KOBJMETHOD(channel_getptr, cmichan_getptr), KOBJMETHOD(channel_getcaps, cmichan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(cmichan); /* ------------------------------------------------------------------------- */ /* Mixer - sb16 with kinks */ static void cmimix_wr(struct sc_info *sc, u_int8_t port, u_int8_t val) { cmi_wr(sc, CMPCI_REG_SBADDR, port, 1); cmi_wr(sc, CMPCI_REG_SBDATA, val, 1); } static u_int8_t cmimix_rd(struct sc_info *sc, u_int8_t port) { cmi_wr(sc, CMPCI_REG_SBADDR, port, 1); return (u_int8_t)cmi_rd(sc, CMPCI_REG_SBDATA, 1); } struct sb16props { u_int8_t rreg; /* right reg chan register */ u_int8_t stereo:1; /* (no explanation needed, honest) */ u_int8_t rec:1; /* recording source */ u_int8_t bits:3; /* num bits to represent maximum gain rep */ u_int8_t oselect; /* output select mask */ u_int8_t iselect; /* right input select mask */ } static const cmt[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_SYNTH] = {CMPCI_SB16_MIXER_FM_R, 1, 1, 5, CMPCI_SB16_SW_FM, CMPCI_SB16_MIXER_FM_SRC_R}, [SOUND_MIXER_CD] = {CMPCI_SB16_MIXER_CDDA_R, 1, 1, 5, CMPCI_SB16_SW_CD, CMPCI_SB16_MIXER_CD_SRC_R}, [SOUND_MIXER_LINE] = {CMPCI_SB16_MIXER_LINE_R, 1, 1, 5, CMPCI_SB16_SW_LINE, CMPCI_SB16_MIXER_LINE_SRC_R}, [SOUND_MIXER_MIC] = {CMPCI_SB16_MIXER_MIC, 0, 1, 5, CMPCI_SB16_SW_MIC, CMPCI_SB16_MIXER_MIC_SRC}, [SOUND_MIXER_SPEAKER] = {CMPCI_SB16_MIXER_SPEAKER, 0, 0, 2, 0, 0}, [SOUND_MIXER_PCM] = {CMPCI_SB16_MIXER_VOICE_R, 1, 0, 5, 0, 0}, [SOUND_MIXER_VOLUME] = {CMPCI_SB16_MIXER_MASTER_R, 1, 0, 5, 0, 0}, /* These controls are not implemented in CMI8738, but maybe at a future date. They are not documented in C-Media documentation, though appear in other drivers for future h/w (ALSA, Linux, NetBSD). */ [SOUND_MIXER_IGAIN] = {CMPCI_SB16_MIXER_INGAIN_R, 1, 0, 2, 0, 0}, [SOUND_MIXER_OGAIN] = {CMPCI_SB16_MIXER_OUTGAIN_R, 1, 0, 2, 0, 0}, [SOUND_MIXER_BASS] = {CMPCI_SB16_MIXER_BASS_R, 1, 0, 4, 0, 0}, [SOUND_MIXER_TREBLE] = {CMPCI_SB16_MIXER_TREBLE_R, 1, 0, 4, 0, 0}, /* The mic pre-amp is implemented with non-SB16 compatible registers. */ [SOUND_MIXER_MONITOR] = {CMPCI_NON_SB16_CONTROL, 0, 1, 4, 0}, }; #define MIXER_GAIN_REG_RTOL(r) (r - 1) static int cmimix_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i,v; for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (cmt[i].bits) v |= 1 << i; } mix_setdevs(m, v); for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (cmt[i].rec) v |= 1 << i; } mix_setrecdevs(m, v); cmimix_wr(sc, CMPCI_SB16_MIXER_RESET, 0); cmimix_wr(sc, CMPCI_SB16_MIXER_ADCMIX_L, 0); cmimix_wr(sc, CMPCI_SB16_MIXER_ADCMIX_R, 0); cmimix_wr(sc, CMPCI_SB16_MIXER_OUTMIX, CMPCI_SB16_SW_CD | CMPCI_SB16_SW_MIC | CMPCI_SB16_SW_LINE); return 0; } static int cmimix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t r, l, max; u_int8_t v; max = (1 << cmt[dev].bits) - 1; if (cmt[dev].rreg == CMPCI_NON_SB16_CONTROL) { /* For time being this can only be one thing (mic in * mic/aux reg) */ v = cmi_rd(sc, CMPCI_REG_AUX_MIC, 1) & 0xf0; l = left * max / 100; /* 3 bit gain with LSB MICGAIN off(1),on(1) -> 4 bit value */ v |= ((l << 1) | (~l >> 3)) & 0x0f; cmi_wr(sc, CMPCI_REG_AUX_MIC, v, 1); return 0; } l = (left * max / 100) << (8 - cmt[dev].bits); if (cmt[dev].stereo) { r = (right * max / 100) << (8 - cmt[dev].bits); cmimix_wr(sc, MIXER_GAIN_REG_RTOL(cmt[dev].rreg), l); cmimix_wr(sc, cmt[dev].rreg, r); DEBMIX(printf("Mixer stereo write dev %d reg 0x%02x "\ "value 0x%02x:0x%02x\n", dev, MIXER_GAIN_REG_RTOL(cmt[dev].rreg), l, r)); } else { r = l; cmimix_wr(sc, cmt[dev].rreg, l); DEBMIX(printf("Mixer mono write dev %d reg 0x%02x " \ "value 0x%02x:0x%02x\n", dev, cmt[dev].rreg, l, l)); } /* Zero gain does not mute channel from output, but this does... */ v = cmimix_rd(sc, CMPCI_SB16_MIXER_OUTMIX); if (l == 0 && r == 0) { v &= ~cmt[dev].oselect; } else { v |= cmt[dev].oselect; } cmimix_wr(sc, CMPCI_SB16_MIXER_OUTMIX, v); return 0; } static u_int32_t cmimix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, ml, sl; ml = sl = 0; for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1< */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "spdif_enabled", CTLFLAG_RW, &sc->spdif_enabled, 0, "enable SPDIF output at 44.1 kHz and above"); return 0; } /* ------------------------------------------------------------------------- */ static kobj_method_t cmi_mixer_methods[] = { KOBJMETHOD(mixer_init, cmimix_init), KOBJMETHOD(mixer_set, cmimix_set), KOBJMETHOD(mixer_setrecsrc, cmimix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(cmi_mixer); /* * mpu401 functions */ static unsigned char cmi_mread(struct mpu401 *arg, void *sc, int reg) { unsigned int d; d = bus_space_read_1(0,0, 0x330 + reg); /* printf("cmi_mread: reg %x %x\n",reg, d); */ return d; } static void cmi_mwrite(struct mpu401 *arg, void *sc, int reg, unsigned char b) { bus_space_write_1(0,0,0x330 + reg , b); } static int cmi_muninit(struct mpu401 *arg, void *cookie) { struct sc_info *sc = cookie; snd_mtxlock(sc->lock); sc->mpu_intr = NULL; sc->mpu = NULL; snd_mtxunlock(sc->lock); return 0; } static kobj_method_t cmi_mpu_methods[] = { KOBJMETHOD(mpufoi_read, cmi_mread), KOBJMETHOD(mpufoi_write, cmi_mwrite), KOBJMETHOD(mpufoi_uninit, cmi_muninit), KOBJMETHOD_END }; static DEFINE_CLASS(cmi_mpu, cmi_mpu_methods, 0); static void cmi_midiattach(struct sc_info *sc) { /* const struct { int port,bits; } *p, ports[] = { {0x330,0}, {0x320,1}, {0x310,2}, {0x300,3}, {0,0} } ; Notes, CMPCI_REG_VMPUSEL sets the io port for the mpu. Does anyone know how to bus_space tag? */ cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); cmi_clr4(sc, CMPCI_REG_LEGACY_CTRL, CMPCI_REG_VMPUSEL_MASK << CMPCI_REG_VMPUSEL_SHIFT); cmi_set4(sc, CMPCI_REG_LEGACY_CTRL, 0 << CMPCI_REG_VMPUSEL_SHIFT ); cmi_set4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); sc->mpu = mpu401_init(&cmi_mpu_class, sc, cmi_intr, &sc->mpu_intr); } /* ------------------------------------------------------------------------- */ /* Power and reset */ static void cmi_power(struct sc_info *sc, int state) { switch (state) { case 0: /* full power */ cmi_clr4(sc, CMPCI_REG_MISC, CMPCI_REG_POWER_DOWN); break; default: /* power off */ cmi_set4(sc, CMPCI_REG_MISC, CMPCI_REG_POWER_DOWN); break; } } static int cmi_init(struct sc_info *sc) { /* Effect reset */ cmi_set4(sc, CMPCI_REG_MISC, CMPCI_REG_BUS_AND_DSP_RESET); DELAY(100); cmi_clr4(sc, CMPCI_REG_MISC, CMPCI_REG_BUS_AND_DSP_RESET); /* Disable interrupts and channels */ cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE | CMPCI_REG_CH1_ENABLE); cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE | CMPCI_REG_CH1_INTR_ENABLE); /* Configure DMA channels, ch0 = play, ch1 = capture */ cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_DIR); cmi_set4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH1_DIR); /* Attempt to enable 4 Channel output */ cmi_set4(sc, CMPCI_REG_MISC, CMPCI_REG_N4SPK3D); /* Disable SPDIF1 - not compatible with config */ cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_SPDIF1_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_SPDIF_LOOP); return 0; } static void cmi_uninit(struct sc_info *sc) { /* Disable interrupts and channels */ cmi_clr4(sc, CMPCI_REG_INTR_CTRL, CMPCI_REG_CH0_INTR_ENABLE | CMPCI_REG_CH1_INTR_ENABLE | CMPCI_REG_TDMA_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE | CMPCI_REG_CH1_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); if( sc->mpu ) sc->mpu_intr = NULL; } /* ------------------------------------------------------------------------- */ /* Bus and device registration */ static int cmi_probe(device_t dev) { switch(pci_get_devid(dev)) { case CMI8338A_PCI_ID: device_set_desc(dev, "CMedia CMI8338A"); return BUS_PROBE_DEFAULT; case CMI8338B_PCI_ID: device_set_desc(dev, "CMedia CMI8338B"); return BUS_PROBE_DEFAULT; case CMI8738_PCI_ID: device_set_desc(dev, "CMedia CMI8738"); return BUS_PROBE_DEFAULT; case CMI8738B_PCI_ID: device_set_desc(dev, "CMedia CMI8738B"); return BUS_PROBE_DEFAULT; case CMI120_USB_ID: device_set_desc(dev, "CMedia CMI120"); return BUS_PROBE_DEFAULT; default: return ENXIO; } } static int cmi_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_cmi softc"); pci_enable_busmaster(dev); sc->dev = dev; sc->regid = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "cmi_attach: Cannot allocate bus resource\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); if (0) cmi_midiattach(sc); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, cmi_intr, sc, &sc->ih)) { device_printf(dev, "cmi_attach: Unable to map interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, CMI_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockfunc*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "cmi_attach: Unable to create dma tag\n"); goto bad; } cmi_power(sc, 0); if (cmi_init(sc)) goto bad; if (mixer_init(dev, &cmi_mixer_class, sc)) goto bad; pcm_init(dev, sc); cmi_initsys(sc); pcm_addchan(dev, PCMDIR_PLAY, &cmichan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cmichan_class, sc); snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) goto bad; DEB(printf("cmi_attach: succeeded\n")); return 0; bad: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); if (sc->lock) snd_mtxfree(sc->lock); if (sc) free(sc, M_DEVBUF); return ENXIO; } static int cmi_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); cmi_uninit(sc); cmi_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if(sc->mpu) mpu401_uninit(sc->mpu); bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); if (sc->mpu_reg) bus_release_resource(dev, SYS_RES_IOPORT, sc->mpu_regid, sc->mpu_reg); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } static int cmi_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); sc->pch.dma_was_active = cmi_ch0_stop(sc, &sc->pch); sc->rch.dma_was_active = cmi_ch1_stop(sc, &sc->rch); cmi_power(sc, 3); snd_mtxunlock(sc->lock); return 0; } static int cmi_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); snd_mtxlock(sc->lock); cmi_power(sc, 0); if (cmi_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); snd_mtxunlock(sc->lock); return ENXIO; } if (sc->pch.dma_was_active) { cmichan_setspeed(NULL, &sc->pch, sc->pch.spd); cmichan_setformat(NULL, &sc->pch, sc->pch.fmt); cmi_ch0_start(sc, &sc->pch); } if (sc->rch.dma_was_active) { cmichan_setspeed(NULL, &sc->rch, sc->rch.spd); cmichan_setformat(NULL, &sc->rch, sc->rch.fmt); cmi_ch1_start(sc, &sc->rch); } snd_mtxunlock(sc->lock); return 0; } static device_method_t cmi_methods[] = { DEVMETHOD(device_probe, cmi_probe), DEVMETHOD(device_attach, cmi_attach), DEVMETHOD(device_detach, cmi_detach), DEVMETHOD(device_resume, cmi_resume), DEVMETHOD(device_suspend, cmi_suspend), { 0, 0 } }; static driver_t cmi_driver = { "pcm", cmi_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_cmi, pci, cmi_driver, 0, 0); MODULE_DEPEND(snd_cmi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_cmi, midi, 1,1,1); MODULE_VERSION(snd_cmi, 1); diff --git a/sys/dev/sound/pci/cs4281.c b/sys/dev/sound/pci/cs4281.c index 7a25f7f4c08d..af2b4da76aba 100644 --- a/sys/dev/sound/pci/cs4281.c +++ b/sys/dev/sound/pci/cs4281.c @@ -1,968 +1,968 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Orion Hodson * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ /* * The order of pokes in the initiation sequence is based on Linux * driver by Thomas Sailer, gw boynton (wesb@crystal.cirrus.com), tom * woller (twoller@crystal.cirrus.com). Shingo Watanabe (nabe@nabechan.org) * contributed towards power management. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #define CS4281_DEFAULT_BUFSZ 16384 /* Max fifo size for full duplex is 64 */ #define CS4281_FIFO_SIZE 15 /* DMA Engine Indices */ #define CS4281_DMA_PLAY 0 #define CS4281_DMA_REC 1 /* Misc */ #define inline __inline #ifndef DEB #define DEB(x) /* x */ #endif /* DEB */ /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; /* channel registers */ struct sc_chinfo { struct sc_info *parent; struct snd_dbuf *buffer; struct pcm_channel *channel; u_int32_t spd, fmt, bps, blksz; int dma_setup, dma_active, dma_chan; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq, *mem; int regtype, regid, irqid, memid; void *ih; int power; unsigned long bufsz; struct sc_chinfo pch; struct sc_chinfo rch; }; /* -------------------------------------------------------------------- */ /* prototypes */ /* ADC/DAC control */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go); static void adcdac_prog(struct sc_chinfo *ch); /* power management and interrupt control */ static void cs4281_intr(void *); static int cs4281_power(struct sc_info *, int); static int cs4281_init(struct sc_info *); /* talk to the card */ static u_int32_t cs4281_rd(struct sc_info *, int); static void cs4281_wr(struct sc_info *, int, u_int32_t); /* misc */ static u_int8_t cs4281_rate_to_rv(u_int32_t); static u_int32_t cs4281_format_to_dmr(u_int32_t); static u_int32_t cs4281_format_to_bps(u_int32_t); /* -------------------------------------------------------------------- */ /* formats (do not add formats without editing cs_fmt_tab) */ static u_int32_t cs4281_fmts[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), SND_FORMAT(AFMT_U16_BE, 1, 0), SND_FORMAT(AFMT_U16_BE, 2, 0), 0 }; static struct pcmchan_caps cs4281_caps = {6024, 48000, cs4281_fmts, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static inline u_int32_t cs4281_rd(struct sc_info *sc, int regno) { return bus_space_read_4(sc->st, sc->sh, regno); } static inline void cs4281_wr(struct sc_info *sc, int regno, u_int32_t data) { bus_space_write_4(sc->st, sc->sh, regno, data); DELAY(100); } static inline void cs4281_clr4(struct sc_info *sc, int regno, u_int32_t mask) { u_int32_t r; r = cs4281_rd(sc, regno); cs4281_wr(sc, regno, r & ~mask); } static inline void cs4281_set4(struct sc_info *sc, int regno, u_int32_t mask) { u_int32_t v; v = cs4281_rd(sc, regno); cs4281_wr(sc, regno, v | mask); } static int cs4281_waitset(struct sc_info *sc, int regno, u_int32_t mask, int tries) { u_int32_t v; while (tries > 0) { DELAY(100); v = cs4281_rd(sc, regno); if ((v & mask) == mask) break; tries --; } return tries; } static int cs4281_waitclr(struct sc_info *sc, int regno, u_int32_t mask, int tries) { u_int32_t v; while (tries > 0) { DELAY(100); v = ~ cs4281_rd(sc, regno); if (v & mask) break; tries --; } return tries; } /* ------------------------------------------------------------------------- */ /* Register value mapping functions */ static u_int32_t cs4281_rates[] = {48000, 44100, 22050, 16000, 11025, 8000}; #define CS4281_NUM_RATES sizeof(cs4281_rates)/sizeof(cs4281_rates[0]) static u_int8_t cs4281_rate_to_rv(u_int32_t rate) { u_int32_t v; for (v = 0; v < CS4281_NUM_RATES; v++) { if (rate == cs4281_rates[v]) return v; } v = 1536000 / rate; if (v > 255 || v < 32) v = 5; /* default to 8k */ return v; } static u_int32_t cs4281_rv_to_rate(u_int8_t rv) { u_int32_t r; if (rv < CS4281_NUM_RATES) return cs4281_rates[rv]; r = 1536000 / rv; return r; } static inline u_int32_t cs4281_format_to_dmr(u_int32_t format) { u_int32_t dmr = 0; if (AFMT_8BIT & format) dmr |= CS4281PCI_DMR_SIZE8; if (AFMT_CHANNEL(format) < 2) dmr |= CS4281PCI_DMR_MONO; if (AFMT_BIGENDIAN & format) dmr |= CS4281PCI_DMR_BEND; if (!(AFMT_SIGNED & format)) dmr |= CS4281PCI_DMR_USIGN; return dmr; } static inline u_int32_t cs4281_format_to_bps(u_int32_t format) { return ((AFMT_8BIT & format) ? 1 : 2) * ((AFMT_CHANNEL(format) > 1) ? 2 : 1); } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int cs4281_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; /* Remove old state */ cs4281_rd(sc, CS4281PCI_ACSDA); /* Fill in AC97 register value request form */ cs4281_wr(sc, CS4281PCI_ACCAD, regno); cs4281_wr(sc, CS4281PCI_ACCDA, 0); cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV | CS4281PCI_ACCTL_CRW); /* Wait for read to complete */ if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { device_printf(sc->dev, "cs4281_rdcd: DCV did not go\n"); return -1; } /* Wait for valid status */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_VSTS, 250) == 0) { device_printf(sc->dev,"cs4281_rdcd: VSTS did not come\n"); return -1; } return cs4281_rd(sc, CS4281PCI_ACSDA); } static int cs4281_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; cs4281_wr(sc, CS4281PCI_ACCAD, regno); cs4281_wr(sc, CS4281PCI_ACCDA, data); cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV); if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { device_printf(sc->dev,"cs4281_wrcd: DCV did not go\n"); } return 0; } static kobj_method_t cs4281_ac97_methods[] = { KOBJMETHOD(ac97_read, cs4281_rdcd), KOBJMETHOD(ac97_write, cs4281_wrcd), KOBJMETHOD_END }; AC97_DECLARE(cs4281_ac97); /* ------------------------------------------------------------------------- */ /* shared rec/play channel interface */ static void * cs4281chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->buffer = b; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { return NULL; } ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->bps = 1; - ch->blksz = sndbuf_getsize(ch->buffer); + ch->blksz = ch->buffer->bufsize; ch->dma_chan = (dir == PCMDIR_PLAY) ? CS4281_DMA_PLAY : CS4281_DMA_REC; ch->dma_setup = 0; adcdac_go(ch, 0); adcdac_prog(ch); return ch; } static u_int32_t cs4281chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t go; go = adcdac_go(ch, 0); /* 2 interrupts are possible and used in buffer (half-empty,empty), * hence factor of 2. */ ch->blksz = MIN(blocksize, sc->bufsz / 2); sndbuf_resize(ch->buffer, 2, ch->blksz); ch->dma_setup = 0; adcdac_prog(ch); adcdac_go(ch, go); DEB(printf("cs4281chan_setblocksize: blksz %d Setting %d\n", blocksize, ch->blksz)); return ch->blksz; } static u_int32_t cs4281chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t go, v, r; go = adcdac_go(ch, 0); /* pause */ r = (ch->dma_chan == CS4281_DMA_PLAY) ? CS4281PCI_DACSR : CS4281PCI_ADCSR; v = cs4281_rate_to_rv(speed); cs4281_wr(sc, r, v); adcdac_go(ch, go); /* unpause */ ch->spd = cs4281_rv_to_rate(v); return ch->spd; } static int cs4281chan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t v, go; go = adcdac_go(ch, 0); /* pause */ if (ch->dma_chan == CS4281_DMA_PLAY) v = CS4281PCI_DMR_TR_PLAY; else v = CS4281PCI_DMR_TR_REC; v |= CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO; v |= cs4281_format_to_dmr(format); cs4281_wr(sc, CS4281PCI_DMR(ch->dma_chan), v); adcdac_go(ch, go); /* unpause */ ch->fmt = format; ch->bps = cs4281_format_to_bps(format); ch->dma_setup = 0; return 0; } static u_int32_t cs4281chan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t dba, dca, ptr; int sz; - sz = sndbuf_getsize(ch->buffer); + sz = ch->buffer->bufsize; dba = cs4281_rd(sc, CS4281PCI_DBA(ch->dma_chan)); dca = cs4281_rd(sc, CS4281PCI_DCA(ch->dma_chan)); ptr = (dca - dba + sz) % sz; return ptr; } static int cs4281chan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; switch(go) { case PCMTRIG_START: adcdac_prog(ch); adcdac_go(ch, 1); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: adcdac_go(ch, 0); break; default: break; } /* return 0 if ok */ return 0; } static struct pcmchan_caps * cs4281chan_getcaps(kobj_t obj, void *data) { return &cs4281_caps; } static kobj_method_t cs4281chan_methods[] = { KOBJMETHOD(channel_init, cs4281chan_init), KOBJMETHOD(channel_setformat, cs4281chan_setformat), KOBJMETHOD(channel_setspeed, cs4281chan_setspeed), KOBJMETHOD(channel_setblocksize, cs4281chan_setblocksize), KOBJMETHOD(channel_trigger, cs4281chan_trigger), KOBJMETHOD(channel_getptr, cs4281chan_getptr), KOBJMETHOD(channel_getcaps, cs4281chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(cs4281chan); /* -------------------------------------------------------------------- */ /* ADC/DAC control */ /* adcdac_go enables/disable DMA channel, returns non-zero if DMA was * active before call */ static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go) { struct sc_info *sc = ch->parent; u_int32_t going; going = !(cs4281_rd(sc, CS4281PCI_DCR(ch->dma_chan)) & CS4281PCI_DCR_MSK); if (go) cs4281_clr4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); else cs4281_set4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); return going; } static void adcdac_prog(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t go; if (!ch->dma_setup) { go = adcdac_go(ch, 0); cs4281_wr(sc, CS4281PCI_DBA(ch->dma_chan), - sndbuf_getbufaddr(ch->buffer)); + ch->buffer->buf_addr); cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan), - sndbuf_getsize(ch->buffer) / ch->bps - 1); + ch->buffer->bufsize / ch->bps - 1); ch->dma_setup = 1; adcdac_go(ch, go); } } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void cs4281_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; u_int32_t hisr; hisr = cs4281_rd(sc, CS4281PCI_HISR); if (hisr == 0) return; if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_PLAY)) { chn_intr(sc->pch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_PLAY)); /* Clear interrupt */ } if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_REC)) { chn_intr(sc->rch.channel); cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_REC)); /* Clear interrupt */ } /* Signal End-of-Interrupt */ cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); } /* -------------------------------------------------------------------- */ /* power management related */ static int cs4281_power(struct sc_info *sc, int state) { switch (state) { case 0: /* Permit r/w access to all BA0 registers */ cs4281_wr(sc, CS4281PCI_CWPR, CS4281PCI_CWPR_MAGIC); /* Power on */ cs4281_clr4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); break; case 3: /* Power off card and codec */ cs4281_set4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN); cs4281_clr4(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); break; } DEB(printf("cs4281_power %d -> %d\n", sc->power, state)); sc->power = state; return 0; } static int cs4281_init(struct sc_info *sc) { u_int32_t i, v; /* (0) Blast clock register and serial port */ cs4281_wr(sc, CS4281PCI_CLKCR1, 0); cs4281_wr(sc, CS4281PCI_SERMC, 0); /* (1) Make ESYN 0 to turn sync pulse on AC97 link */ cs4281_wr(sc, CS4281PCI_ACCTL, 0); DELAY(50); /* (2) Effect Reset */ cs4281_wr(sc, CS4281PCI_SPMC, 0); DELAY(100); cs4281_wr(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); /* Wait 50ms for ABITCLK to become stable */ DELAY(50000); /* (3) Enable Sound System Clocks */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP); DELAY(50000); /* Wait for PLL to stabilize */ cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP | CS4281PCI_CLKCR1_SWCE); /* (4) Power Up - this combination is essential. */ cs4281_set4(sc, CS4281PCI_SSPM, CS4281PCI_SSPM_ACLEN | CS4281PCI_SSPM_PSRCEN | CS4281PCI_SSPM_CSRCEN | CS4281PCI_SSPM_MIXEN); /* (5) Wait for clock stabilization */ if (cs4281_waitset(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLRDY, 250) == 0) { device_printf(sc->dev, "Clock stabilization failed\n"); return -1; } /* (6) Enable ASYNC generation. */ cs4281_wr(sc, CS4281PCI_ACCTL,CS4281PCI_ACCTL_ESYN); /* Wait to allow AC97 to start generating clock bit */ DELAY(50000); /* Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (7) Wait for AC97 ready signal */ if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_CRDY, 250) == 0) { device_printf(sc->dev, "codec did not avail\n"); return -1; } /* (8) Assert valid frame signal to begin sending commands to * AC97 codec */ cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_ESYN); /* (9) Wait for codec calibration */ for(i = 0 ; i < 1000; i++) { DELAY(10000); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if ((v & 0x0f) == 0x0f) { break; } } if (i == 1000) { device_printf(sc->dev, "codec failed to calibrate\n"); return -1; } /* (10) Set AC97 timing */ cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); /* (11) Wait for valid data to arrive */ if (cs4281_waitset(sc, CS4281PCI_ACISV, CS4281PCI_ACISV_ISV(3) | CS4281PCI_ACISV_ISV(4), 10000) == 0) { device_printf(sc->dev, "cs4281 never got valid data\n"); return -1; } /* (12) Start digital data transfer of audio data to codec */ cs4281_wr(sc, CS4281PCI_ACOSV, CS4281PCI_ACOSV_SLV(3) | CS4281PCI_ACOSV_SLV(4)); /* Set Master and headphone to max */ cs4281_wrcd(0, sc, AC97_MIX_AUXOUT, 0); cs4281_wrcd(0, sc, AC97_MIX_MASTER, 0); /* Power on the DAC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfdff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until DAC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x02) break; } /* Power on the ADC */ v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfeff; cs4281_wrcd(0, sc, AC97_REG_POWER, v); /* Wait until ADC state ready */ for(i = 0; i < 320; i++) { DELAY(100); v = cs4281_rdcd(0, sc, AC97_REG_POWER); if (v & 0x01) break; } /* FIFO configuration (driver is DMA orientated, implicit FIFO) */ /* Play FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(0); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v | CS4281PCI_FCR_FEN); /* Record FIFO */ v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_REC_SLOT) | CS4281PCI_FCR_LS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| CS4281PCI_FCR_OF(CS4281_FIFO_SIZE + 1); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_PSH); cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_FEN); /* Match AC97 slots to FIFOs */ v = CS4281PCI_SRCSA_PLSS(CS4281PCI_LPCM_PLAY_SLOT) | CS4281PCI_SRCSA_PRSS(CS4281PCI_RPCM_PLAY_SLOT) | CS4281PCI_SRCSA_CLSS(CS4281PCI_LPCM_REC_SLOT) | CS4281PCI_SRCSA_CRSS(CS4281PCI_RPCM_REC_SLOT); cs4281_wr(sc, CS4281PCI_SRCSA, v); /* Set Auto-Initialize and set directions */ cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_PLAY), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_PLAY); cs4281_wr(sc, CS4281PCI_DMR(CS4281_DMA_REC), CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO | CS4281PCI_DMR_TR_REC); /* Enable half and empty buffer interrupts keeping DMA paused */ cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_PLAY), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); cs4281_wr(sc, CS4281PCI_DCR(CS4281_DMA_REC), CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); /* Enable Interrupts */ cs4281_clr4(sc, CS4281PCI_HIMR, CS4281PCI_HIMR_DMAI | CS4281PCI_HIMR_DMA(CS4281_DMA_PLAY) | CS4281PCI_HIMR_DMA(CS4281_DMA_REC)); /* Set playback volume */ cs4281_wr(sc, CS4281PCI_PPLVC, 7); cs4281_wr(sc, CS4281PCI_PPRVC, 7); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int cs4281_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case CS4281_PCI_ID: s = "Crystal Semiconductor CS4281"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } static int cs4281_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); pci_enable_busmaster(dev); if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { /* Reset the power state. */ device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->memid = PCIR_BAR(1); sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->memid, RF_ACTIVE); if (sc->mem == NULL) { device_printf(dev, "unable to allocate fifo space\n"); goto bad; } sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, 0, cs4281_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, 4096, CS4281_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* power up */ cs4281_power(sc, 0); /* init chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, cs4281_ac97); if (codec == NULL) goto bad; mixer_init(dev, ac97_getmixerclass(), codec); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &cs4281chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &cs4281chan_class, sc); snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", (sc->regtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) goto bad; return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->mem) bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return ENXIO; } static int cs4281_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* power off */ cs4281_power(sc, 3); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); return 0; } static int cs4281_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); sc->rch.dma_active = adcdac_go(&sc->rch, 0); sc->pch.dma_active = adcdac_go(&sc->pch, 0); cs4281_power(sc, 3); return 0; } static int cs4281_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* power up */ cs4281_power(sc, 0); /* initialize chip */ if (cs4281_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } /* restore mixer state */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restore chip state */ cs4281chan_setspeed(NULL, &sc->rch, sc->rch.spd); cs4281chan_setblocksize(NULL, &sc->rch, sc->rch.blksz); cs4281chan_setformat(NULL, &sc->rch, sc->rch.fmt); adcdac_go(&sc->rch, sc->rch.dma_active); cs4281chan_setspeed(NULL, &sc->pch, sc->pch.spd); cs4281chan_setblocksize(NULL, &sc->pch, sc->pch.blksz); cs4281chan_setformat(NULL, &sc->pch, sc->pch.fmt); adcdac_go(&sc->pch, sc->pch.dma_active); return 0; } static device_method_t cs4281_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cs4281_pci_probe), DEVMETHOD(device_attach, cs4281_pci_attach), DEVMETHOD(device_detach, cs4281_pci_detach), DEVMETHOD(device_suspend, cs4281_pci_suspend), DEVMETHOD(device_resume, cs4281_pci_resume), { 0, 0 } }; static driver_t cs4281_driver = { "pcm", cs4281_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_cs4281, pci, cs4281_driver, 0, 0); MODULE_DEPEND(snd_cs4281, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_cs4281, 1); diff --git a/sys/dev/sound/pci/csapcm.c b/sys/dev/sound/pci/csapcm.c index a966a2e66402..688aee6400d8 100644 --- a/sys/dev/sound/pci/csapcm.c +++ b/sys/dev/sound/pci/csapcm.c @@ -1,1039 +1,1039 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Seigo Tanimura * All rights reserved. * * Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in * cwcealdr1.zip, the sample sources by Crystal Semiconductor. * Copyright (c) 1996-1998 Crystal Semiconductor Corp. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include /* Buffer size on dma transfer. Fixed for CS416x. */ #define CS461x_BUFFSIZE (4 * 1024) #define GOF_PER_SEC 200 /* device private data */ struct csa_info; struct csa_chinfo { struct csa_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir; u_int32_t fmt, spd; int dma; }; struct csa_info { csa_res res; /* resource */ void *ih; /* Interrupt cookie */ bus_dma_tag_t parent_dmat; /* DMA tag */ struct csa_bridgeinfo *binfo; /* The state of the parent. */ struct csa_card *card; int active; /* Contents of board's registers */ u_long pfie; u_long pctl; u_long cctl; struct csa_chinfo pch, rch; u_int32_t ac97[CS461x_AC97_NUMBER_RESTORE_REGS]; u_int32_t ac97_powerdown; u_int32_t ac97_general_purpose; }; /* -------------------------------------------------------------------- */ /* prototypes */ static int csa_init(struct csa_info *); static void csa_intr(void *); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate); static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate); static void csa_startplaydma(struct csa_info *csa); static void csa_startcapturedma(struct csa_info *csa); static void csa_stopplaydma(struct csa_info *csa); static void csa_stopcapturedma(struct csa_info *csa); static int csa_startdsp(csa_res *resp); static int csa_stopdsp(csa_res *resp); static int csa_allocres(struct csa_info *scp, device_t dev); static void csa_releaseres(struct csa_info *scp, device_t dev); static void csa_ac97_suspend(struct csa_info *csa); static void csa_ac97_resume(struct csa_info *csa); static u_int32_t csa_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), 0 }; static struct pcmchan_caps csa_playcaps = {8000, 48000, csa_playfmt, 0}; static u_int32_t csa_recfmt[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps csa_reccaps = {11025, 48000, csa_recfmt, 0}; /* -------------------------------------------------------------------- */ static int csa_active(struct csa_info *csa, int run) { int old; old = csa->active; csa->active += run; if ((csa->active > 1) || (csa->active < -1)) csa->active = 0; if (csa->card->active) return (csa->card->active(!(csa->active && old))); return 0; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int csa_rdcd(kobj_t obj, void *devinfo, int regno) { u_int32_t data; struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data)) data = 0; csa_active(csa, -1); return data; } static int csa_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct csa_info *csa = (struct csa_info *)devinfo; csa_active(csa, 1); csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data); csa_active(csa, -1); return 0; } static kobj_method_t csa_ac97_methods[] = { KOBJMETHOD(ac97_read, csa_rdcd), KOBJMETHOD(ac97_write, csa_wrcd), KOBJMETHOD_END }; AC97_DECLARE(csa_ac97); static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate) { u_long ulTemp1, ulTemp2; u_long ulPhiIncr; u_long ulCorrectionPerGOF, ulCorrectionPerSec; u_long ulOutRate; ulOutRate = 48000; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * * i.e. * * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) */ ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; /* * Fill in the SampleRateConverter control block. */ csa_writemem(resp, BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_PPI, ulPhiIncr); } static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate) { u_long ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2; u_long ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay; u_long dwFrameGroupLength, dwCnt; u_long ulInRate; ulInRate = 48000; /* * We can only decimate by up to a factor of 1/9th the hardware rate. * Return an error if an attempt is made to stray outside that limit. */ if((ulOutRate * 9) < ulInRate) return; /* * We can not capture at at rate greater than the Input Rate (48000). * Return an error if an attempt is made to stray outside that limit. */ if(ulOutRate > ulInRate) return; /* * Compute the values used to drive the actual sample rate conversion. * The following formulas are being computed, using inline assembly * since we need to use 64 bit arithmetic to compute the values: * * ulCoeffIncr = -floor((Fs,out * 2^23) / Fs,in) * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / * GOF_PER_SEC) * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - * GOF_PER_SEC * ulCorrectionPerGOF * ulInitialDelay = ceil((24 * Fs,in) / Fs,out) * * i.e. * * ulCoeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) * ulCorrectionPerGOF:ulCorrectionPerSec = * dividend:remainder(ulOther / GOF_PER_SEC) * ulInitialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) */ ulTemp1 = ulOutRate << 16; ulCoeffIncr = ulTemp1 / ulInRate; ulTemp1 -= ulCoeffIncr * ulInRate; ulTemp1 <<= 7; ulCoeffIncr <<= 7; ulCoeffIncr += ulTemp1 / ulInRate; ulCoeffIncr ^= 0xFFFFFFFF; ulCoeffIncr++; ulTemp1 = ulInRate << 16; ulPhiIncr = ulTemp1 / ulOutRate; ulTemp1 -= ulPhiIncr * ulOutRate; ulTemp1 <<= 10; ulPhiIncr <<= 10; ulTemp2 = ulTemp1 / ulOutRate; ulPhiIncr += ulTemp2; ulTemp1 -= ulTemp2 * ulOutRate; ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; ulCorrectionPerSec = ulTemp1; ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate; /* * Fill in the VariDecimate control block. */ csa_writemem(resp, BA1_CSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); csa_writemem(resp, BA1_CCI, ulCoeffIncr); csa_writemem(resp, BA1_CD, (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); csa_writemem(resp, BA1_CPI, ulPhiIncr); /* * Figure out the frame group length for the write back task. Basically, * this is just the factors of 24000 (2^6*3*5^3) that are not present in * the output sample rate. */ dwFrameGroupLength = 1; for(dwCnt = 2; dwCnt <= 64; dwCnt *= 2) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 2; } } if(((ulOutRate / 3) * 3) != ulOutRate) { dwFrameGroupLength *= 3; } for(dwCnt = 5; dwCnt <= 125; dwCnt *= 5) { if(((ulOutRate / dwCnt) * dwCnt) != ulOutRate) { dwFrameGroupLength *= 5; } } /* * Fill in the WriteBack control block. */ csa_writemem(resp, BA1_CFG1, dwFrameGroupLength); csa_writemem(resp, BA1_CFG2, (0x00800000 | dwFrameGroupLength)); csa_writemem(resp, BA1_CCST, 0x0000FFFF); csa_writemem(resp, BA1_CSPB, ((65536 * ulOutRate) / 24000)); csa_writemem(resp, (BA1_CSPB + 4), 0x0000FFFF); } static void csa_startplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); ul &= 0x0000ffff; csa_writemem(resp, BA1_PCTL, ul | csa->pctl); csa_writemem(resp, BA1_PVOL, 0x80008000); csa->pch.dma = 1; } } static void csa_startcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (!csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); ul &= 0xffff0000; csa_writemem(resp, BA1_CCTL, ul | csa->cctl); csa_writemem(resp, BA1_CVOL, 0x80008000); csa->rch.dma = 1; } } static void csa_stopplaydma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->pch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_PCTL); csa->pctl = ul & 0xffff0000; csa_writemem(resp, BA1_PCTL, ul & 0x0000ffff); csa_writemem(resp, BA1_PVOL, 0xffffffff); csa->pch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not capturing. */ if (!csa->rch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static void csa_stopcapturedma(struct csa_info *csa) { csa_res *resp; u_long ul; if (csa->rch.dma) { resp = &csa->res; ul = csa_readmem(resp, BA1_CCTL); csa->cctl = ul & 0x0000ffff; csa_writemem(resp, BA1_CCTL, ul & 0xffff0000); csa_writemem(resp, BA1_CVOL, 0xffffffff); csa->rch.dma = 0; /* * The bitwise pointer of the serial FIFO in the DSP * seems to make an error upon starting or stopping the * DSP. Clear the FIFO and correct the pointer if we * are not playing. */ if (!csa->pch.dma) { csa_clearserialfifos(resp); csa_writeio(resp, BA0_SERBSP, 0); } } } static int csa_startdsp(csa_res *resp) { int i; u_long ul; /* * Set the frame timer to reflect the number of cycles per frame. */ csa_writemem(resp, BA1_FRMT, 0xadf); /* * Turn on the run, run at frame, and DMA enable bits in the local copy of * the SP control register. */ csa_writemem(resp, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); /* * Wait until the run at frame bit resets itself in the SP control * register. */ ul = 0; for (i = 0 ; i < 25 ; i++) { /* * Wait a little bit, so we don't issue PCI reads too frequently. */ DELAY(50); /* * Fetch the current value of the SP status register. */ ul = csa_readmem(resp, BA1_SPCR); /* * If the run at frame bit has reset, then stop waiting. */ if((ul & SPCR_RUNFR) == 0) break; } /* * If the run at frame bit never reset, then return an error. */ if((ul & SPCR_RUNFR) != 0) return (EAGAIN); return (0); } static int csa_stopdsp(csa_res *resp) { /* * Turn off the run, run at frame, and DMA enable bits in * the local copy of the SP control register. */ csa_writemem(resp, BA1_SPCR, 0); return (0); } static int csa_setupchan(struct csa_chinfo *ch) { struct csa_info *csa = ch->parent; csa_res *resp = &csa->res; u_long pdtc, tmp; if (ch->dir == PCMDIR_PLAY) { /* direction */ - csa_writemem(resp, BA1_PBA, sndbuf_getbufaddr(ch->buffer)); + csa_writemem(resp, BA1_PBA, ch->buffer->buf_addr); /* format */ csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f; if (!(ch->fmt & AFMT_SIGNED)) csa->pfie |= 0x8000; if (ch->fmt & AFMT_BIGENDIAN) csa->pfie |= 0x4000; if (AFMT_CHANNEL(ch->fmt) < 2) csa->pfie |= 0x2000; if (ch->fmt & AFMT_8BIT) csa->pfie |= 0x1000; csa_writemem(resp, BA1_PFIE, csa->pfie); tmp = 4; if (ch->fmt & AFMT_16BIT) tmp <<= 1; if (AFMT_CHANNEL(ch->fmt) > 1) tmp <<= 1; tmp--; pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000001ff; pdtc |= tmp; csa_writemem(resp, BA1_PDTC, pdtc); /* rate */ csa_setplaysamplerate(resp, ch->spd); } else if (ch->dir == PCMDIR_REC) { /* direction */ - csa_writemem(resp, BA1_CBA, sndbuf_getbufaddr(ch->buffer)); + csa_writemem(resp, BA1_CBA, ch->buffer->buf_addr); /* format */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* rate */ csa_setcapturesamplerate(resp, ch->spd); } return 0; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * csachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct csa_info *csa = devinfo; struct csa_chinfo *ch = (dir == PCMDIR_PLAY)? &csa->pch : &csa->rch; ch->parent = csa; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, csa->parent_dmat, 0, CS461x_BUFFSIZE) != 0) return NULL; return ch; } static int csachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct csa_chinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t csachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct csa_chinfo *ch = data; ch->spd = speed; return ch->spd; /* XXX calc real speed */ } static u_int32_t csachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return CS461x_BUFFSIZE / 2; } static int csachan_trigger(kobj_t obj, void *data, int go) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { csa_active(csa, 1); csa_setupchan(ch); if (ch->dir == PCMDIR_PLAY) csa_startplaydma(csa); else csa_startcapturedma(csa); } else { if (ch->dir == PCMDIR_PLAY) csa_stopplaydma(csa); else csa_stopcapturedma(csa); csa_active(csa, -1); } return 0; } static u_int32_t csachan_getptr(kobj_t obj, void *data) { struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; csa_res *resp; u_int32_t ptr; resp = &csa->res; if (ch->dir == PCMDIR_PLAY) { - ptr = csa_readmem(resp, BA1_PBA) - sndbuf_getbufaddr(ch->buffer); + ptr = csa_readmem(resp, BA1_PBA) - ch->buffer->buf_addr; if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } else { - ptr = csa_readmem(resp, BA1_CBA) - sndbuf_getbufaddr(ch->buffer); + ptr = csa_readmem(resp, BA1_CBA) - ch->buffer->buf_addr; if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) ptr >>= 1; } return (ptr); } static struct pcmchan_caps * csachan_getcaps(kobj_t obj, void *data) { struct csa_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps; } static kobj_method_t csachan_methods[] = { KOBJMETHOD(channel_init, csachan_init), KOBJMETHOD(channel_setformat, csachan_setformat), KOBJMETHOD(channel_setspeed, csachan_setspeed), KOBJMETHOD(channel_setblocksize, csachan_setblocksize), KOBJMETHOD(channel_trigger, csachan_trigger), KOBJMETHOD(channel_getptr, csachan_getptr), KOBJMETHOD(channel_getcaps, csachan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(csachan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void csa_intr(void *p) { struct csa_info *csa = p; if ((csa->binfo->hisr & HISR_VC0) != 0) chn_intr(csa->pch.channel); if ((csa->binfo->hisr & HISR_VC1) != 0) chn_intr(csa->rch.channel); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int csa_init(struct csa_info *csa) { csa_res *resp; resp = &csa->res; csa->pfie = 0; csa_stopplaydma(csa); csa_stopcapturedma(csa); if (csa_startdsp(resp)) return (1); /* Crank up the power on the DAC and ADC. */ csa_setplaysamplerate(resp, 8000); csa_setcapturesamplerate(resp, 8000); /* Set defaults */ csa_writeio(resp, BA0_EGPIODR, EGPIODR_GPOE0); csa_writeio(resp, BA0_EGPIOPTR, EGPIOPTR_GPPT0); /* Power up amplifier */ csa_writeio(resp, BA0_EGPIODR, csa_readio(resp, BA0_EGPIODR) | EGPIODR_GPOE2); csa_writeio(resp, BA0_EGPIOPTR, csa_readio(resp, BA0_EGPIOPTR) | EGPIOPTR_GPPT2); return 0; } /* Allocates resources. */ static int csa_allocres(struct csa_info *csa, device_t dev) { csa_res *resp; resp = &csa->res; if (resp->io == NULL) { resp->io = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->io_rid, RF_ACTIVE); if (resp->io == NULL) return (1); } if (resp->mem == NULL) { resp->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &resp->mem_rid, RF_ACTIVE); if (resp->mem == NULL) return (1); } if (resp->irq == NULL) { resp->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &resp->irq_rid, RF_ACTIVE | RF_SHAREABLE); if (resp->irq == NULL) return (1); } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/CS461x_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &csa->parent_dmat) != 0) return (1); return (0); } /* Releases resources. */ static void csa_releaseres(struct csa_info *csa, device_t dev) { csa_res *resp; KASSERT(csa != NULL, ("called with bogus resource structure")); resp = &csa->res; if (resp->irq != NULL) { if (csa->ih) bus_teardown_intr(dev, resp->irq, csa->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); resp->irq = NULL; } if (resp->io != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); resp->io = NULL; } if (resp->mem != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); resp->mem = NULL; } if (csa->parent_dmat != NULL) { bus_dma_tag_destroy(csa->parent_dmat); csa->parent_dmat = NULL; } free(csa, M_DEVBUF); } static int pcmcsa_probe(device_t dev) { char *s; struct sndcard_func *func; /* The parent device has already been probed. */ func = device_get_ivars(dev); if (func == NULL || func->func != SCF_PCM) return (ENXIO); s = "CS461x PCM Audio"; device_set_desc(dev, s); return (0); } static int pcmcsa_attach(device_t dev) { struct csa_info *csa; csa_res *resp; char status[SND_STATUSLEN]; struct ac97_info *codec; struct sndcard_func *func; csa = malloc(sizeof(*csa), M_DEVBUF, M_WAITOK | M_ZERO); func = device_get_ivars(dev); csa->binfo = func->varinfo; /* * Fake the status of DMA so that the initial value of * PCTL and CCTL can be stored into csa->pctl and csa->cctl, * respectively. */ csa->pch.dma = csa->rch.dma = 1; csa->active = 0; csa->card = csa->binfo->card; /* Allocate the resources. */ resp = &csa->res; resp->io_rid = PCIR_BAR(0); resp->mem_rid = PCIR_BAR(1); resp->irq_rid = 0; if (csa_allocres(csa, dev)) { csa_releaseres(csa, dev); return (ENXIO); } csa_active(csa, 1); if (csa_init(csa)) { csa_releaseres(csa, dev); return (ENXIO); } codec = AC97_CREATE(dev, csa, csa_ac97); if (codec == NULL) { csa_releaseres(csa, dev); return (ENXIO); } if (csa->card->inv_eapd) ac97_setflags(codec, AC97_F_EAPD_INV); if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } snprintf(status, SND_STATUSLEN, "irq %jd on %s", rman_get_start(resp->irq), device_get_nameunit(device_get_parent(dev))); /* Enable interrupt. */ if (snd_setup_intr(dev, resp->irq, 0, csa_intr, csa, &csa->ih)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); csa_active(csa, -1); pcm_init(dev, csa); pcm_addchan(dev, PCMDIR_REC, &csachan_class, csa); pcm_addchan(dev, PCMDIR_PLAY, &csachan_class, csa); if (pcm_register(dev, status)) { ac97_destroy(codec); csa_releaseres(csa, dev); return (ENXIO); } return (0); } static int pcmcsa_detach(device_t dev) { int r; struct csa_info *csa; r = pcm_unregister(dev); if (r) return r; csa = pcm_getdevinfo(dev); csa_releaseres(csa, dev); return 0; } static void csa_ac97_suspend(struct csa_info *csa) { int count, i; uint32_t tmp; for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_readcodec(&csa->res, BA0_AC97_RESET + count, &csa->ac97[i]); /* mute the outputs */ csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_HEADPHONE_VOLUME, 0x8000); csa_writecodec(&csa->res, BA0_AC97_MASTER_VOLUME_MONO, 0x8000); csa_writecodec(&csa->res, BA0_AC97_PCM_OUT_VOLUME, 0x8000); /* save the registers that cause pops */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &csa->ac97_powerdown); csa_readcodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, &csa->ac97_general_purpose); /* * And power down everything on the AC97 codec. Well, for now, * only power down the DAC/ADC and MIXER VREFON components. * trouble with removing VREF. */ /* MIXVON */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_MIXVON); /* ADC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_ADC); /* DAC */ csa_readcodec(&csa->res, BA0_AC97_POWERDOWN, &tmp); csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, tmp | CS_AC97_POWER_CONTROL_DAC); } static void csa_ac97_resume(struct csa_info *csa) { int count, i; /* * First, we restore the state of the general purpose register. This * contains the mic select (mic1 or mic2) and if we restore this after * we restore the mic volume/boost state and mic2 was selected at * suspend time, we will end up with a brief period of time where mic1 * is selected with the volume/boost settings for mic2, causing * acoustic feedback. So we restore the general purpose register * first, thereby getting the correct mic selected before we restore * the mic volume/boost. */ csa_writecodec(&csa->res, BA0_AC97_GENERAL_PURPOSE, csa->ac97_general_purpose); /* * Now, while the outputs are still muted, restore the state of power * on the AC97 part. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, csa->ac97_powerdown); /* * Restore just the first set of registers, from register number * 0x02 to the register number that ulHighestRegToRestore specifies. */ for (count = 0x2, i=0; (count <= CS461x_AC97_HIGHESTREGTORESTORE) && (i < CS461x_AC97_NUMBER_RESTORE_REGS); count += 2, i++) csa_writecodec(&csa->res, BA0_AC97_RESET + count, csa->ac97[i]); } static int pcmcsa_suspend(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* playback interrupt disable */ csa_writemem(resp, BA1_PFIE, (csa_readmem(resp, BA1_PFIE) & ~0x0000f03f) | 0x00000010); /* capture interrupt disable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000011); csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_suspend(csa); csa_resetdsp(resp); csa_stopdsp(resp); /* * Power down the DAC and ADC. For now leave the other areas on. */ csa_writecodec(&csa->res, BA0_AC97_POWERDOWN, 0x300); /* * Power down the PLL. */ csa_writemem(resp, BA0_CLKCR1, 0); /* * Turn off the Processor by turning off the software clock * enable flag in the clock control register. */ csa_writemem(resp, BA0_CLKCR1, csa_readmem(resp, BA0_CLKCR1) & ~CLKCR1_SWCE); csa_active(csa, -1); return 0; } static int pcmcsa_resume(device_t dev) { struct csa_info *csa; csa_res *resp; csa = pcm_getdevinfo(dev); resp = &csa->res; csa_active(csa, 1); /* cs_hardware_init */ csa_stopplaydma(csa); csa_stopcapturedma(csa); csa_ac97_resume(csa); if (csa_startdsp(resp)) return (ENXIO); /* Enable interrupts on the part. */ if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); /* playback interrupt enable */ csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); /* capture interrupt enable */ csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); /* cs_restart_part */ csa_setupchan(&csa->pch); csa_startplaydma(csa); csa_setupchan(&csa->rch); csa_startcapturedma(csa); csa_active(csa, -1); return 0; } static device_method_t pcmcsa_methods[] = { /* Device interface */ DEVMETHOD(device_probe , pcmcsa_probe ), DEVMETHOD(device_attach, pcmcsa_attach), DEVMETHOD(device_detach, pcmcsa_detach), DEVMETHOD(device_suspend, pcmcsa_suspend), DEVMETHOD(device_resume, pcmcsa_resume), { 0, 0 }, }; static driver_t pcmcsa_driver = { "pcm", pcmcsa_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_csapcm, csa, pcmcsa_driver, 0, 0); MODULE_DEPEND(snd_csapcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_csapcm, snd_csa, 1, 1, 1); MODULE_VERSION(snd_csapcm, 1); diff --git a/sys/dev/sound/pci/emu10k1.c b/sys/dev/sound/pci/emu10k1.c index e4b2c22f4f07..a85031977f4b 100644 --- a/sys/dev/sound/pci/emu10k1.c +++ b/sys/dev/sound/pci/emu10k1.c @@ -1,2249 +1,2249 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004 David O'Brien * Copyright (c) 2003 Orlando Bassotto * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include "mpufoi_if.h" /* -------------------------------------------------------------------- */ #define NUM_G 64 /* use all channels */ #define WAVEOUT_MAXBUFSIZE 32768 #define EMUPAGESIZE 4096 /* don't change */ #define EMUMAXPAGES (WAVEOUT_MAXBUFSIZE * NUM_G / EMUPAGESIZE) #define EMU10K1_PCI_ID 0x00021102 /* 1102 => Creative Labs Vendor ID */ #define EMU10K2_PCI_ID 0x00041102 #define EMU10K3_PCI_ID 0x00081102 #define EMU_DEFAULT_BUFSZ 4096 #define EMU_MAX_CHANS 8 #define EMU_CHANS 4 #define MAXREQVOICES 8 #define RESERVED 0 #define NUM_MIDI 16 #define NUM_FXSENDS 4 #define TMEMSIZE 256*1024 #define TMEMSIZEREG 4 #define ENABLE 0xffffffff #define DISABLE 0x00000000 #define ENV_ON EMU_CHAN_DCYSUSV_CHANNELENABLE_MASK #define ENV_OFF 0x00 /* XXX: should this be 1? */ #define EMU_A_IOCFG_GPOUT_A 0x40 #define EMU_A_IOCFG_GPOUT_D 0x04 #define EMU_A_IOCFG_GPOUT_AD (EMU_A_IOCFG_GPOUT_A|EMU_A_IOCFG_GPOUT_D) /* EMU_A_IOCFG_GPOUT0 */ #define EMU_HCFG_GPOUT1 0x00000800 /* instruction set */ #define iACC3 0x06 #define iMACINT0 0x04 #define iINTERP 0x0e #define C_00000000 0x40 #define C_00000001 0x41 #define C_00000004 0x44 #define C_40000000 0x4d /* Audigy constants */ #define A_C_00000000 0xc0 #define A_C_40000000 0xcd /* GPRs */ #define FXBUS(x) (0x00 + (x)) #define EXTIN(x) (0x10 + (x)) #define EXTOUT(x) (0x20 + (x)) #define GPR(x) (EMU_FXGPREGBASE + (x)) #define A_EXTIN(x) (0x40 + (x)) #define A_FXBUS(x) (0x00 + (x)) #define A_EXTOUT(x) (0x60 + (x)) #define A_GPR(x) (EMU_A_FXGPREGBASE + (x)) /* FX buses */ #define FXBUS_PCM_LEFT 0x00 #define FXBUS_PCM_RIGHT 0x01 #define FXBUS_MIDI_LEFT 0x04 #define FXBUS_MIDI_RIGHT 0x05 #define FXBUS_MIDI_REVERB 0x0c #define FXBUS_MIDI_CHORUS 0x0d /* Inputs */ #define EXTIN_AC97_L 0x00 #define EXTIN_AC97_R 0x01 #define EXTIN_SPDIF_CD_L 0x02 #define EXTIN_SPDIF_CD_R 0x03 #define EXTIN_TOSLINK_L 0x06 #define EXTIN_TOSLINK_R 0x07 #define EXTIN_COAX_SPDIF_L 0x0a #define EXTIN_COAX_SPDIF_R 0x0b /* Audigy Inputs */ #define A_EXTIN_AC97_L 0x00 #define A_EXTIN_AC97_R 0x01 /* Outputs */ #define EXTOUT_AC97_L 0x00 #define EXTOUT_AC97_R 0x01 #define EXTOUT_TOSLINK_L 0x02 #define EXTOUT_TOSLINK_R 0x03 #define EXTOUT_AC97_CENTER 0x04 #define EXTOUT_AC97_LFE 0x05 #define EXTOUT_HEADPHONE_L 0x06 #define EXTOUT_HEADPHONE_R 0x07 #define EXTOUT_REAR_L 0x08 #define EXTOUT_REAR_R 0x09 #define EXTOUT_ADC_CAP_L 0x0a #define EXTOUT_ADC_CAP_R 0x0b #define EXTOUT_ACENTER 0x11 #define EXTOUT_ALFE 0x12 /* Audigy Outputs */ #define A_EXTOUT_FRONT_L 0x00 #define A_EXTOUT_FRONT_R 0x01 #define A_EXTOUT_CENTER 0x02 #define A_EXTOUT_LFE 0x03 #define A_EXTOUT_HEADPHONE_L 0x04 #define A_EXTOUT_HEADPHONE_R 0x05 #define A_EXTOUT_REAR_L 0x06 #define A_EXTOUT_REAR_R 0x07 #define A_EXTOUT_AFRONT_L 0x08 #define A_EXTOUT_AFRONT_R 0x09 #define A_EXTOUT_ACENTER 0x0a #define A_EXTOUT_ALFE 0x0b #define A_EXTOUT_AREAR_L 0x0e #define A_EXTOUT_AREAR_R 0x0f #define A_EXTOUT_AC97_L 0x10 #define A_EXTOUT_AC97_R 0x11 #define A_EXTOUT_ADC_CAP_L 0x16 #define A_EXTOUT_ADC_CAP_R 0x17 struct emu_memblk { SLIST_ENTRY(emu_memblk) link; void *buf; bus_addr_t buf_addr; u_int32_t pte_start, pte_size; bus_dmamap_t buf_map; }; struct emu_mem { u_int8_t bmap[EMUMAXPAGES / 8]; u_int32_t *ptb_pages; void *silent_page; bus_addr_t silent_page_addr; bus_addr_t ptb_pages_addr; bus_dmamap_t ptb_map; bus_dmamap_t silent_map; SLIST_HEAD(, emu_memblk) blocks; }; struct emu_voice { int vnum; unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start, end, vol; int fxrt1; /* FX routing */ int fxrt2; /* FX routing (only for audigy) */ u_int32_t buf; struct emu_voice *slave; struct pcm_channel *channel; }; struct sc_info; /* channel registers */ struct sc_pchinfo { int spd, fmt, blksz, run; struct emu_voice *master, *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; struct sc_rchinfo { int spd, fmt, run, blksz, num; u_int32_t idxreg, basereg, sizereg, setupreg, irqmask; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type, rev; u_int32_t tos_link:1, APS:1, audigy:1, audigy2:1; u_int32_t addrmask; /* wider if audigy */ bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; void *ih; struct mtx *lock; unsigned int bufsz; int timer, timerinterval; int pnum, rnum; int nchans; struct emu_mem mem; struct emu_voice voice[64]; struct sc_pchinfo pch[EMU_MAX_CHANS]; struct sc_rchinfo rch[3]; struct mpu401 *mpu; mpu401_intr_t *mpu_intr; int mputx; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int emu_init(struct sc_info *); static void emu_intr(void *); static void *emu_malloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr, bus_dmamap_t *map); static void *emu_memalloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr); static int emu_memfree(struct sc_info *sc, void *buf); static int emu_memstart(struct sc_info *sc, void *buf); #ifdef EMUDEBUG static void emu_vdump(struct sc_info *sc, struct emu_voice *v); #endif /* talk to the card */ static u_int32_t emu_rd(struct sc_info *, int, int); static void emu_wr(struct sc_info *, int, u_int32_t, int); /* -------------------------------------------------------------------- */ static u_int32_t emu_rfmt_ac97[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static u_int32_t emu_rfmt_mic[] = { SND_FORMAT(AFMT_U8, 1, 0), 0 }; static u_int32_t emu_rfmt_efx[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_reccaps[3] = { {8000, 48000, emu_rfmt_ac97, 0}, {8000, 8000, emu_rfmt_mic, 0}, {48000, 48000, emu_rfmt_efx, 0}, }; static u_int32_t emu_pfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static int adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int audigy_adcspeed[9] = { 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000 }; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t emu_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return bus_space_read_1(sc->st, sc->sh, regno); case 2: return bus_space_read_2(sc->st, sc->sh, regno); case 4: return bus_space_read_4(sc->st, sc->sh, regno); default: return 0xffffffff; } } static void emu_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->st, sc->sh, regno, data); break; case 2: bus_space_write_2(sc->st, sc->sh, regno, data); break; case 4: bus_space_write_4(sc->st, sc->sh, regno, data); break; } } static u_int32_t emu_rdptr(struct sc_info *sc, int chn, int reg) { u_int32_t ptr, val, mask, size, offset; ptr = ((reg << 16) & sc->addrmask) | (chn & EMU_PTR_CHNO_MASK); emu_wr(sc, EMU_PTR, ptr, 4); val = emu_rd(sc, EMU_DATA, 4); if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; val &= mask; val >>= offset; } return val; } static void emu_wrptr(struct sc_info *sc, int chn, int reg, u_int32_t data) { u_int32_t ptr, mask, size, offset; ptr = ((reg << 16) & sc->addrmask) | (chn & EMU_PTR_CHNO_MASK); emu_wr(sc, EMU_PTR, ptr, 4); if (reg & 0xff000000) { size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; data <<= offset; data &= mask; data |= emu_rd(sc, EMU_DATA, 4) & ~mask; } emu_wr(sc, EMU_DATA, data, 4); } static void emu_wrefx(struct sc_info *sc, unsigned int pc, unsigned int data) { pc += sc->audigy ? EMU_A_MICROCODEBASE : EMU_MICROCODEBASE; emu_wrptr(sc, 0, pc, data); } /* -------------------------------------------------------------------- */ /* ac97 codec */ /* no locking needed */ static int emu_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; emu_wr(sc, EMU_AC97ADDR, regno, 1); return emu_rd(sc, EMU_AC97DATA, 2); } static int emu_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; emu_wr(sc, EMU_AC97ADDR, regno, 1); emu_wr(sc, EMU_AC97DATA, data, 2); return 0; } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_ac97); /* -------------------------------------------------------------------- */ /* stuff */ static int emu_settimer(struct sc_info *sc) { struct sc_pchinfo *pch; struct sc_rchinfo *rch; int i, tmp, rate; rate = 0; for (i = 0; i < sc->nchans; i++) { pch = &sc->pch[i]; if (pch->buffer) { - tmp = (pch->spd * sndbuf_getalign(pch->buffer)) + tmp = (pch->spd * pch->buffer->align) / pch->blksz; if (tmp > rate) rate = tmp; } } for (i = 0; i < 3; i++) { rch = &sc->rch[i]; if (rch->buffer) { - tmp = (rch->spd * sndbuf_getalign(rch->buffer)) + tmp = (rch->spd * rch->buffer->align) / rch->blksz; if (tmp > rate) rate = tmp; } } RANGE(rate, 48, 9600); sc->timerinterval = 48000 / rate; emu_wr(sc, EMU_TIMER, sc->timerinterval & 0x03ff, 2); return sc->timerinterval; } static int emu_enatimer(struct sc_info *sc, int go) { u_int32_t x; if (go) { if (sc->timer++ == 0) { x = emu_rd(sc, EMU_INTE, 4); x |= EMU_INTE_INTERTIMERENB; emu_wr(sc, EMU_INTE, x, 4); } } else { sc->timer = 0; x = emu_rd(sc, EMU_INTE, 4); x &= ~EMU_INTE_INTERTIMERENB; emu_wr(sc, EMU_INTE, x, 4); } return 0; } static void emu_enastop(struct sc_info *sc, char channel, int enable) { int reg = (channel & 0x20) ? EMU_SOLEH : EMU_SOLEL; channel &= 0x1f; reg |= 1 << 24; reg |= channel << 16; emu_wrptr(sc, 0, reg, enable); } static int emu_recval(int speed) { int val; val = 0; while (val < 7 && speed < adcspeed[val]) val++; return val; } static int audigy_recval(int speed) { int val; val = 0; while (val < 8 && speed < audigy_adcspeed[val]) val++; return val; } static u_int32_t emu_rate_to_pitch(u_int32_t rate) { static u_int32_t logMagTable[128] = { 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df }; static char logSlopeTable[128] = { 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f }; int i; if (rate == 0) return 0; /* Bail out if no leading "1" */ rate *= 11185; /* Scale 48000 to 0x20002380 */ for (i = 31; i > 0; i--) { if (rate & 0x80000000) { /* Detect leading "1" */ return (((u_int32_t) (i - 15) << 20) + logMagTable[0x7f & (rate >> 24)] + (0x7f & (rate >> 17)) * logSlopeTable[0x7f & (rate >> 24)]); } rate <<= 1; } return 0; /* Should never reach this point */ } static u_int32_t emu_rate_to_linearpitch(u_int32_t rate) { rate = (rate << 8) / 375; return (rate >> 1) + (rate & 1); } static struct emu_voice * emu_valloc(struct sc_info *sc) { struct emu_voice *v; int i; v = NULL; for (i = 0; i < 64 && sc->voice[i].busy; i++); if (i < 64) { v = &sc->voice[i]; v->busy = 1; } return v; } static int emu_vinit(struct sc_info *sc, struct emu_voice *m, struct emu_voice *s, u_int32_t sz, struct snd_dbuf *b) { void *buf; bus_addr_t tmp_addr; buf = emu_memalloc(sc, sz, &tmp_addr); if (buf == NULL) return -1; if (b != NULL) sndbuf_setup(b, buf, sz); m->start = emu_memstart(sc, buf) * EMUPAGESIZE; m->end = m->start + sz; m->channel = NULL; m->speed = 0; m->b16 = 0; m->stereo = 0; m->running = 0; m->ismaster = 1; m->vol = 0xff; m->buf = tmp_addr; m->slave = s; if (sc->audigy) { m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 8 | FXBUS_PCM_LEFT << 16 | FXBUS_MIDI_REVERB << 24; m->fxrt2 = 0x3f3f3f3f; /* No effects on second route */ } else { m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 4 | FXBUS_PCM_LEFT << 8 | FXBUS_MIDI_REVERB << 12; m->fxrt2 = 0; } if (s != NULL) { s->start = m->start; s->end = m->end; s->channel = NULL; s->speed = 0; s->b16 = 0; s->stereo = 0; s->running = 0; s->ismaster = 0; s->vol = m->vol; s->buf = m->buf; s->fxrt1 = m->fxrt1; s->fxrt2 = m->fxrt2; s->slave = NULL; } return 0; } static void emu_vsetup(struct sc_pchinfo *ch) { struct emu_voice *v = ch->master; if (ch->fmt) { v->b16 = (ch->fmt & AFMT_16BIT) ? 1 : 0; v->stereo = (AFMT_CHANNEL(ch->fmt) > 1) ? 1 : 0; if (v->slave != NULL) { v->slave->b16 = v->b16; v->slave->stereo = v->stereo; } } if (ch->spd) { v->speed = ch->spd; if (v->slave != NULL) v->slave->speed = v->speed; } } static void emu_vwrite(struct sc_info *sc, struct emu_voice *v) { int s; int l, r, x, y; u_int32_t sa, ea, start, val, silent_page; s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0); sa = v->start >> s; ea = v->end >> s; l = r = x = y = v->vol; if (v->stereo) { l = v->ismaster ? l : 0; r = v->ismaster ? 0 : r; } emu_wrptr(sc, v->vnum, EMU_CHAN_CPF, v->stereo ? EMU_CHAN_CPF_STEREO_MASK : 0); val = v->stereo ? 28 : 30; val *= v->b16 ? 1 : 2; start = sa + val; if (sc->audigy) { emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT1, v->fxrt1); emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT2, v->fxrt2); emu_wrptr(sc, v->vnum, EMU_A_CHAN_SENDAMOUNTS, 0); } else emu_wrptr(sc, v->vnum, EMU_CHAN_FXRT, v->fxrt1 << 16); emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX, (x << 8) | r); emu_wrptr(sc, v->vnum, EMU_CHAN_DSL, ea | (y << 24)); emu_wrptr(sc, v->vnum, EMU_CHAN_PSST, sa | (l << 24)); emu_wrptr(sc, v->vnum, EMU_CHAN_CCCA, start | (v->b16 ? 0 : EMU_CHAN_CCCA_8BITSELECT)); emu_wrptr(sc, v->vnum, EMU_CHAN_Z1, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_Z2, 0); silent_page = ((u_int32_t)(sc->mem.silent_page_addr) << 1) | EMU_CHAN_MAP_PTI_MASK; emu_wrptr(sc, v->vnum, EMU_CHAN_MAPA, silent_page); emu_wrptr(sc, v->vnum, EMU_CHAN_MAPB, silent_page); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, EMU_CHAN_CVCF_CURRFILTER_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, EMU_CHAN_VTFT_FILTERTARGET_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDM, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSM, EMU_CHAN_DCYSUSM_DECAYTIME_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL1, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL2, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_FMMOD, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_TREMFRQ, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_FM2FRQ2, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVAL, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDV, EMU_CHAN_ATKHLDV_HOLDTIME_MASK | EMU_CHAN_ATKHLDV_ATTACKTIME_MASK); emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVOL, 0x8000); emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_FILTERAMOUNT, 0x7f); emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_PITCHAMOUNT, 0); if (v->slave != NULL) emu_vwrite(sc, v->slave); } static void emu_vtrigger(struct sc_info *sc, struct emu_voice *v, int go) { u_int32_t pitch_target, initial_pitch; u_int32_t cra, cs, ccis; u_int32_t sample, i; if (go) { cra = 64; cs = v->stereo ? 4 : 2; ccis = v->stereo ? 28 : 30; ccis *= v->b16 ? 1 : 2; sample = v->b16 ? 0x00000000 : 0x80808080; for (i = 0; i < cs; i++) emu_wrptr(sc, v->vnum, EMU_CHAN_CD0 + i, sample); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_READADDRESS, cra); emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, ccis); emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xff00); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0xffffffff); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0xffffffff); emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSV, 0x00007f7f); emu_enastop(sc, v->vnum, 0); pitch_target = emu_rate_to_linearpitch(v->speed); initial_pitch = emu_rate_to_pitch(v->speed) >> 8; emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, pitch_target); emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, pitch_target); emu_wrptr(sc, v->vnum, EMU_CHAN_IP, initial_pitch); } else { emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, 0); emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xffff); emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0x0000ffff); emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0x0000ffff); emu_wrptr(sc, v->vnum, EMU_CHAN_IP, 0); emu_enastop(sc, v->vnum, 1); } if (v->slave != NULL) emu_vtrigger(sc, v->slave, go); } static int emu_vpos(struct sc_info *sc, struct emu_voice *v) { int s, ptr; s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0); ptr = (emu_rdptr(sc, v->vnum, EMU_CHAN_CCCA_CURRADDR) - (v->start >> s)) << s; return ptr & ~0x0000001f; } #ifdef EMUDEBUG static void emu_vdump(struct sc_info *sc, struct emu_voice *v) { char *regname[] = { "cpf", "ptrx", "cvcf", "vtft", "z2", "z1", "psst", "dsl", "ccca", "ccr", "clp", "fxrt", "mapa", "mapb", NULL, NULL, "envvol", "atkhldv", "dcysusv", "lfoval1", "envval", "atkhldm", "dcysusm", "lfoval2", "ip", "ifatn", "pefe", "fmmod", "tremfrq", "fmfrq2", "tempenv" }; char *regname2[] = { "mudata1", "mustat1", "mudata2", "mustat2", "fxwc1", "fxwc2", "spdrate", NULL, NULL, NULL, NULL, NULL, "fxrt2", "sndamnt", "fxrt1", NULL, NULL }; int i, x; printf("voice number %d\n", v->vnum); for (i = 0, x = 0; i <= 0x1e; i++) { if (regname[i] == NULL) continue; printf("%s\t[%08x]", regname[i], emu_rdptr(sc, v->vnum, i)); printf("%s", (x == 2) ? "\n" : "\t"); x++; if (x > 2) x = 0; } /* Print out audigy extra registers */ if (sc->audigy) { for (i = 0; i <= 0xe; i++) { if (regname2[i] == NULL) continue; printf("%s\t[%08x]", regname2[i], emu_rdptr(sc, v->vnum, i + 0x70)); printf("%s", (x == 2)? "\n" : "\t"); x++; if (x > 2) x = 0; } } printf("\n\n"); } #endif /* channel interface */ static void * emupchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); ch = &sc->pch[sc->pnum++]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; snd_mtxlock(sc->lock); ch->master = emu_valloc(sc); ch->slave = emu_valloc(sc); snd_mtxunlock(sc->lock); r = (emu_vinit(sc, ch->master, ch->slave, sc->bufsz, ch->buffer)) ? NULL : ch; return r; } static int emupchan_free(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); - r = emu_memfree(sc, sndbuf_getbuf(ch->buffer)); + r = emu_memfree(sc, ch->buffer->buf); snd_mtxunlock(sc->lock); return r; } static int emupchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_pchinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t emupchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_pchinfo *ch = data; ch->spd = speed; return ch->spd; } static u_int32_t emupchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; ch->blksz = blocksize; snd_mtxlock(sc->lock); emu_settimer(sc); snd_mtxunlock(sc->lock); return blocksize; } static int emupchan_trigger(kobj_t obj, void *data, int go) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); if (go == PCMTRIG_START) { emu_vsetup(ch); emu_vwrite(sc, ch->master); emu_settimer(sc); emu_enatimer(sc, 1); #ifdef EMUDEBUG printf("start [%d bit, %s, %d hz]\n", ch->master->b16 ? 16 : 8, ch->master->stereo ? "stereo" : "mono", ch->master->speed); emu_vdump(sc, ch->master); emu_vdump(sc, ch->slave); #endif } ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc, ch->master, ch->run); snd_mtxunlock(sc->lock); return 0; } static u_int32_t emupchan_getptr(kobj_t obj, void *data) { struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_vpos(sc, ch->master); snd_mtxunlock(sc->lock); return r; } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj, void *data) { return &emu_playcaps; } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emupchan); /* channel interface */ static void * emurchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch[sc->rnum]; ch->buffer = b; ch->parent = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->num = sc->rnum; switch(sc->rnum) { case 0: ch->idxreg = sc->audigy ? EMU_A_ADCIDX : EMU_ADCIDX; ch->basereg = EMU_ADCBA; ch->sizereg = EMU_ADCBS; ch->setupreg = EMU_ADCCR; ch->irqmask = EMU_INTE_ADCBUFENABLE; break; case 1: ch->idxreg = EMU_FXIDX; ch->basereg = EMU_FXBA; ch->sizereg = EMU_FXBS; ch->setupreg = EMU_FXWC; ch->irqmask = EMU_INTE_EFXBUFENABLE; break; case 2: ch->idxreg = EMU_MICIDX; ch->basereg = EMU_MICBA; ch->sizereg = EMU_MICBS; ch->setupreg = 0; ch->irqmask = EMU_INTE_MICBUFENABLE; break; } sc->rnum++; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; else { snd_mtxlock(sc->lock); - emu_wrptr(sc, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc, 0, ch->basereg, ch->buffer->buf_addr); emu_wrptr(sc, 0, ch->sizereg, 0); /* off */ snd_mtxunlock(sc->lock); return ch; } } static int emurchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_rchinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t emurchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_rchinfo *ch = data; if (ch->num == 0) { if (ch->parent->audigy) speed = audigy_adcspeed[audigy_recval(speed)]; else speed = adcspeed[emu_recval(speed)]; } if (ch->num == 1) speed = 48000; if (ch->num == 2) speed = 8000; ch->spd = speed; return ch->spd; } static u_int32_t emurchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; ch->blksz = blocksize; snd_mtxlock(sc->lock); emu_settimer(sc); snd_mtxunlock(sc->lock); return blocksize; } /* semantic note: must start at beginning of buffer */ static int emurchan_trigger(kobj_t obj, void *data, int go) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t val, sz; if (!PCMTRIG_COMMON(go)) return 0; switch(sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc, 0, ch->sizereg, sz); if (ch->num == 0) { if (sc->audigy) { val = EMU_A_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= EMU_A_ADCCR_RCHANENABLE; val |= audigy_recval(ch->spd); } else { val = EMU_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= EMU_ADCCR_RCHANENABLE; val |= emu_recval(ch->spd); } emu_wrptr(sc, 0, ch->setupreg, 0); emu_wrptr(sc, 0, ch->setupreg, val); } val = emu_rd(sc, EMU_INTE, 4); val |= ch->irqmask; emu_wr(sc, EMU_INTE, val, 4); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc, 0, ch->setupreg, 0); val = emu_rd(sc, EMU_INTE, 4); val &= ~ch->irqmask; emu_wr(sc, EMU_INTE, val, 4); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: default: break; } snd_mtxunlock(sc->lock); return 0; } static u_int32_t emurchan_getptr(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; struct sc_info *sc = ch->parent; int r; snd_mtxlock(sc->lock); r = emu_rdptr(sc, 0, ch->idxreg) & 0x0000ffff; snd_mtxunlock(sc->lock); return r; } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj, void *data) { struct sc_rchinfo *ch = data; return &emu_reccaps[ch->num]; } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emurchan); static unsigned char emu_mread(struct mpu401 *arg, void *sc, int reg) { unsigned int d; d = emu_rd((struct sc_info *)sc, 0x18 + reg, 1); return d; } static void emu_mwrite(struct mpu401 *arg, void *sc, int reg, unsigned char b) { emu_wr((struct sc_info *)sc, 0x18 + reg, b, 1); } static int emu_muninit(struct mpu401 *arg, void *cookie) { struct sc_info *sc = cookie; snd_mtxlock(sc->lock); sc->mpu_intr = NULL; snd_mtxunlock(sc->lock); return 0; } static kobj_method_t emu_mpu_methods[] = { KOBJMETHOD(mpufoi_read, emu_mread), KOBJMETHOD(mpufoi_write, emu_mwrite), KOBJMETHOD(mpufoi_uninit, emu_muninit), KOBJMETHOD_END }; static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); static void emu_intr2(void *p) { struct sc_info *sc = (struct sc_info *)p; if (sc->mpu_intr) (sc->mpu_intr)(sc->mpu); } static void emu_midiattach(struct sc_info *sc) { int i; i = emu_rd(sc, EMU_INTE, 4); i |= EMU_INTE_MIDIRXENABLE; emu_wr(sc, EMU_INTE, i, 4); sc->mpu = mpu401_init(&emu_mpu_class, sc, emu_intr2, &sc->mpu_intr); } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void emu_intr(void *data) { struct sc_info *sc = data; u_int32_t stat, ack, i, x; snd_mtxlock(sc->lock); while (1) { stat = emu_rd(sc, EMU_IPR, 4); if (stat == 0) break; ack = 0; /* process irq */ if (stat & EMU_IPR_INTERVALTIMER) ack |= EMU_IPR_INTERVALTIMER; if (stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) ack |= stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL); if (stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) ack |= stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL); if (stat & (EMU_IPR_MICBUFFULL | EMU_IPR_MICBUFHALFFULL)) ack |= stat & (EMU_IPR_MICBUFFULL | EMU_IPR_MICBUFHALFFULL); if (stat & EMU_PCIERROR) { ack |= EMU_PCIERROR; device_printf(sc->dev, "pci error\n"); /* we still get an nmi with ecc ram even if we ack this */ } if (stat & EMU_IPR_RATETRCHANGE) { ack |= EMU_IPR_RATETRCHANGE; #ifdef EMUDEBUG device_printf(sc->dev, "sample rate tracker lock status change\n"); #endif } if (stat & EMU_IPR_MIDIRECVBUFE) { if (sc->mpu_intr) { (sc->mpu_intr)(sc->mpu); ack |= EMU_IPR_MIDIRECVBUFE | EMU_IPR_MIDITRANSBUFE; } } if (stat & ~ack) device_printf(sc->dev, "dodgy irq: %x (harmless)\n", stat & ~ack); emu_wr(sc, EMU_IPR, stat, 4); if (ack) { snd_mtxunlock(sc->lock); if (ack & EMU_IPR_INTERVALTIMER) { x = 0; for (i = 0; i < sc->nchans; i++) { if (sc->pch[i].run) { x = 1; chn_intr(sc->pch[i].channel); } } if (x == 0) emu_enatimer(sc, 0); } if (ack & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) { if (sc->rch[0].channel) chn_intr(sc->rch[0].channel); } if (ack & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) { if (sc->rch[1].channel) chn_intr(sc->rch[1].channel); } if (ack & (EMU_IPR_MICBUFFULL | EMU_IPR_MICBUFHALFFULL)) { if (sc->rch[2].channel) chn_intr(sc->rch[2].channel); } snd_mtxlock(sc->lock); } } snd_mtxunlock(sc->lock); } /* -------------------------------------------------------------------- */ static void emu_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *phys = arg; *phys = error ? 0 : (bus_addr_t)segs->ds_addr; if (bootverbose) { printf("emu: setmap (%lx, %lx), nseg=%d, error=%d\n", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, nseg, error); } } static void * emu_malloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr, bus_dmamap_t *map) { void *buf; *addr = 0; if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, map)) return NULL; if (bus_dmamap_load(sc->parent_dmat, *map, buf, sz, emu_setmap, addr, BUS_DMA_NOWAIT) || !*addr) { bus_dmamem_free(sc->parent_dmat, buf, *map); return NULL; } return buf; } static void emu_free(struct sc_info *sc, void *buf, bus_dmamap_t map) { bus_dmamap_unload(sc->parent_dmat, map); bus_dmamem_free(sc->parent_dmat, buf, map); } static void * emu_memalloc(struct sc_info *sc, u_int32_t sz, bus_addr_t *addr) { u_int32_t blksz, start, idx, ofs, tmp, found; struct emu_mem *mem = &sc->mem; struct emu_memblk *blk; void *buf; blksz = sz / EMUPAGESIZE; if (sz > (blksz * EMUPAGESIZE)) blksz++; /* find a free block in the bitmap */ found = 0; start = 1; while (!found && start + blksz < EMUMAXPAGES) { found = 1; for (idx = start; idx < start + blksz; idx++) if (mem->bmap[idx >> 3] & (1 << (idx & 7))) found = 0; if (!found) start++; } if (!found) return NULL; blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT); if (blk == NULL) return NULL; buf = emu_malloc(sc, sz, &blk->buf_addr, &blk->buf_map); *addr = blk->buf_addr; if (buf == NULL) { free(blk, M_DEVBUF); return NULL; } blk->buf = buf; blk->pte_start = start; blk->pte_size = blksz; #ifdef EMUDEBUG printf("buf %p, pte_start %d, pte_size %d\n", blk->buf, blk->pte_start, blk->pte_size); #endif ofs = 0; for (idx = start; idx < start + blksz; idx++) { mem->bmap[idx >> 3] |= 1 << (idx & 7); tmp = (uint32_t)(blk->buf_addr + ofs); #ifdef EMUDEBUG printf("pte[%d] -> %x phys, %x virt\n", idx, tmp, ((u_int32_t)buf) + ofs); #endif mem->ptb_pages[idx] = (tmp << 1) | idx; ofs += EMUPAGESIZE; } SLIST_INSERT_HEAD(&mem->blocks, blk, link); return buf; } static int emu_memfree(struct sc_info *sc, void *buf) { u_int32_t idx, tmp; struct emu_mem *mem = &sc->mem; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == buf) blk = i; } if (blk == NULL) return EINVAL; SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link); emu_free(sc, buf, blk->buf_map); tmp = (u_int32_t)(sc->mem.silent_page_addr) << 1; for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) { mem->bmap[idx >> 3] &= ~(1 << (idx & 7)); mem->ptb_pages[idx] = tmp | idx; } free(blk, M_DEVBUF); return 0; } static int emu_memstart(struct sc_info *sc, void *buf) { struct emu_mem *mem = &sc->mem; struct emu_memblk *blk, *i; blk = NULL; SLIST_FOREACH(i, &mem->blocks, link) { if (i->buf == buf) blk = i; } if (blk == NULL) return -EINVAL; return blk->pte_start; } static void emu_addefxop(struct sc_info *sc, int op, int z, int w, int x, int y, u_int32_t *pc) { emu_wrefx(sc, (*pc) * 2, (x << 10) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << 20) | (z << 10) | w); (*pc)++; } static void audigy_addefxop(struct sc_info *sc, int op, int z, int w, int x, int y, u_int32_t *pc) { emu_wrefx(sc, (*pc) * 2, (x << 12) | y); emu_wrefx(sc, (*pc) * 2 + 1, (op << 24) | (z << 12) | w); (*pc)++; } static void audigy_initefx(struct sc_info *sc) { int i; u_int32_t pc = 0; /* skip 0, 0, -1, 0 - NOPs */ for (i = 0; i < 512; i++) audigy_addefxop(sc, 0x0f, 0x0c0, 0x0c0, 0x0cf, 0x0c0, &pc); for (i = 0; i < 512; i++) emu_wrptr(sc, 0, EMU_A_FXGPREGBASE + i, 0x0); pc = 16; /* stop fx processor */ emu_wrptr(sc, 0, EMU_A_DBG, EMU_A_DBG_SINGLE_STEP); /* Audigy 2 (EMU10K2) DSP Registers: FX Bus 0x000-0x00f : 16 registers (?) Input 0x040/0x041 : AC97 Codec (l/r) 0x042/0x043 : ADC, S/PDIF (l/r) 0x044/0x045 : Optical S/PDIF in (l/r) 0x046/0x047 : ? 0x048/0x049 : Line/Mic 2 (l/r) 0x04a/0x04b : RCA S/PDIF (l/r) 0x04c/0x04d : Aux 2 (l/r) Output 0x060/0x061 : Digital Front (l/r) 0x062/0x063 : Digital Center/LFE 0x064/0x065 : AudigyDrive Heaphone (l/r) 0x066/0x067 : Digital Rear (l/r) 0x068/0x069 : Analog Front (l/r) 0x06a/0x06b : Analog Center/LFE 0x06c/0x06d : ? 0x06e/0x06f : Analog Rear (l/r) 0x070/0x071 : AC97 Output (l/r) 0x072/0x073 : ? 0x074/0x075 : ? 0x076/0x077 : ADC Recording Buffer (l/r) Constants 0x0c0 - 0x0c4 = 0 - 4 0x0c5 = 0x8, 0x0c6 = 0x10, 0x0c7 = 0x20 0x0c8 = 0x100, 0x0c9 = 0x10000, 0x0ca = 0x80000 0x0cb = 0x10000000, 0x0cc = 0x20000000, 0x0cd = 0x40000000 0x0ce = 0x80000000, 0x0cf = 0x7fffffff, 0x0d0 = 0xffffffff 0x0d1 = 0xfffffffe, 0x0d2 = 0xc0000000, 0x0d3 = 0x41fbbcdc 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (?) Temporary Values 0x0d6 : Accumulator (?) 0x0d7 : Condition Register 0x0d8 : Noise source 0x0d9 : Noise source Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff General Purpose Registers 0x400 - 0x5ff */ /* AC97Output[l/r] = FXBus PCM[l/r] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AC97_L), A_C_00000000, A_C_00000000, A_FXBUS(FXBUS_PCM_LEFT), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AC97_R), A_C_00000000, A_C_00000000, A_FXBUS(FXBUS_PCM_RIGHT), &pc); /* GPR[0/1] = RCA S/PDIF[l/r] -- Master volume */ audigy_addefxop(sc, iACC3, A_GPR(0), A_C_00000000, A_C_00000000, A_EXTIN(EXTIN_COAX_SPDIF_L), &pc); audigy_addefxop(sc, iACC3, A_GPR(1), A_C_00000000, A_C_00000000, A_EXTIN(EXTIN_COAX_SPDIF_R), &pc); /* GPR[2] = GPR[0] (Left) / 2 + GPR[1] (Right) / 2 -- Central volume */ audigy_addefxop(sc, iINTERP, A_GPR(2), A_GPR(1), A_C_40000000, A_GPR(0), &pc); /* Headphones[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_HEADPHONE_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_HEADPHONE_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Analog Front[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AFRONT_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AFRONT_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Digital Front[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_FRONT_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Center and Subwoofer configuration */ /* Analog Center = GPR[0] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ACENTER), A_C_00000000, A_GPR(0), A_GPR(2), &pc); /* Analog Sub = GPR[1] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ALFE), A_C_00000000, A_GPR(1), A_GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_CENTER), A_C_00000000, A_GPR(0), A_GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_LFE), A_C_00000000, A_GPR(1), A_GPR(2), &pc); #if 0 /* Analog Rear[l/r] = (GPR[0/1] * RearVolume[l/r]) >> 31 */ /* RearVolume = GPR[0x10/0x11] (Will this ever be implemented?) */ audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_AREAR_L), A_C_00000000, A_GPR(16), A_GPR(0), &pc); audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_AREAR_R), A_C_00000000, A_GPR(17), A_GPR(1), &pc); /* Digital Rear[l/r] = (GPR[0/1] * RearVolume[l/r]) >> 31 */ /* RearVolume = GPR[0x10/0x11] (Will this ever be implemented?) */ audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_REAR_L), A_C_00000000, A_GPR(16), A_GPR(0), &pc); audigy_addefxop(sc, iMAC0, A_EXTOUT(A_EXTOUT_REAR_R), A_C_00000000, A_GPR(17), A_GPR(1), &pc); #else /* XXX This is just a copy to the channel, since we do not have * a patch manager, it is useful for have another output enabled. */ /* Analog Rear[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AREAR_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_AREAR_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); /* Digital Rear[l/r] = GPR[0/1] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_REAR_L), A_C_00000000, A_C_00000000, A_GPR(0), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_REAR_R), A_C_00000000, A_C_00000000, A_GPR(1), &pc); #endif /* ADC Recording buffer[l/r] = AC97Input[l/r] */ audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ADC_CAP_L), A_C_00000000, A_C_00000000, A_EXTIN(A_EXTIN_AC97_L), &pc); audigy_addefxop(sc, iACC3, A_EXTOUT(A_EXTOUT_ADC_CAP_R), A_C_00000000, A_C_00000000, A_EXTIN(A_EXTIN_AC97_R), &pc); /* resume normal operations */ emu_wrptr(sc, 0, EMU_A_DBG, 0); } static void emu_initefx(struct sc_info *sc) { int i; u_int32_t pc = 16; /* acc3 0,0,0,0 - NOPs */ for (i = 0; i < 512; i++) { emu_wrefx(sc, i * 2, 0x10040); emu_wrefx(sc, i * 2 + 1, 0x610040); } for (i = 0; i < 256; i++) emu_wrptr(sc, 0, EMU_FXGPREGBASE + i, 0); /* FX-8010 DSP Registers: FX Bus 0x000-0x00f : 16 registers Input 0x010/0x011 : AC97 Codec (l/r) 0x012/0x013 : ADC, S/PDIF (l/r) 0x014/0x015 : Mic(left), Zoom (l/r) 0x016/0x017 : TOS link in (l/r) 0x018/0x019 : Line/Mic 1 (l/r) 0x01a/0x01b : COAX S/PDIF (l/r) 0x01c/0x01d : Line/Mic 2 (l/r) Output 0x020/0x021 : AC97 Output (l/r) 0x022/0x023 : TOS link out (l/r) 0x024/0x025 : Center/LFE 0x026/0x027 : LiveDrive Headphone (l/r) 0x028/0x029 : Rear Channel (l/r) 0x02a/0x02b : ADC Recording Buffer (l/r) 0x02c : Mic Recording Buffer 0x031/0x032 : Analog Center/LFE Constants 0x040 - 0x044 = 0 - 4 0x045 = 0x8, 0x046 = 0x10, 0x047 = 0x20 0x048 = 0x100, 0x049 = 0x10000, 0x04a = 0x80000 0x04b = 0x10000000, 0x04c = 0x20000000, 0x04d = 0x40000000 0x04e = 0x80000000, 0x04f = 0x7fffffff, 0x050 = 0xffffffff 0x051 = 0xfffffffe, 0x052 = 0xc0000000, 0x053 = 0x41fbbcdc 0x054 = 0x5a7ef9db, 0x055 = 0x00100000 Temporary Values 0x056 : Accumulator 0x057 : Condition Register 0x058 : Noise source 0x059 : Noise source 0x05a : IRQ Register 0x05b : TRAM Delay Base Address Count General Purpose Registers 0x100 - 0x1ff Tank Memory Data Registers 0x200 - 0x2ff Tank Memory Address Registers 0x300 - 0x3ff */ /* Routing - this will be configurable in later version */ /* GPR[0/1] = FX * 4 + SPDIF-in */ emu_addefxop(sc, iMACINT0, GPR(0), EXTIN(EXTIN_SPDIF_CD_L), FXBUS(FXBUS_PCM_LEFT), C_00000004, &pc); emu_addefxop(sc, iMACINT0, GPR(1), EXTIN(EXTIN_SPDIF_CD_R), FXBUS(FXBUS_PCM_RIGHT), C_00000004, &pc); /* GPR[0/1] += APS-input */ emu_addefxop(sc, iACC3, GPR(0), GPR(0), C_00000000, sc->APS ? EXTIN(EXTIN_TOSLINK_L) : C_00000000, &pc); emu_addefxop(sc, iACC3, GPR(1), GPR(1), C_00000000, sc->APS ? EXTIN(EXTIN_TOSLINK_R) : C_00000000, &pc); /* FrontOut (AC97) = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_R), C_00000000, C_00000001, GPR(1), &pc); /* GPR[2] = GPR[0] (Left) / 2 + GPR[1] (Right) / 2 -- Central volume */ emu_addefxop(sc, iINTERP, GPR(2), GPR(1), C_40000000, GPR(0), &pc); #if 0 /* RearOut = (GPR[0/1] * RearVolume) >> 31 */ /* RearVolume = GPR[0x10/0x11] */ emu_addefxop(sc, iMAC0, EXTOUT(EXTOUT_REAR_L), C_00000000, GPR(16), GPR(0), &pc); emu_addefxop(sc, iMAC0, EXTOUT(EXTOUT_REAR_R), C_00000000, GPR(17), GPR(1), &pc); #else /* XXX This is just a copy to the channel, since we do not have * a patch manager, it is useful for have another output enabled. */ /* Rear[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_REAR_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_REAR_R), C_00000000, C_00000000, GPR(1), &pc); #endif /* TOS out[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_TOSLINK_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_TOSLINK_R), C_00000000, C_00000000, GPR(1), &pc); /* Center and Subwoofer configuration */ /* Analog Center = GPR[0] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ACENTER), C_00000000, GPR(0), GPR(2), &pc); /* Analog Sub = GPR[1] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ALFE), C_00000000, GPR(1), GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_CENTER), C_00000000, GPR(0), GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_LFE), C_00000000, GPR(1), GPR(2), &pc); /* Headphones[l/r] = GPR[0/1] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_HEADPHONE_L), C_00000000, C_00000000, GPR(0), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_HEADPHONE_R), C_00000000, C_00000000, GPR(1), &pc); /* ADC Recording buffer[l/r] = AC97Input[l/r] */ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ADC_CAP_L), C_00000000, C_00000000, EXTIN(EXTIN_AC97_L), &pc); emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ADC_CAP_R), C_00000000, C_00000000, EXTIN(EXTIN_AC97_R), &pc); /* resume normal operations */ emu_wrptr(sc, 0, EMU_DBG, 0); } /* Probe and attach the card */ static int emu_init(struct sc_info *sc) { u_int32_t spcs, ch, tmp, i; if (sc->audigy) { /* enable additional AC97 slots */ emu_wrptr(sc, 0, EMU_AC97SLOT, EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE); } /* disable audio and lock cache */ emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4); /* reset recording buffers */ emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_MICBA, 0); emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_FXBA, 0); emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_ADCBA, 0); /* disable channel interrupt */ emu_wr(sc, EMU_INTE, EMU_INTE_INTERTIMERENB | EMU_INTE_SAMPLERATER | EMU_INTE_PCIERRENABLE, 4); emu_wrptr(sc, 0, EMU_CLIEL, 0); emu_wrptr(sc, 0, EMU_CLIEH, 0); emu_wrptr(sc, 0, EMU_SOLEL, 0); emu_wrptr(sc, 0, EMU_SOLEH, 0); /* wonder what these do... */ if (sc->audigy) { emu_wrptr(sc, 0, EMU_SPBYPASS, 0xf00); emu_wrptr(sc, 0, EMU_AC97SLOT, 0x3); } /* init envelope engine */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, ENV_OFF); emu_wrptr(sc, ch, EMU_CHAN_IP, 0); emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0); emu_wrptr(sc, ch, EMU_CHAN_CPF, 0); emu_wrptr(sc, ch, EMU_CHAN_CCR, 0); emu_wrptr(sc, ch, EMU_CHAN_PSST, 0); emu_wrptr(sc, ch, EMU_CHAN_DSL, 0x10); emu_wrptr(sc, ch, EMU_CHAN_CCCA, 0); emu_wrptr(sc, ch, EMU_CHAN_Z1, 0); emu_wrptr(sc, ch, EMU_CHAN_Z2, 0); emu_wrptr(sc, ch, EMU_CHAN_FXRT, 0xd01c0000); emu_wrptr(sc, ch, EMU_CHAN_ATKHLDM, 0); emu_wrptr(sc, ch, EMU_CHAN_DCYSUSM, 0); emu_wrptr(sc, ch, EMU_CHAN_IFATN, 0xffff); emu_wrptr(sc, ch, EMU_CHAN_PEFE, 0); emu_wrptr(sc, ch, EMU_CHAN_FMMOD, 0); emu_wrptr(sc, ch, EMU_CHAN_TREMFRQ, 24); /* 1 Hz */ emu_wrptr(sc, ch, EMU_CHAN_FM2FRQ2, 24); /* 1 Hz */ emu_wrptr(sc, ch, EMU_CHAN_TEMPENV, 0); /*** these are last so OFF prevents writing ***/ emu_wrptr(sc, ch, EMU_CHAN_LFOVAL2, 0); emu_wrptr(sc, ch, EMU_CHAN_LFOVAL1, 0); emu_wrptr(sc, ch, EMU_CHAN_ATKHLDV, 0); emu_wrptr(sc, ch, EMU_CHAN_ENVVOL, 0); emu_wrptr(sc, ch, EMU_CHAN_ENVVAL, 0); if (sc->audigy) { /* audigy cards need this to initialize correctly */ emu_wrptr(sc, ch, 0x4c, 0); emu_wrptr(sc, ch, 0x4d, 0); emu_wrptr(sc, ch, 0x4e, 0); emu_wrptr(sc, ch, 0x4f, 0); /* set default routing */ emu_wrptr(sc, ch, EMU_A_CHAN_FXRT1, 0x03020100); emu_wrptr(sc, ch, EMU_A_CHAN_FXRT2, 0x3f3f3f3f); emu_wrptr(sc, ch, EMU_A_CHAN_SENDAMOUNTS, 0); } sc->voice[ch].vnum = ch; sc->voice[ch].slave = NULL; sc->voice[ch].busy = 0; sc->voice[ch].ismaster = 0; sc->voice[ch].running = 0; sc->voice[ch].b16 = 0; sc->voice[ch].stereo = 0; sc->voice[ch].speed = 0; sc->voice[ch].start = 0; sc->voice[ch].end = 0; sc->voice[ch].channel = NULL; } sc->pnum = sc->rnum = 0; /* * Init to 0x02109204 : * Clock accuracy = 0 (1000ppm) * Sample Rate = 2 (48kHz) * Audio Channel = 1 (Left of 2) * Source Number = 0 (Unspecified) * Generation Status = 1 (Original for Cat Code 12) * Cat Code = 12 (Digital Signal Mixer) * Mode = 0 (Mode 0) * Emphasis = 0 (None) * CP = 1 (Copyright unasserted) * AN = 0 (Audio data) * P = 0 (Consumer) */ spcs = EMU_SPCS_CLKACCY_1000PPM | EMU_SPCS_SAMPLERATE_48 | EMU_SPCS_CHANNELNUM_LEFT | EMU_SPCS_SOURCENUM_UNSPEC | EMU_SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 | EMU_SPCS_EMPHASIS_NONE | EMU_SPCS_COPYRIGHT; emu_wrptr(sc, 0, EMU_SPCS0, spcs); emu_wrptr(sc, 0, EMU_SPCS1, spcs); emu_wrptr(sc, 0, EMU_SPCS2, spcs); if (!sc->audigy) emu_initefx(sc); else if (sc->audigy2) { /* Audigy 2 */ /* from ALSA initialization code: */ /* Hack for Alice3 to work independent of haP16V driver */ u_int32_t tmp; /* Setup SRCMulti_I2S SamplingRate */ tmp = emu_rdptr(sc, 0, EMU_A_SPDIF_SAMPLERATE) & 0xfffff1ff; emu_wrptr(sc, 0, EMU_A_SPDIF_SAMPLERATE, tmp | 0x400); /* Setup SRCSel (Enable SPDIF, I2S SRCMulti) */ emu_wr(sc, 0x20, 0x00600000, 4); emu_wr(sc, 0x24, 0x00000014, 4); /* Setup SRCMulti Input Audio Enable */ emu_wr(sc, 0x20, 0x006e0000, 4); emu_wr(sc, 0x24, 0xff00ff00, 4); } SLIST_INIT(&sc->mem.blocks); sc->mem.ptb_pages = emu_malloc(sc, EMUMAXPAGES * sizeof(u_int32_t), &sc->mem.ptb_pages_addr, &sc->mem.ptb_map); if (sc->mem.ptb_pages == NULL) return -1; sc->mem.silent_page = emu_malloc(sc, EMUPAGESIZE, &sc->mem.silent_page_addr, &sc->mem.silent_map); if (sc->mem.silent_page == NULL) { emu_free(sc, sc->mem.ptb_pages, sc->mem.ptb_map); return -1; } /* Clear page with silence & setup all pointers to this page */ bzero(sc->mem.silent_page, EMUPAGESIZE); tmp = (u_int32_t)(sc->mem.silent_page_addr) << 1; for (i = 0; i < EMUMAXPAGES; i++) sc->mem.ptb_pages[i] = tmp | i; emu_wrptr(sc, 0, EMU_PTB, (sc->mem.ptb_pages_addr)); emu_wrptr(sc, 0, EMU_TCB, 0); /* taken from original driver */ emu_wrptr(sc, 0, EMU_TCBS, 0); /* taken from original driver */ for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_MAPA, tmp | EMU_CHAN_MAP_PTI_MASK); emu_wrptr(sc, ch, EMU_CHAN_MAPB, tmp | EMU_CHAN_MAP_PTI_MASK); } /* emu_memalloc(sc, EMUPAGESIZE); */ /* * Hokay, now enable the AUD bit * * Audigy * Enable Audio = 0 (enabled after fx processor initialization) * Mute Disable Audio = 0 * Joystick = 1 * * Audigy 2 * Enable Audio = 1 * Mute Disable Audio = 0 * Joystick = 1 * GP S/PDIF AC3 Enable = 1 * CD S/PDIF AC3 Enable = 1 * * EMU10K1 * Enable Audio = 1 * Mute Disable Audio = 0 * Lock Tank Memory = 1 * Lock Sound Memory = 0 * Auto Mute = 1 */ if (sc->audigy) { tmp = EMU_HCFG_AUTOMUTE | EMU_HCFG_JOYENABLE; if (sc->audigy2) /* Audigy 2 */ tmp = EMU_HCFG_AUDIOENABLE | EMU_HCFG_AC3ENABLE_CDSPDIF | EMU_HCFG_AC3ENABLE_GPSPDIF; emu_wr(sc, EMU_HCFG, tmp, 4); audigy_initefx(sc); /* from ALSA initialization code: */ /* enable audio and disable both audio/digital outputs */ emu_wr(sc, EMU_HCFG, emu_rd(sc, EMU_HCFG, 4) | EMU_HCFG_AUDIOENABLE, 4); emu_wr(sc, EMU_A_IOCFG, emu_rd(sc, EMU_A_IOCFG, 4) & ~EMU_A_IOCFG_GPOUT_AD, 4); if (sc->audigy2) { /* Audigy 2 */ /* Unmute Analog. * Set GPO6 to 1 for Apollo. This has to be done after * init Alice3 I2SOut beyond 48kHz. * So, sequence is important. */ emu_wr(sc, EMU_A_IOCFG, emu_rd(sc, EMU_A_IOCFG, 4) | EMU_A_IOCFG_GPOUT_A, 4); } } else { /* EMU10K1 initialization code */ tmp = EMU_HCFG_AUDIOENABLE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_AUTOMUTE; if (sc->rev >= 6) tmp |= EMU_HCFG_JOYENABLE; emu_wr(sc, EMU_HCFG, tmp, 4); /* TOSLink detection */ sc->tos_link = 0; tmp = emu_rd(sc, EMU_HCFG, 4); if (tmp & (EMU_HCFG_GPINPUT0 | EMU_HCFG_GPINPUT1)) { emu_wr(sc, EMU_HCFG, tmp | EMU_HCFG_GPOUT1, 4); DELAY(50); if (tmp != (emu_rd(sc, EMU_HCFG, 4) & ~EMU_HCFG_GPOUT1)) { sc->tos_link = 1; emu_wr(sc, EMU_HCFG, tmp, 4); } } } return 0; } static int emu_uninit(struct sc_info *sc) { u_int32_t ch; emu_wr(sc, EMU_INTE, 0, 4); for (ch = 0; ch < NUM_G; ch++) emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, ENV_OFF); for (ch = 0; ch < NUM_G; ch++) { emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0); emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0); emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0); emu_wrptr(sc, ch, EMU_CHAN_CPF, 0); } if (sc->audigy) { /* stop fx processor */ emu_wrptr(sc, 0, EMU_A_DBG, EMU_A_DBG_SINGLE_STEP); } /* disable audio and lock cache */ emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4); emu_wrptr(sc, 0, EMU_PTB, 0); /* reset recording buffers */ emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_MICBA, 0); emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_FXBA, 0); emu_wrptr(sc, 0, EMU_FXWC, 0); emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE); emu_wrptr(sc, 0, EMU_ADCBA, 0); emu_wrptr(sc, 0, EMU_TCB, 0); emu_wrptr(sc, 0, EMU_TCBS, 0); /* disable channel interrupt */ emu_wrptr(sc, 0, EMU_CLIEL, 0); emu_wrptr(sc, 0, EMU_CLIEH, 0); emu_wrptr(sc, 0, EMU_SOLEL, 0); emu_wrptr(sc, 0, EMU_SOLEH, 0); /* init envelope engine */ if (!SLIST_EMPTY(&sc->mem.blocks)) device_printf(sc->dev, "warning: memblock list not empty\n"); emu_free(sc, sc->mem.ptb_pages, sc->mem.ptb_map); emu_free(sc, sc->mem.silent_page, sc->mem.silent_map); if(sc->mpu) mpu401_uninit(sc->mpu); return 0; } static int emu_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case EMU10K1_PCI_ID: s = "Creative EMU10K1"; break; case EMU10K2_PCI_ID: if (pci_get_revid(dev) == 0x04) s = "Creative Audigy 2 (EMU10K2)"; else s = "Creative Audigy (EMU10K2)"; break; case EMU10K3_PCI_ID: s = "Creative Audigy 2 (EMU10K3)"; break; default: return ENXIO; } device_set_desc(dev, s); return BUS_PROBE_LOW_PRIORITY; } static int emu_pci_attach(device_t dev) { struct ac97_info *codec = NULL; struct sc_info *sc; int i, gotmic; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10k1 softc"); sc->dev = dev; sc->type = pci_get_devid(dev); sc->rev = pci_get_revid(dev); sc->audigy = sc->type == EMU10K2_PCI_ID || sc->type == EMU10K3_PCI_ID; sc->audigy2 = (sc->audigy && sc->rev == 0x04); sc->nchans = sc->audigy ? 8 : 4; sc->addrmask = sc->audigy ? EMU_A_PTR_ADDR_MASK : EMU_PTR_ADDR_MASK; pci_enable_busmaster(dev); i = PCIR_BAR(0); sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE); if (sc->reg == NULL) { device_printf(dev, "unable to map register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, 4096, EMU_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/(1U << 31) - 1, /* can only access 0-2gb */ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (emu_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, emu_ac97); if (codec == NULL) goto bad; gotmic = (ac97_getcaps(codec) & AC97_CAP_MICCHANNEL) ? 1 : 0; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; emu_midiattach(sc); i = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, emu_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); pcm_init(dev, sc); for (i = 0; i < sc->nchans; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); for (i = 0; i < (gotmic ? 3 : 2); i++) pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); if (pcm_register(dev, status)) goto bad; return 0; bad: if (codec) ac97_destroy(codec); if (sc->reg) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return ENXIO; } static int emu_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); /* shutdown chip */ emu_uninit(sc); bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); bus_dma_tag_destroy(sc->parent_dmat); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return 0; } /* add suspend, resume */ static device_method_t emu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emu_pci_probe), DEVMETHOD(device_attach, emu_pci_attach), DEVMETHOD(device_detach, emu_pci_detach), DEVMETHOD_END }; static driver_t emu_driver = { "pcm", emu_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_emu10k1, pci, emu_driver, NULL, NULL); MODULE_DEPEND(snd_emu10k1, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10k1, 1); MODULE_DEPEND(snd_emu10k1, midi, 1, 1, 1); /* dummy driver to silence the joystick device */ static int emujoy_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case 0x70021102: s = "Creative EMU10K1 Joystick"; device_quiet(dev); break; case 0x70031102: s = "Creative EMU10K2 Joystick"; device_quiet(dev); break; } if (s) device_set_desc(dev, s); return s ? -1000 : ENXIO; } static int emujoy_pci_attach(device_t dev) { return 0; } static int emujoy_pci_detach(device_t dev) { return 0; } static device_method_t emujoy_methods[] = { DEVMETHOD(device_probe, emujoy_pci_probe), DEVMETHOD(device_attach, emujoy_pci_attach), DEVMETHOD(device_detach, emujoy_pci_detach), DEVMETHOD_END }; static driver_t emujoy_driver = { "emujoy", emujoy_methods, 1 /* no softc */ }; DRIVER_MODULE(emujoy, pci, emujoy_driver, NULL, NULL); diff --git a/sys/dev/sound/pci/emu10kx-pcm.c b/sys/dev/sound/pci/emu10kx-pcm.c index c280b64892f6..a93d48ba71b1 100644 --- a/sys/dev/sound/pci/emu10kx-pcm.c +++ b/sys/dev/sound/pci/emu10kx-pcm.c @@ -1,1530 +1,1532 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * Copyright (c) 2003-2007 Yuriy Tsibizov * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN 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. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "mixer_if.h" #include #include struct emu_pcm_pchinfo { int spd; int fmt; unsigned int blksz; int run; struct emu_voice *master; struct emu_voice *slave; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; struct emu_pcm_rchinfo { int spd; int fmt; unsigned int blksz; int run; uint32_t idxreg; uint32_t basereg; uint32_t sizereg; uint32_t setupreg; uint32_t irqmask; uint32_t iprmask; int ihandle; struct snd_dbuf *buffer; struct pcm_channel *channel; struct emu_pcm_info *pcm; int timer; }; /* XXX Hardware playback channels */ #define MAX_CHANNELS 4 #if MAX_CHANNELS > 13 #error Too many hardware channels defined. 13 is the maximum #endif struct emu_pcm_info { struct mtx *lock; device_t dev; /* device information */ struct emu_sc_info *card; struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */ int pnum; /* next free channel number */ struct emu_pcm_rchinfo rch_adc; struct emu_pcm_rchinfo rch_efx; struct emu_route rt; struct emu_route rt_mono; int route; int ihandle; /* interrupt handler */ unsigned int bufsz; int is_emu10k1; struct ac97_info *codec; uint32_t ac97_state[0x7F]; kobj_class_t ac97_mixerclass; uint32_t ac97_recdevs; uint32_t ac97_playdevs; struct snd_mixer *sm; int mch_disabled; unsigned int emu10k1_volcache[2][2]; }; static uint32_t emu_rfmt_adc[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps emu_reccaps_adc = { 8000, 48000, emu_rfmt_adc, 0 }; static uint32_t emu_rfmt_efx[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), 0 }; static struct pcmchan_caps emu_reccaps_efx_live = { 48000*32, 48000*32, emu_rfmt_efx, 0 }; static struct pcmchan_caps emu_reccaps_efx_audigy = { 48000*64, 48000*64, emu_rfmt_efx, 0 }; static int emu_rates_live[] = { 48000*32 }; static int emu_rates_audigy[] = { 48000*64 }; static uint32_t emu_pfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static uint32_t emu_pfmt_mono[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), 0 }; static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0}; static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; /* audigy supports 12kHz. */ static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000}; static uint32_t emu_pcm_intr(void *pcm, uint32_t stat); static const struct emu_dspmix_props_k1 { uint8_t present; uint8_t recdev; int8_t input; } dspmix_k1 [SOUND_MIXER_NRDEVICES] = { /* no mixer device for ac97 */ /* in0 AC97 */ [SOUND_MIXER_DIGITAL1] = {1, 1, 1}, /* in1 CD SPDIF */ /* not connected */ /* in2 (zoom) */ [SOUND_MIXER_DIGITAL2] = {1, 1, 3}, /* in3 toslink */ [SOUND_MIXER_LINE2] = {1, 1, 4}, /* in4 Line-In2 */ [SOUND_MIXER_DIGITAL3] = {1, 1, 5}, /* in5 on-card SPDIF */ [SOUND_MIXER_LINE3] = {1, 1, 6}, /* in6 AUX2 */ /* not connected */ /* in7 */ }; static const struct emu_dspmix_props_k2 { uint8_t present; uint8_t recdev; int8_t input; } dspmix_k2 [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = {1, 0, (-1)}, [SOUND_MIXER_PCM] = {1, 0, (-1)}, /* no mixer device */ /* in0 AC97 */ [SOUND_MIXER_DIGITAL1] = {1, 1, 1}, /* in1 CD SPDIF */ [SOUND_MIXER_DIGITAL2] = {1, 1, 2}, /* in2 COAX SPDIF */ /* not connected */ /* in3 */ [SOUND_MIXER_LINE2] = {1, 1, 4}, /* in4 Line-In2 */ [SOUND_MIXER_DIGITAL3] = {1, 1, 5}, /* in5 on-card SPDIF */ [SOUND_MIXER_LINE3] = {1, 1, 6}, /* in6 AUX2 */ /* not connected */ /* in7 */ }; static int emu_dspmixer_init(struct snd_mixer *m) { struct emu_pcm_info *sc; int i; int p, r; p = 0; r = 0; sc = mix_getdevinfo(m); if (sc->route == RT_FRONT) { /* create submixer for AC97 codec */ if ((sc->ac97_mixerclass != NULL) && (sc->codec != NULL)) { sc->sm = mixer_create(sc->dev, sc->ac97_mixerclass, sc->codec, "ac97"); if (sc->sm != NULL) { p = mix_getdevs(sc->sm); r = mix_getrecdevs(sc->sm); } } sc->ac97_playdevs = p; sc->ac97_recdevs = r; } /* This two are always here */ p |= (1 << SOUND_MIXER_PCM); p |= (1 << SOUND_MIXER_VOLUME); if (sc->route == RT_FRONT) { if (sc->is_emu10k1) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k1[i].present) p |= (1 << i); if (dspmix_k1[i].recdev) r |= (1 << i); } } else { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k2[i].present) p |= (1 << i); if (dspmix_k2[i].recdev) r |= (1 << i); } } } mix_setdevs(m, p); mix_setrecdevs(m, r); return (0); } static int emu_dspmixer_uninit(struct snd_mixer *m) { struct emu_pcm_info *sc; int err = 0; /* drop submixer for AC97 codec */ sc = mix_getdevinfo(m); if (sc->sm != NULL) { err = mixer_delete(sc->sm); if (err) return (err); sc->sm = NULL; } return (0); } static int emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct emu_pcm_info *sc; sc = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_VOLUME: switch (sc->route) { case RT_FRONT: if (sc->sm != NULL) mix_set(sc->sm, dev, left, right); if (sc->mch_disabled) { /* In emu10k1 case PCM volume does not affect sound routed to rear & center/sub (it is connected to AC97 codec). Calculate it manually. */ /* This really should belong to emu10kx.c */ if (sc->is_emu10k1) { sc->emu10k1_volcache[0][0] = left; left = left * sc->emu10k1_volcache[1][0] / 100; sc->emu10k1_volcache[0][1] = right; right = right * sc->emu10k1_volcache[1][1] / 100; } emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); /* XXX side */ } } /* mch disabled */ break; case RT_REAR: emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_PCM: switch (sc->route) { case RT_FRONT: if (sc->sm != NULL) mix_set(sc->sm, dev, left, right); if (sc->mch_disabled) { /* See SOUND_MIXER_VOLUME case */ if (sc->is_emu10k1) { sc->emu10k1_volcache[1][0] = left; left = left * sc->emu10k1_volcache[0][0] / 100; sc->emu10k1_volcache[1][1] = right; right = right * sc->emu10k1_volcache[0][1] / 100; } emumix_set_volume(sc->card, M_MASTER_REAR_L, left); emumix_set_volume(sc->card, M_MASTER_REAR_R, right); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); /* XXX side */ } } /* mch_disabled */ break; case RT_REAR: emumix_set_volume(sc->card, M_FX2_REAR_L, left); emumix_set_volume(sc->card, M_FX3_REAR_R, right); break; case RT_CENTER: emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2); break; case RT_SUB: emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2); break; } break; case SOUND_MIXER_DIGITAL1: /* CD SPDIF, in1 */ emumix_set_volume(sc->card, M_IN1_FRONT_L, left); emumix_set_volume(sc->card, M_IN1_FRONT_R, right); break; case SOUND_MIXER_DIGITAL2: if (sc->is_emu10k1) { /* TOSLink, in3 */ emumix_set_volume(sc->card, M_IN3_FRONT_L, left); emumix_set_volume(sc->card, M_IN3_FRONT_R, right); } else { /* COAX SPDIF, in2 */ emumix_set_volume(sc->card, M_IN2_FRONT_L, left); emumix_set_volume(sc->card, M_IN2_FRONT_R, right); } break; case SOUND_MIXER_LINE2: /* Line-In2, in4 */ emumix_set_volume(sc->card, M_IN4_FRONT_L, left); emumix_set_volume(sc->card, M_IN4_FRONT_R, right); break; case SOUND_MIXER_DIGITAL3: /* on-card SPDIF, in5 */ emumix_set_volume(sc->card, M_IN5_FRONT_L, left); emumix_set_volume(sc->card, M_IN5_FRONT_R, right); break; case SOUND_MIXER_LINE3: /* AUX2, in6 */ emumix_set_volume(sc->card, M_IN6_FRONT_L, left); emumix_set_volume(sc->card, M_IN6_FRONT_R, right); break; default: if (sc->sm != NULL) { /* XXX emumix_set_volume is not required here */ emumix_set_volume(sc->card, M_IN0_FRONT_L, 100); emumix_set_volume(sc->card, M_IN0_FRONT_R, 100); mix_set(sc->sm, dev, left, right); } else device_printf(sc->dev, "mixer error: unknown device %d\n", dev); } return (0); } static u_int32_t emu_dspmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct emu_pcm_info *sc; int i; u_int32_t recmask; int input[8]; sc = mix_getdevinfo(m); recmask = 0; for (i=0; i < 8; i++) input[i]=0; if (sc->sm != NULL) if ((src & sc->ac97_recdevs) !=0) if (mix_setrecsrc(sc->sm, src & sc->ac97_recdevs) == 0) { recmask |= (src & sc->ac97_recdevs); /* Recording from AC97 codec. Enable AC97 route to rec on DSP */ input[0] = 1; } if (sc->is_emu10k1) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k1[i].recdev) if ((src & (1 << i)) == ((uint32_t)1 << i)) { recmask |= (1 << i); /* enable device i */ input[dspmix_k1[i].input] = 1; } } } else { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (dspmix_k2[i].recdev) if ((src & (1 << i)) == ((uint32_t)1 << i)) { recmask |= (1 << i); /* enable device i */ input[dspmix_k2[i].input] = 1; } } } emumix_set_volume(sc->card, M_IN0_REC_L, input[0] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN0_REC_R, input[0] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN1_REC_L, input[1] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN1_REC_R, input[1] == 1 ? 100 : 0); if (!sc->is_emu10k1) { emumix_set_volume(sc->card, M_IN2_REC_L, input[2] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN2_REC_R, input[2] == 1 ? 100 : 0); } if (sc->is_emu10k1) { emumix_set_volume(sc->card, M_IN3_REC_L, input[3] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN3_REC_R, input[3] == 1 ? 100 : 0); } emumix_set_volume(sc->card, M_IN4_REC_L, input[4] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN4_REC_R, input[4] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN5_REC_L, input[5] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN5_REC_R, input[5] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN6_REC_L, input[6] == 1 ? 100 : 0); emumix_set_volume(sc->card, M_IN6_REC_R, input[6] == 1 ? 100 : 0); /* XXX check for K1/k2 differences? */ if ((src & (1 << SOUND_MIXER_PCM)) == (1 << SOUND_MIXER_PCM)) { emumix_set_volume(sc->card, M_FX0_REC_L, emumix_get_volume(sc->card, M_FX0_FRONT_L)); emumix_set_volume(sc->card, M_FX1_REC_R, emumix_get_volume(sc->card, M_FX1_FRONT_R)); } else { emumix_set_volume(sc->card, M_FX0_REC_L, 0); emumix_set_volume(sc->card, M_FX1_REC_R, 0); } return (recmask); } static kobj_method_t emudspmixer_methods[] = { KOBJMETHOD(mixer_init, emu_dspmixer_init), KOBJMETHOD(mixer_uninit, emu_dspmixer_uninit), KOBJMETHOD(mixer_set, emu_dspmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(emudspmixer); static int emu_efxmixer_init(struct snd_mixer *m) { mix_setdevs(m, SOUND_MASK_VOLUME); mix_setrecdevs(m, SOUND_MASK_MONITOR); return (0); } static int emu_efxmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { if (left + right == 200) return (0); return (0); } static u_int32_t emu_efxmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused) { return (SOUND_MASK_MONITOR); } static kobj_method_t emuefxmixer_methods[] = { KOBJMETHOD(mixer_init, emu_efxmixer_init), KOBJMETHOD(mixer_set, emu_efxmixer_set), KOBJMETHOD(mixer_setrecsrc, emu_efxmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(emuefxmixer); /* * AC97 emulation code for Audigy and later cards. * Some parts of AC97 codec are not used by hardware, but can be used * to change some DSP controls via AC97 mixer interface. This includes: * - master volume controls MASTER_FRONT_[R|L] * - pcm volume controls FX[0|1]_FRONT_[R|L] * - rec volume controls MASTER_REC_[R|L] * We do it because we need to put it under user control.... * We also keep some parts of AC97 disabled to get better sound quality */ #define AC97LEFT(x) ((x & 0x7F00)>>8) #define AC97RIGHT(x) (x & 0x007F) #define AC97MUTE(x) ((x & 0x8000)>>15) #define BIT4_TO100(x) (100-(x)*100/(0x0f)) #define BIT6_TO100(x) (100-(x)*100/(0x3f)) #define BIT4_TO255(x) (255-(x)*255/(0x0f)) #define BIT6_TO255(x) (255-(x)*255/(0x3f)) #define V100_TOBIT6(x) (0x3f*(100-x)/100) #define V100_TOBIT4(x) (0x0f*(100-x)/100) #define AC97ENCODE(x_muted, x_left, x_right) (((x_muted & 1)<<15) | ((x_left & 0x3f)<<8) | (x_right & 0x3f)) static int emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno) { int use_ac97; int emulated; int tmp; use_ac97 = 1; emulated = 0; switch (regno) { case AC97_MIX_MASTER: emulated = sc->ac97_state[AC97_MIX_MASTER]; use_ac97 = 0; break; case AC97_MIX_PCM: emulated = sc->ac97_state[AC97_MIX_PCM]; use_ac97 = 0; break; case AC97_REG_RECSEL: emulated = 0x0505; use_ac97 = 0; break; case AC97_MIX_RGAIN: emulated = sc->ac97_state[AC97_MIX_RGAIN]; use_ac97 = 0; break; } emu_wr(sc->card, EMU_AC97ADDR, regno, 1); tmp = emu_rd(sc->card, EMU_AC97DATA, 2); if (use_ac97) emulated = tmp; return (emulated); } static void emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data) { int write_ac97; int left, right; uint32_t emu_left, emu_right; int is_mute; write_ac97 = 1; left = AC97LEFT(data); emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */ right = AC97RIGHT(data); emu_right = BIT6_TO100(right); is_mute = AC97MUTE(data); if (is_mute) emu_left = emu_right = 0; switch (regno) { /* TODO: reset emulator on AC97_RESET */ case AC97_MIX_MASTER: emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left); emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 main out */ break; case AC97_MIX_PCM: /* PCM OUT VOL */ emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left); emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right); sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f); data = 0x8000; /* Mute AC97 PCM out */ break; case AC97_REG_RECSEL: /* * PCM recording source is set to "stereo mix" (labeled "vol" * in mixer). There is no 'playback' from AC97 codec - * if you want to hear anything from AC97 you have to _record_ * it. Keep things simple and record "stereo mix". */ data = 0x0505; break; case AC97_MIX_RGAIN: /* RECORD GAIN */ emu_left = BIT4_TO100(left); /* rgain is 4-bit */ emu_right = BIT4_TO100(right); emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left); emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right); /* * Record gain on AC97 should stay zero to get AC97 sound on * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not * directly connected to any output, only to EMU10K2 chip Use * this control to set AC97 mix volume inside EMU10K2 chip */ sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f); data = 0x0000; break; } if (write_ac97) { emu_wr(sc->card, EMU_AC97ADDR, regno, 1); emu_wr(sc->card, EMU_AC97DATA, data, 2); } } static int emu_erdcd(kobj_t obj __unused, void *devinfo, int regno) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; return (emu_ac97_read_emulation(sc, regno)); } static int emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; emu_ac97_write_emulation(sc, regno, data); return (0); } static kobj_method_t emu_eac97_methods[] = { KOBJMETHOD(ac97_read, emu_erdcd), KOBJMETHOD(ac97_write, emu_ewrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_eac97); /* real ac97 codec */ static int emu_rdcd(kobj_t obj __unused, void *devinfo, int regno) { int rd; struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard")); emu_wr(sc->card, EMU_AC97ADDR, regno, 1); rd = emu_rd(sc->card, EMU_AC97DATA, 2); return (rd); } static int emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) { struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard")); emu_wr(sc->card, EMU_AC97ADDR, regno, 1); emu_wr(sc->card, EMU_AC97DATA, data, 2); return (0); } static kobj_method_t emu_ac97_methods[] = { KOBJMETHOD(ac97_read, emu_rdcd), KOBJMETHOD(ac97_write, emu_wrcd), KOBJMETHOD_END }; AC97_DECLARE(emu_ac97); static int emu_k1_recval(int speed) { int val; val = 0; while ((val < 7) && (speed < emu10k1_adcspeed[val])) val++; return (val); } static int emu_k2_recval(int speed) { int val; val = 0; while ((val < 8) && (speed < emu10k2_adcspeed[val])) val++; return (val); } static void * emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_pchinfo *ch; void *r; KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); KASSERT(sc->card != NULL, ("empchan_init: no soundcard")); if (sc->pnum >= MAX_CHANNELS) return (NULL); ch = &(sc->pch[sc->pnum++]); ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->master = emu_valloc(sc->card); /* * XXX we have to allocate slave even for mono channel until we * fix emu_vfree to handle this case. */ ch->slave = emu_valloc(sc->card); ch->timer = emu_timer_create(sc->card); r = (emu_vinit(sc->card, ch->master, ch->slave, EMU_PLAY_BUFSZ, ch->buffer)) ? NULL : ch; return (r); } static int emupchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); if (ch->slave != NULL) emu_vfree(sc->card, ch->slave); emu_vfree(sc->card, ch->master); return (0); } static int emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static uint32_t emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_pchinfo *ch = c_devinfo; ch->spd = speed; return (ch->spd); } static uint32_t emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (blocksize > ch->pcm->bufsz) blocksize = ch->pcm->bufsz; snd_mtxlock(sc->lock); ch->blksz = blocksize; - emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); + emu_timer_set(sc->card, ch->timer, ch->blksz / ch->buffer->align); snd_mtxunlock(sc->lock); return (ch->blksz); } static int emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */ if (go == PCMTRIG_START) { emu_vsetup(ch->master, ch->fmt, ch->spd); if (AFMT_CHANNEL(ch->fmt) > 1) emu_vroute(sc->card, &(sc->rt), ch->master); else emu_vroute(sc->card, &(sc->rt_mono), ch->master); emu_vwrite(sc->card, ch->master); - emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); + emu_timer_set(sc->card, ch->timer, ch->blksz / + ch->buffer->align); emu_timer_enable(sc->card, ch->timer, 1); } /* PCM interrupt handler will handle PCMTRIG_STOP event */ ch->run = (go == PCMTRIG_START) ? 1 : 0; emu_vtrigger(sc->card, ch->master, ch->run); snd_mtxunlock(sc->lock); return (0); } static uint32_t emupchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_vpos(sc->card, ch->master); return (r); } static struct pcmchan_caps * emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { struct emu_pcm_pchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; switch (sc->route) { case RT_FRONT: /* FALLTHROUGH */ case RT_REAR: /* FALLTHROUGH */ case RT_SIDE: return (&emu_playcaps); break; case RT_CENTER: /* FALLTHROUGH */ case RT_SUB: return (&emu_playcaps_mono); break; } return (NULL); } static kobj_method_t emupchan_methods[] = { KOBJMETHOD(channel_init, emupchan_init), KOBJMETHOD(channel_free, emupchan_free), KOBJMETHOD(channel_setformat, emupchan_setformat), KOBJMETHOD(channel_setspeed, emupchan_setspeed), KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), KOBJMETHOD(channel_trigger, emupchan_trigger), KOBJMETHOD(channel_getptr, emupchan_getptr), KOBJMETHOD(channel_getcaps, emupchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emupchan); static void * emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); ch = &sc->rch_adc; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; /* We rise interrupt for half-full buffer */ ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = 8000; ch->idxreg = sc->is_emu10k1 ? EMU_ADCIDX : EMU_A_ADCIDX; ch->basereg = EMU_ADCBA; ch->sizereg = EMU_ADCBS; ch->setupreg = EMU_ADCCR; ch->irqmask = EMU_INTE_ADCBUFENABLE; ch->iprmask = EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { ch->timer = emu_timer_create(sc->card); - emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc->card, 0, ch->basereg, ch->buffer->buf_addr); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emurchan_free(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; emu_timer_clear(sc->card, ch->timer); return (0); } static int emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->fmt = format; return (0); } static uint32_t emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; if (ch->pcm->is_emu10k1) { speed = emu10k1_adcspeed[emu_k1_recval(speed)]; } else { speed = emu10k2_adcspeed[emu_k2_recval(speed)]; } ch->spd = speed; return (ch->spd); } static uint32_t emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; ch->blksz = blocksize; /* * If blocksize is less than half of buffer size we will not get * BUFHALFFULL interrupt in time and channel will need to generate * (and use) timer interrupts. Otherwise channel will be marked dead. */ if (ch->blksz < (ch->pcm->bufsz / 2)) { - emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getalign(ch->buffer)); + emu_timer_set(sc->card, ch->timer, ch->blksz / + ch->buffer->align); emu_timer_enable(sc->card, ch->timer, 1); } else { emu_timer_enable(sc->card, ch->timer, 0); } return (ch->blksz); } static int emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t val, sz; if (!PCMTRIG_COMMON(go)) return (0); switch (sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); val = sc->is_emu10k1 ? EMU_ADCCR_LCHANENABLE : EMU_A_ADCCR_LCHANENABLE; if (AFMT_CHANNEL(ch->fmt) > 1) val |= sc->is_emu10k1 ? EMU_ADCCR_RCHANENABLE : EMU_A_ADCCR_RCHANENABLE; val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd); emu_wrptr(sc->card, 0, ch->setupreg, 0); emu_wrptr(sc->card, 0, ch->setupreg, val); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; emu_wrptr(sc->card, 0, ch->sizereg, 0); if (ch->setupreg) emu_wrptr(sc->card, 0, ch->setupreg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t emurchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) { return (&emu_reccaps_adc); } static kobj_method_t emurchan_methods[] = { KOBJMETHOD(channel_init, emurchan_init), KOBJMETHOD(channel_free, emurchan_free), KOBJMETHOD(channel_setformat, emurchan_setformat), KOBJMETHOD(channel_setspeed, emurchan_setspeed), KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), KOBJMETHOD(channel_trigger, emurchan_trigger), KOBJMETHOD(channel_getptr, emurchan_getptr), KOBJMETHOD(channel_getcaps, emurchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(emurchan); static void * emufxrchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) { struct emu_pcm_info *sc = devinfo; struct emu_pcm_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); if (sc == NULL) return (NULL); ch = &(sc->rch_efx); ch->fmt = SND_FORMAT(AFMT_S16_LE, 1, 0); ch->spd = sc->is_emu10k1 ? 48000*32 : 48000 * 64; ch->idxreg = EMU_FXIDX; ch->basereg = EMU_FXBA; ch->sizereg = EMU_FXBS; ch->irqmask = EMU_INTE_EFXBUFENABLE; ch->iprmask = EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL; ch->buffer = b; ch->pcm = sc; ch->channel = c; ch->blksz = sc->bufsz / 2; if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) return (NULL); else { - emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc->card, 0, ch->basereg, ch->buffer->buf_addr); emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ return (ch); } } static int emufxrchan_setformat(kobj_t obj __unused, void *c_devinfo __unused, uint32_t format) { if (format == SND_FORMAT(AFMT_S16_LE, 1, 0)) return (0); return (EINVAL); } static uint32_t emufxrchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) { struct emu_pcm_rchinfo *ch = c_devinfo; /* FIXED RATE CHANNEL */ return (ch->spd); } static uint32_t emufxrchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) { struct emu_pcm_rchinfo *ch = c_devinfo; ch->blksz = blocksize; /* * XXX If blocksize is less than half of buffer size we will not get * interrupt in time and channel will die due to interrupt timeout. * This should not happen with FX rchan, because it will fill buffer * very fast (64K buffer is 0.021seconds on Audigy). */ if (ch->blksz < (ch->pcm->bufsz / 2)) ch->blksz = ch->pcm->bufsz / 2; return (ch->blksz); } static int emufxrchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; uint32_t sz; if (!PCMTRIG_COMMON(go)) return (0); switch (sc->bufsz) { case 4096: sz = EMU_RECBS_BUFSIZE_4096; break; case 8192: sz = EMU_RECBS_BUFSIZE_8192; break; case 16384: sz = EMU_RECBS_BUFSIZE_16384; break; case 32768: sz = EMU_RECBS_BUFSIZE_32768; break; case 65536: sz = EMU_RECBS_BUFSIZE_65536; break; default: sz = EMU_RECBS_BUFSIZE_4096; } snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; emu_wrptr(sc->card, 0, ch->sizereg, sz); ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); /* * SB Live! is limited to 32 mono channels. Audigy * has 64 mono channels. Channels are enabled * by setting a bit in EMU_A_FXWC[1|2] registers. */ /* XXX there is no way to demultiplex this streams for now */ if (sc->is_emu10k1) { emu_wrptr(sc->card, 0, EMU_FXWC, 0xffffffff); } else { emu_wrptr(sc->card, 0, EMU_A_FXWC1, 0xffffffff); emu_wrptr(sc->card, 0, EMU_A_FXWC2, 0xffffffff); } break; case PCMTRIG_STOP: /* FALLTHROUGH */ case PCMTRIG_ABORT: ch->run = 0; if (sc->is_emu10k1) { emu_wrptr(sc->card, 0, EMU_FXWC, 0x0); } else { emu_wrptr(sc->card, 0, EMU_A_FXWC1, 0x0); emu_wrptr(sc->card, 0, EMU_A_FXWC2, 0x0); } emu_wrptr(sc->card, 0, ch->sizereg, 0); (void)emu_intr_unregister(sc->card, ch->ihandle); break; case PCMTRIG_EMLDMAWR: /* FALLTHROUGH */ case PCMTRIG_EMLDMARD: /* FALLTHROUGH */ default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t emufxrchan_getptr(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; int r; r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; return (r); } static struct pcmchan_caps * emufxrchan_getcaps(kobj_t obj __unused, void *c_devinfo) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (sc->is_emu10k1) return (&emu_reccaps_efx_live); return (&emu_reccaps_efx_audigy); } static int emufxrchan_getrates(kobj_t obj __unused, void *c_devinfo, int **rates) { struct emu_pcm_rchinfo *ch = c_devinfo; struct emu_pcm_info *sc = ch->pcm; if (sc->is_emu10k1) *rates = emu_rates_live; else *rates = emu_rates_audigy; return 1; } static kobj_method_t emufxrchan_methods[] = { KOBJMETHOD(channel_init, emufxrchan_init), KOBJMETHOD(channel_setformat, emufxrchan_setformat), KOBJMETHOD(channel_setspeed, emufxrchan_setspeed), KOBJMETHOD(channel_setblocksize, emufxrchan_setblocksize), KOBJMETHOD(channel_trigger, emufxrchan_trigger), KOBJMETHOD(channel_getptr, emufxrchan_getptr), KOBJMETHOD(channel_getcaps, emufxrchan_getcaps), KOBJMETHOD(channel_getrates, emufxrchan_getrates), KOBJMETHOD_END }; CHANNEL_DECLARE(emufxrchan); static uint32_t emu_pcm_intr(void *pcm, uint32_t stat) { struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm; uint32_t ack; int i; ack = 0; snd_mtxlock(sc->lock); if (stat & EMU_IPR_INTERVALTIMER) { ack |= EMU_IPR_INTERVALTIMER; for (i = 0; i < MAX_CHANNELS; i++) if (sc->pch[i].channel) { if (sc->pch[i].run == 1) { snd_mtxunlock(sc->lock); chn_intr(sc->pch[i].channel); snd_mtxlock(sc->lock); } else emu_timer_enable(sc->card, sc->pch[i].timer, 0); } /* ADC may install timer to get low-latency interrupts */ if ((sc->rch_adc.channel) && (sc->rch_adc.run)) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_adc.channel); snd_mtxlock(sc->lock); } /* * EFX does not use timer, because it will fill * buffer at least 32x times faster than ADC. */ } if (stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL)) { ack |= stat & (EMU_IPR_ADCBUFFULL | EMU_IPR_ADCBUFHALFFULL); if (sc->rch_adc.channel) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_adc.channel); snd_mtxlock(sc->lock); } } if (stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL)) { ack |= stat & (EMU_IPR_EFXBUFFULL | EMU_IPR_EFXBUFHALFFULL); if (sc->rch_efx.channel) { snd_mtxunlock(sc->lock); chn_intr(sc->rch_efx.channel); snd_mtxlock(sc->lock); } } snd_mtxunlock(sc->lock); return (ack); } static int emu_pcm_init(struct emu_pcm_info *sc) { sc->bufsz = pcm_getbuffersize(sc->dev, EMUPAGESIZE, EMU_REC_BUFSZ, EMU_MAX_BUFSZ); return (0); } static int emu_pcm_uninit(struct emu_pcm_info *sc __unused) { return (0); } static int emu_pcm_probe(device_t dev) { uintptr_t func, route; const char *rt; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func); if (func != SCF_PCM) return (ENXIO); rt = "UNKNOWN"; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); switch (route) { case RT_FRONT: rt = "front"; break; case RT_REAR: rt = "rear"; break; case RT_CENTER: rt = "center"; break; case RT_SUB: rt = "subwoofer"; break; case RT_SIDE: rt = "side"; break; case RT_MCHRECORD: rt = "multichannel recording"; break; } device_set_descf(dev, "EMU10Kx DSP %s PCM interface", rt); return (0); } static int emu_pcm_attach(device_t dev) { struct emu_pcm_info *sc; unsigned int i; char status[SND_STATUSLEN]; uint32_t inte, ipr; uintptr_t route, ivar; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev))); if (sc->card == NULL) { device_printf(dev, "cannot get bridge conf\n"); free(sc, M_DEVBUF); return (ENXIO); } sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10kx pcm softc"); sc->dev = dev; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &ivar); sc->is_emu10k1 = ivar ? 1 : 0; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_MCH_DISABLED, &ivar); sc->mch_disabled = ivar ? 1 : 0; sc->codec = NULL; for (i = 0; i < 8; i++) { sc->rt.routing_left[i] = i; sc->rt.amounts_left[i] = 0x00; sc->rt.routing_right[i] = i; sc->rt.amounts_right[i] = 0x00; } for (i = 0; i < 8; i++) { sc->rt_mono.routing_left[i] = i; sc->rt_mono.amounts_left[i] = 0x00; sc->rt_mono.routing_right[i] = i; sc->rt_mono.amounts_right[i] = 0x00; } sc->emu10k1_volcache[0][0] = 75; sc->emu10k1_volcache[1][0] = 75; sc->emu10k1_volcache[0][1] = 75; sc->emu10k1_volcache[1][1] = 75; BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); sc->route = route; switch (route) { case RT_FRONT: sc->rt.amounts_left[0] = 0xff; sc->rt.amounts_right[1] = 0xff; sc->rt_mono.amounts_left[0] = 0xff; sc->rt_mono.amounts_left[1] = 0xff; if (sc->is_emu10k1) sc->codec = AC97_CREATE(dev, sc, emu_ac97); else sc->codec = AC97_CREATE(dev, sc, emu_eac97); sc->ac97_mixerclass = NULL; if (sc->codec != NULL) sc->ac97_mixerclass = ac97_getmixerclass(); if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize DSP mixer\n"); goto bad; } break; case RT_REAR: sc->rt.amounts_left[2] = 0xff; sc->rt.amounts_right[3] = 0xff; sc->rt_mono.amounts_left[2] = 0xff; sc->rt_mono.amounts_left[3] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_CENTER: sc->rt.amounts_left[4] = 0xff; sc->rt_mono.amounts_left[4] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SUB: sc->rt.amounts_left[5] = 0xff; sc->rt_mono.amounts_left[5] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_SIDE: sc->rt.amounts_left[6] = 0xff; sc->rt.amounts_right[7] = 0xff; sc->rt_mono.amounts_left[6] = 0xff; sc->rt_mono.amounts_left[7] = 0xff; if (mixer_init(dev, &emudspmixer_class, sc)) { device_printf(dev, "failed to initialize mixer\n"); goto bad; } break; case RT_MCHRECORD: if (mixer_init(dev, &emuefxmixer_class, sc)) { device_printf(dev, "failed to initialize EFX mixer\n"); goto bad; } break; default: device_printf(dev, "invalid default route\n"); goto bad; } inte = EMU_INTE_INTERTIMERENB; ipr = EMU_IPR_INTERVALTIMER; /* Used by playback & ADC */ sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc); if (emu_pcm_init(sc) == -1) { device_printf(dev, "unable to initialize PCM part of the card\n"); goto bad; } /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); /* XXX we should better get number of available channels from parent */ pcm_init(dev, sc); sc->pnum = 0; if (route != RT_MCHRECORD) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); if (route == RT_FRONT) { for (i = 1; i < MAX_CHANNELS; i++) pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); } if (route == RT_MCHRECORD) pcm_addchan(dev, PCMDIR_REC, &emufxrchan_class, sc); snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) goto bad; return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static int emu_pcm_detach(device_t dev) { int r; struct emu_pcm_info *sc; sc = pcm_getdevinfo(dev); r = pcm_unregister(dev); if (r) return (r); emu_pcm_uninit(sc); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (0); } static device_method_t emu_pcm_methods[] = { DEVMETHOD(device_probe, emu_pcm_probe), DEVMETHOD(device_attach, emu_pcm_attach), DEVMETHOD(device_detach, emu_pcm_detach), DEVMETHOD_END }; static driver_t emu_pcm_driver = { "pcm", emu_pcm_methods, PCM_SOFTC_SIZE, NULL, 0, NULL }; DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, 0, 0); MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER); diff --git a/sys/dev/sound/pci/es137x.c b/sys/dev/sound/pci/es137x.c index 3c1bea09b5d1..eb546de81f65 100644 --- a/sys/dev/sound/pci/es137x.c +++ b/sys/dev/sound/pci/es137x.c @@ -1,1944 +1,1944 @@ /*- * SPDX-License-Identifier: BSD-2-Clause AND BSD-4-Clause * * Copyright (c) 1999 Russell Cattelan * Copyright (c) 1998 Joachim Kuebart * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgement: * This product includes software developed by Joachim Kuebart. * * 4. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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. */ /* * Support the ENSONIQ AudioPCI board and Creative Labs SoundBlaster PCI * boards based on the ES1370, ES1371 and ES1373 chips. * * Part of this code was heavily inspired by the linux driver from * Thomas Sailer (sailer@ife.ee.ethz.ch) * Just about everything has been touched and reworked in some way but * the all the underlying sequences/timing/register values are from * Thomas' code. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" #define MEM_MAP_REG 0x14 /* PCI IDs of supported chips */ #define ES1370_PCI_ID 0x50001274 #define ES1371_PCI_ID 0x13711274 #define ES1371_PCI_ID2 0x13713274 #define CT5880_PCI_ID 0x58801274 #define CT4730_PCI_ID 0x89381102 #define ES1371REV_ES1371_A 0x02 #define ES1371REV_ES1371_B 0x09 #define ES1371REV_ES1373_8 0x08 #define ES1371REV_ES1373_A 0x04 #define ES1371REV_ES1373_B 0x06 #define ES1371REV_CT5880_A 0x07 #define CT5880REV_CT5880_C 0x02 #define CT5880REV_CT5880_D 0x03 #define CT5880REV_CT5880_E 0x04 #define CT4730REV_CT4730_A 0x00 #define ES_DEFAULT_BUFSZ 4096 /* 2 DAC for playback, 1 ADC for record */ #define ES_DAC1 0 #define ES_DAC2 1 #define ES_ADC 2 #define ES_NCHANS 3 #define ES_DMA_SEGS_MIN 2 #define ES_DMA_SEGS_MAX 256 #define ES_BLK_MIN 64 #define ES_BLK_ALIGN (~(ES_BLK_MIN - 1)) #define ES1370_DAC1_MINSPEED 5512 #define ES1370_DAC1_MAXSPEED 44100 /* device private data */ struct es_info; struct es_chinfo { struct es_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct pcmchan_caps caps; int dir, num, index; uint32_t fmt, blksz, blkcnt, bufsz; uint32_t ptr, prevptr; int active; }; /* * 32bit Ensoniq Configuration (es->escfg). * ---------------------------------------- * * +-------+--------+------+------+---------+--------+---------+---------+ * len | 16 | 1 | 1 | 1 | 2 | 2 | 1 | 8 | * +-------+--------+------+------+---------+--------+---------+---------+ * | fixed | single | | | | | is | general | * | rate | pcm | DACx | DACy | numplay | numrec | es1370? | purpose | * | | mixer | | | | | | | * +-------+--------+------+------+---------+--------+---------+---------+ */ #define ES_FIXED_RATE(cfgv) \ (((cfgv) & 0xffff0000) >> 16) #define ES_SET_FIXED_RATE(cfgv, nv) \ (((cfgv) & ~0xffff0000) | (((nv) & 0xffff) << 16)) #define ES_SINGLE_PCM_MIX(cfgv) \ (((cfgv) & 0x8000) >> 15) #define ES_SET_SINGLE_PCM_MIX(cfgv, nv) \ (((cfgv) & ~0x8000) | (((nv) ? 1 : 0) << 15)) #define ES_DAC_FIRST(cfgv) \ (((cfgv) & 0x4000) >> 14) #define ES_SET_DAC_FIRST(cfgv, nv) \ (((cfgv) & ~0x4000) | (((nv) & 0x1) << 14)) #define ES_DAC_SECOND(cfgv) \ (((cfgv) & 0x2000) >> 13) #define ES_SET_DAC_SECOND(cfgv, nv) \ (((cfgv) & ~0x2000) | (((nv) & 0x1) << 13)) #define ES_NUMPLAY(cfgv) \ (((cfgv) & 0x1800) >> 11) #define ES_SET_NUMPLAY(cfgv, nv) \ (((cfgv) & ~0x1800) | (((nv) & 0x3) << 11)) #define ES_NUMREC(cfgv) \ (((cfgv) & 0x600) >> 9) #define ES_SET_NUMREC(cfgv, nv) \ (((cfgv) & ~0x600) | (((nv) & 0x3) << 9)) #define ES_IS_ES1370(cfgv) \ (((cfgv) & 0x100) >> 8) #define ES_SET_IS_ES1370(cfgv, nv) \ (((cfgv) & ~0x100) | (((nv) ? 1 : 0) << 8)) #define ES_GP(cfgv) \ ((cfgv) & 0xff) #define ES_SET_GP(cfgv, nv) \ (((cfgv) & ~0xff) | ((nv) & 0xff)) #define ES_DAC1_ENABLED(cfgv) \ (ES_NUMPLAY(cfgv) > 1 || \ (ES_NUMPLAY(cfgv) == 1 && ES_DAC_FIRST(cfgv) == ES_DAC1)) #define ES_DAC2_ENABLED(cfgv) \ (ES_NUMPLAY(cfgv) > 1 || \ (ES_NUMPLAY(cfgv) == 1 && ES_DAC_FIRST(cfgv) == ES_DAC2)) /* * DAC 1/2 configuration through kernel hint - hint.pcm..dac="val" * * 0 = Enable both DACs - Default * 1 = Enable single DAC (DAC1) * 2 = Enable single DAC (DAC2) * 3 = Enable both DACs, swap position (DAC2 comes first instead of DAC1) */ #define ES_DEFAULT_DAC_CFG 0 struct es_info { bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; device_t dev; int num; unsigned int bufsz, blkcnt; /* Contents of board's registers */ uint32_t ctrl; uint32_t sctrl; uint32_t escfg; struct es_chinfo ch[ES_NCHANS]; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; #define ES_LOCK(sc) snd_mtxlock((sc)->lock) #define ES_UNLOCK(sc) snd_mtxunlock((sc)->lock) #define ES_LOCK_ASSERT(sc) snd_mtxassert((sc)->lock) /* prototypes */ static void es_intr(void *); static uint32_t es1371_wait_src_ready(struct es_info *); static void es1371_src_write(struct es_info *, unsigned short, unsigned short); static unsigned int es1371_adc_rate(struct es_info *, unsigned int, int); static unsigned int es1371_dac_rate(struct es_info *, unsigned int, int); static int es1371_init(struct es_info *); static int es1370_init(struct es_info *); static int es1370_wrcodec(struct es_info *, unsigned char, unsigned char); static uint32_t es_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps es_caps = {4000, 48000, es_fmt, 0}; static const struct { unsigned volidx:4; unsigned left:4; unsigned right:4; unsigned stereo:1; unsigned recmask:13; unsigned avail:1; } mixtable[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x1f7f, 1 }, [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, [SOUND_MIXER_LINE] = { 4, 0x8, 0x9, 1, 0x0018, 1 }, [SOUND_MIXER_LINE1] = { 5, 0xa, 0xb, 1, 0x1800, 1 }, [SOUND_MIXER_LINE2] = { 6, 0xc, 0x0, 0, 0x0100, 1 }, [SOUND_MIXER_LINE3] = { 7, 0xd, 0x0, 0, 0x0200, 1 }, [SOUND_MIXER_MIC] = { 8, 0xe, 0x0, 0, 0x0001, 1 }, [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; static __inline uint32_t es_rd(struct es_info *es, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(es->st, es->sh, regno)); case 2: return (bus_space_read_2(es->st, es->sh, regno)); case 4: return (bus_space_read_4(es->st, es->sh, regno)); default: return (0xFFFFFFFF); } } static __inline void es_wr(struct es_info *es, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(es->st, es->sh, regno, data); break; case 2: bus_space_write_2(es->st, es->sh, regno, data); break; case 4: bus_space_write_4(es->st, es->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* The es1370 mixer interface */ static int es1370_mixinit(struct snd_mixer *m) { struct es_info *es; int i; uint32_t v; es = mix_getdevinfo(m); v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mixtable[i].avail) v |= (1 << i); } /* * Each DAC1/2 for ES1370 can be controlled independently * DAC1 = controlled by synth * DAC2 = controlled by pcm * This is indeed can confuse user if DAC1 become primary playback * channel. Try to be smart and combine both if necessary. */ if (ES_SINGLE_PCM_MIX(es->escfg)) v &= ~(1 << SOUND_MIXER_SYNTH); mix_setdevs(m, v); v = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mixtable[i].recmask) v |= (1 << i); } if (ES_SINGLE_PCM_MIX(es->escfg)) /* ditto */ v &= ~(1 << SOUND_MIXER_SYNTH); mix_setrecdevs(m, v); return (0); } static int es1370_mixset(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct es_info *es; int l, r, rl, rr, set_dac1; if (!mixtable[dev].avail) return (-1); l = left; r = (mixtable[dev].stereo) ? right : l; if (mixtable[dev].left == 0xf) rl = (l < 2) ? 0x80 : 7 - (l - 2) / 14; else rl = (l < 7) ? 0x80 : 31 - (l - 7) / 3; es = mix_getdevinfo(m); ES_LOCK(es); if (dev == SOUND_MIXER_PCM && (ES_SINGLE_PCM_MIX(es->escfg)) && ES_DAC1_ENABLED(es->escfg)) set_dac1 = 1; else set_dac1 = 0; if (mixtable[dev].stereo) { rr = (r < 7) ? 0x80 : 31 - (r - 7) / 3; es1370_wrcodec(es, mixtable[dev].right, rr); if (set_dac1 && mixtable[SOUND_MIXER_SYNTH].stereo) es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].right, rr); } es1370_wrcodec(es, mixtable[dev].left, rl); if (set_dac1) es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].left, rl); ES_UNLOCK(es); return (l | (r << 8)); } static uint32_t es1370_mixsetrecsrc(struct snd_mixer *m, uint32_t src) { struct es_info *es; int i, j = 0; es = mix_getdevinfo(m); if (src == 0) src = 1 << SOUND_MIXER_MIC; src &= mix_getrecdevs(m); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if ((src & (1 << i)) != 0) j |= mixtable[i].recmask; ES_LOCK(es); if ((src & (1 << SOUND_MIXER_PCM)) && ES_SINGLE_PCM_MIX(es->escfg) && ES_DAC1_ENABLED(es->escfg)) j |= mixtable[SOUND_MIXER_SYNTH].recmask; es1370_wrcodec(es, CODEC_LIMIX1, j & 0x55); es1370_wrcodec(es, CODEC_RIMIX1, j & 0xaa); es1370_wrcodec(es, CODEC_LIMIX2, (j >> 8) & 0x17); es1370_wrcodec(es, CODEC_RIMIX2, (j >> 8) & 0x0f); es1370_wrcodec(es, CODEC_OMIX1, 0x7f); es1370_wrcodec(es, CODEC_OMIX2, 0x3f); ES_UNLOCK(es); return (src); } static kobj_method_t es1370_mixer_methods[] = { KOBJMETHOD(mixer_init, es1370_mixinit), KOBJMETHOD(mixer_set, es1370_mixset), KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(es1370_mixer); /* -------------------------------------------------------------------- */ static int es1370_wrcodec(struct es_info *es, unsigned char i, unsigned char data) { unsigned int t; ES_LOCK_ASSERT(es); for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1370_REG_STATUS, 4) & STAT_CSTAT) == 0) { es_wr(es, ES1370_REG_CODEC, ((unsigned short)i << CODEC_INDEX_SHIFT) | data, 2); return (0); } DELAY(1); } device_printf(es->dev, "%s: timed out\n", __func__); return (-1); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct es_info *es = devinfo; struct es_chinfo *ch; uint32_t index; ES_LOCK(es); if (dir == PCMDIR_PLAY) { index = ES_GP(es->escfg); es->escfg = ES_SET_GP(es->escfg, index + 1); if (index == 0) index = ES_DAC_FIRST(es->escfg); else if (index == 1) index = ES_DAC_SECOND(es->escfg); else { device_printf(es->dev, "Invalid ES_GP index: %d\n", index); ES_UNLOCK(es); return (NULL); } if (!(index == ES_DAC1 || index == ES_DAC2)) { device_printf(es->dev, "Unknown DAC: %d\n", index + 1); ES_UNLOCK(es); return (NULL); } if (es->ch[index].channel != NULL) { device_printf(es->dev, "DAC%d already initialized!\n", index + 1); ES_UNLOCK(es); return (NULL); } } else index = ES_ADC; ch = &es->ch[index]; ch->index = index; ch->num = es->num++; ch->caps = es_caps; if (ES_IS_ES1370(es->escfg)) { if (ch->index == ES_DAC1) { ch->caps.maxspeed = ES1370_DAC1_MAXSPEED; ch->caps.minspeed = ES1370_DAC1_MINSPEED; } else { uint32_t fixed_rate = ES_FIXED_RATE(es->escfg); if (!(fixed_rate < es_caps.minspeed || fixed_rate > es_caps.maxspeed)) { ch->caps.maxspeed = fixed_rate; ch->caps.minspeed = fixed_rate; } } } ch->parent = es; ch->channel = c; ch->buffer = b; ch->bufsz = es->bufsz; ch->blkcnt = es->blkcnt; ch->blksz = ch->bufsz / ch->blkcnt; ch->dir = dir; ES_UNLOCK(es); if (sndbuf_alloc(ch->buffer, es->parent_dmat, 0, ch->bufsz) != 0) return (NULL); ES_LOCK(es); if (dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, - sndbuf_getbufaddr(ch->buffer), 4); + ch->buffer->buf_addr, 4); es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_DAC2_FRAMEADR & 0xff, - sndbuf_getbufaddr(ch->buffer), 4); + ch->buffer->buf_addr, 4); es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8, 1); es_wr(es, ES1370_REG_ADC_FRAMEADR & 0xff, - sndbuf_getbufaddr(ch->buffer), 4); + ch->buffer->buf_addr, 4); es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } ES_UNLOCK(es); return (ch); } static int eschan_setformat(kobj_t obj, void *data, uint32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es->sctrl &= ~SCTRL_P1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P1SEB; if (AFMT_CHANNEL(format) > 1) es->sctrl |= SCTRL_P1SMB; } else { es->sctrl &= ~SCTRL_P2FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; if (AFMT_CHANNEL(format) > 1) es->sctrl |= SCTRL_P2SMB; } } else { es->sctrl &= ~SCTRL_R1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; if (AFMT_CHANNEL(format) > 1) es->sctrl |= SCTRL_R1SMB; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); ch->fmt = format; return (0); } static uint32_t eschan1370_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; ES_LOCK(es); /* Fixed rate , do nothing. */ if (ch->caps.minspeed == ch->caps.maxspeed) { ES_UNLOCK(es); return (ch->caps.maxspeed); } if (speed < ch->caps.minspeed) speed = ch->caps.minspeed; if (speed > ch->caps.maxspeed) speed = ch->caps.maxspeed; if (ch->index == ES_DAC1) { /* * DAC1 does not support continuous rate settings. * Pick the nearest and use it since FEEDER_RATE will * do the proper conversion for us. */ es->ctrl &= ~CTRL_WTSRSEL; if (speed < 8268) { speed = 5512; es->ctrl |= 0 << CTRL_SH_WTSRSEL; } else if (speed < 16537) { speed = 11025; es->ctrl |= 1 << CTRL_SH_WTSRSEL; } else if (speed < 33075) { speed = 22050; es->ctrl |= 2 << CTRL_SH_WTSRSEL; } else { speed = 44100; es->ctrl |= 3 << CTRL_SH_WTSRSEL; } } else { es->ctrl &= ~CTRL_PCLKDIV; es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; } es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); ES_UNLOCK(es); return (speed); } static uint32_t eschan1371_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t i; int delta; ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) i = es1371_dac_rate(es, speed, ch->index); /* play */ else i = es1371_adc_rate(es, speed, ch->index); /* record */ ES_UNLOCK(es); delta = (speed > i) ? (speed - i) : (i - speed); if (delta < 2) return (speed); return (i); } static int eschan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; blksz &= ES_BLK_ALIGN; - if (blksz > (sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN)) - blksz = sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN; + if (blksz > (ch->buffer->maxsize / ES_DMA_SEGS_MIN)) + blksz = ch->buffer->maxsize / ES_DMA_SEGS_MIN; if (blksz < ES_BLK_MIN) blksz = ES_BLK_MIN; if (blkcnt > ES_DMA_SEGS_MAX) blkcnt = ES_DMA_SEGS_MAX; if (blkcnt < ES_DMA_SEGS_MIN) blkcnt = ES_DMA_SEGS_MIN; - while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + while ((blksz * blkcnt) > ch->buffer->maxsize) { if ((blkcnt >> 1) >= ES_DMA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= ES_BLK_MIN) blksz >>= 1; else break; } - if ((sndbuf_getblksz(ch->buffer) != blksz || - sndbuf_getblkcnt(ch->buffer) != blkcnt) && + if ((ch->buffer->blksz != blksz || + ch->buffer->blkcnt != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(es->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); - ch->bufsz = sndbuf_getsize(ch->buffer); - ch->blksz = sndbuf_getblksz(ch->buffer); - ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + ch->bufsz = ch->buffer->bufsize; + ch->blksz = ch->buffer->blksz; + ch->blkcnt = ch->buffer->blkcnt; return (0); } static uint32_t eschan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; eschan_setfragments(obj, data, blksz, es->blkcnt); return (ch->blksz); } #define es_chan_active(es) ((es)->ch[ES_DAC1].active + \ (es)->ch[ES_DAC2].active + \ (es)->ch[ES_ADC].active) static __inline int es_poll_channel(struct es_chinfo *ch) { struct es_info *es; uint32_t sz, delta; uint32_t reg, ptr; if (ch == NULL || ch->channel == NULL || ch->active == 0) return (0); es = ch->parent; if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) reg = ES1370_REG_DAC1_FRAMECNT; else reg = ES1370_REG_DAC2_FRAMECNT; } else reg = ES1370_REG_ADC_FRAMECNT; sz = ch->blksz * ch->blkcnt; es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); ptr = es_rd(es, reg & 0x000000ff, 4) >> 16; ptr <<= 2; ch->ptr = ptr; ptr %= sz; ptr &= ~(ch->blksz - 1); delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } static void es_poll_callback(void *arg) { struct es_info *es = arg; uint32_t trigger = 0; int i; if (es == NULL) return; ES_LOCK(es); if (es->polling == 0 || es_chan_active(es) == 0) { ES_UNLOCK(es); return; } for (i = 0; i < ES_NCHANS; i++) { if (es_poll_channel(&es->ch[i]) != 0) trigger |= 1 << i; } /* XXX */ callout_reset(&es->poll_timer, 1/*es->poll_ticks*/, es_poll_callback, es); ES_UNLOCK(es); for (i = 0; i < ES_NCHANS; i++) { if (trigger & (1 << i)) chn_intr(es->ch[i].channel); } } static int eschan_trigger(kobj_t obj, void *data, int go) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t cnt, b = 0; if (!PCMTRIG_COMMON(go)) return 0; ES_LOCK(es); - cnt = (ch->blksz / sndbuf_getalign(ch->buffer)) - 1; + cnt = (ch->blksz / ch->buffer->align) - 1; if (ch->fmt & AFMT_16BIT) b |= 0x02; if (AFMT_CHANNEL(ch->fmt) > 1) b |= 0x01; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { if (ch->index == ES_DAC1) { es->ctrl |= CTRL_DAC1_EN; es->sctrl &= ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD); if (es->polling == 0) es->sctrl |= SCTRL_P1INTEN; else es->sctrl &= ~SCTRL_P1INTEN; es->sctrl |= b; es_wr(es, ES1370_REG_DAC1_SCOUNT, cnt, 4); /* start at beginning of buffer */ es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMECNT >> 8, 4); es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else { es->ctrl |= CTRL_DAC2_EN; es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN); if (es->polling == 0) es->sctrl |= SCTRL_P2INTEN; else es->sctrl &= ~SCTRL_P2INTEN; es->sctrl |= (b << 2) | ((((b >> 1) & 1) + 1) << SCTRL_SH_P2ENDINC); es_wr(es, ES1370_REG_DAC2_SCOUNT, cnt, 4); /* start at beginning of buffer */ es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8, 4); es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } } else es->ctrl &= ~((ch->index == ES_DAC1) ? CTRL_DAC1_EN : CTRL_DAC2_EN); } else { if (go == PCMTRIG_START) { es->ctrl |= CTRL_ADC_EN; es->sctrl &= ~SCTRL_R1LOOPSEL; if (es->polling == 0) es->sctrl |= SCTRL_R1INTEN; else es->sctrl &= ~SCTRL_R1INTEN; es->sctrl |= b << 4; es_wr(es, ES1370_REG_ADC_SCOUNT, cnt, 4); /* start at beginning of buffer */ es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8, 4); es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } else es->ctrl &= ~CTRL_ADC_EN; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); if (go == PCMTRIG_START) { if (es->polling != 0) { ch->ptr = 0; ch->prevptr = 0; if (es_chan_active(es) == 0) { es->poll_ticks = 1; callout_reset(&es->poll_timer, 1, es_poll_callback, es); } } ch->active = 1; } else { ch->active = 0; if (es->polling != 0) { if (es_chan_active(es) == 0) { callout_stop(&es->poll_timer); es->poll_ticks = 1; } } } ES_UNLOCK(es); return (0); } static uint32_t eschan_getptr(kobj_t obj, void *data) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; uint32_t reg, cnt; ES_LOCK(es); if (es->polling != 0) cnt = ch->ptr; else { if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) reg = ES1370_REG_DAC1_FRAMECNT; else reg = ES1370_REG_DAC2_FRAMECNT; } else reg = ES1370_REG_ADC_FRAMECNT; es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; /* cnt is longwords */ cnt <<= 2; } ES_UNLOCK(es); cnt &= ES_BLK_ALIGN; return (cnt); } static struct pcmchan_caps * eschan_getcaps(kobj_t obj, void *data) { struct es_chinfo *ch = data; return (&ch->caps); } static kobj_method_t eschan1370_methods[] = { KOBJMETHOD(channel_init, eschan_init), KOBJMETHOD(channel_setformat, eschan_setformat), KOBJMETHOD(channel_setspeed, eschan1370_setspeed), KOBJMETHOD(channel_setblocksize, eschan_setblocksize), KOBJMETHOD(channel_setfragments, eschan_setfragments), KOBJMETHOD(channel_trigger, eschan_trigger), KOBJMETHOD(channel_getptr, eschan_getptr), KOBJMETHOD(channel_getcaps, eschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(eschan1370); static kobj_method_t eschan1371_methods[] = { KOBJMETHOD(channel_init, eschan_init), KOBJMETHOD(channel_setformat, eschan_setformat), KOBJMETHOD(channel_setspeed, eschan1371_setspeed), KOBJMETHOD(channel_setblocksize, eschan_setblocksize), KOBJMETHOD(channel_setfragments, eschan_setfragments), KOBJMETHOD(channel_trigger, eschan_trigger), KOBJMETHOD(channel_getptr, eschan_getptr), KOBJMETHOD(channel_getcaps, eschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(eschan1371); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void es_intr(void *p) { struct es_info *es = p; uint32_t intsrc, sctrl; ES_LOCK(es); if (es->polling != 0) { ES_UNLOCK(es); return; } intsrc = es_rd(es, ES1370_REG_STATUS, 4); if ((intsrc & STAT_INTR) == 0) { ES_UNLOCK(es); return; } sctrl = es->sctrl; if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; es_wr(es, ES1370_REG_SERIAL_CONTROL, sctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); if (intsrc & STAT_ADC) chn_intr(es->ch[ES_ADC].channel); if (intsrc & STAT_DAC1) chn_intr(es->ch[ES_DAC1].channel); if (intsrc & STAT_DAC2) chn_intr(es->ch[ES_DAC2].channel); } /* ES1370 specific */ static int es1370_init(struct es_info *es) { uint32_t fixed_rate; int r, single_pcm; /* ES1370 default to fixed rate operation */ if (resource_int_value(device_get_name(es->dev), device_get_unit(es->dev), "fixed_rate", &r) == 0) { fixed_rate = r; if (fixed_rate) { if (fixed_rate < es_caps.minspeed) fixed_rate = es_caps.minspeed; if (fixed_rate > es_caps.maxspeed) fixed_rate = es_caps.maxspeed; } } else fixed_rate = es_caps.maxspeed; if (resource_int_value(device_get_name(es->dev), device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) single_pcm = (r != 0) ? 1 : 0; else single_pcm = 1; ES_LOCK(es); if (ES_NUMPLAY(es->escfg) == 1) single_pcm = 1; /* This is ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 1); if (fixed_rate) es->escfg = ES_SET_FIXED_RATE(es->escfg, fixed_rate); else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); fixed_rate = DSP_DEFAULT_SPEED; } if (single_pcm) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); es->ctrl = CTRL_CDC_EN | CTRL_JYSTK_EN | CTRL_SERR_DIS | (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); es->ctrl |= 3 << CTRL_SH_WTSRSEL; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es->sctrl = 0; es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); /* No RST, PD */ es1370_wrcodec(es, CODEC_RES_PD, 3); /* * CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; * program DAC_SYNC=0! */ es1370_wrcodec(es, CODEC_CSEL, 0); /* Recording source is mixer */ es1370_wrcodec(es, CODEC_ADSEL, 0); /* MIC amp is 0db */ es1370_wrcodec(es, CODEC_MGAIN, 0); ES_UNLOCK(es); return (0); } /* ES1371 specific */ int es1371_init(struct es_info *es) { uint32_t cssr, devid, revid, subdev; int idx; ES_LOCK(es); /* This is NOT ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 0); es->num = 0; es->sctrl = 0; cssr = 0; devid = pci_get_devid(es->dev); revid = pci_get_revid(es->dev); subdev = (pci_get_subdevice(es->dev) << 16) | pci_get_subvendor(es->dev); /* * Joyport blacklist. Either we're facing with broken hardware * or because this hardware need special (unknown) initialization * procedures. */ switch (subdev) { case 0x20001274: /* old Ensoniq */ es->ctrl = 0; break; default: es->ctrl = CTRL_JYSTK_EN; break; } if (devid == CT4730_PCI_ID) { /* XXX amplifier hack? */ es->ctrl |= (1 << 16); } /* initialize the chips */ es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1371_REG_LEGACY, 0, 4); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { cssr = 1 << 29; es_wr(es, ES1370_REG_STATUS, cssr, 4); DELAY(20000); } /* AC'97 warm reset to start the bitclk */ es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1371_REG_LEGACY, ES1371_SYNC_RES, 4); DELAY(2000); es_wr(es, ES1370_REG_CONTROL, es->sctrl, 4); es1371_wait_src_ready(es); /* Init the sample rate converter */ es_wr(es, ES1371_REG_SMPRATE, ES1371_DIS_SRC, 4); for (idx = 0; idx < 0x80; idx++) es1371_src_write(es, idx, 0); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); es1371_adc_rate(es, 22050, ES_ADC); es1371_dac_rate(es, 22050, ES_DAC1); es1371_dac_rate(es, 22050, ES_DAC2); /* * WARNING: * enabling the sample rate converter without properly programming * its parameters causes the chip to lock up (the SRC busy bit will * be stuck high, and I've found no way to rectify this other than * power cycle) */ es1371_wait_src_ready(es); es_wr(es, ES1371_REG_SMPRATE, 0, 4); /* try to reset codec directly */ es_wr(es, ES1371_REG_CODEC, 0, 4); es_wr(es, ES1370_REG_STATUS, cssr, 4); ES_UNLOCK(es); return (0); } /* -------------------------------------------------------------------- */ static int es1371_wrcd(kobj_t obj, void *s, int addr, uint32_t data) { uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; for (t = 0; t < 0x1000; t++) { if (!es_rd(es, ES1371_REG_CODEC & CODEC_WIP, 4)) break; } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) break; } /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; } es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); return (0); } static int es1371_rdcd(kobj_t obj, void *s, int addr) { uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; for (t = 0; t < 0x1000; t++) { if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) break; } /* wait for a SAFE time to write addr/data and then do it, dammit */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; } es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; } return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static kobj_method_t es1371_ac97_methods[] = { KOBJMETHOD(ac97_read, es1371_rdcd), KOBJMETHOD(ac97_write, es1371_wrcd), KOBJMETHOD_END }; AC97_DECLARE(es1371_ac97); /* -------------------------------------------------------------------- */ static unsigned int es1371_src_read(struct es_info *es, unsigned short reg) { uint32_t r; r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg); es_wr(es, ES1371_REG_SMPRATE, r, 4); return (ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es))); } static void es1371_src_write(struct es_info *es, unsigned short reg, unsigned short data) { uint32_t r; r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg) | ES1371_SRC_RAM_DATAO(data); es_wr(es, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE, 4); } static unsigned int es1371_adc_rate(struct es_info *es, unsigned int rate, int set) { unsigned int n, truncm, freq, result; ES_LOCK_ASSERT(es); if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; n = rate / 3000; if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) n--; truncm = (21 * n - 1) | 1; freq = ((48000UL << 15) / rate) * n; result = (48000UL << 15) / (freq / n); if (set) { if (rate >= 24000) { if (truncm > 239) truncm = 239; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, (((239 - truncm) >> 1) << 9) | (n << 4)); } else { if (truncm > 119) truncm = 119; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); } es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS, (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & 0x00ff) | ((freq >> 5) & 0xfc00)); es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); es1371_src_write(es, ES_SMPREG_VOL_ADC, n << 8); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, n << 8); } return (result); } static unsigned int es1371_dac_rate(struct es_info *es, unsigned int rate, int set) { unsigned int freq, r, result, dac, dis; ES_LOCK_ASSERT(es); if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; freq = ((rate << 15) + 1500) / 3000; result = (freq * 3000) >> 15; dac = (set == ES_DAC1) ? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; dis = (set == ES_DAC1) ? ES1371_DIS_P2 : ES1371_DIS_P1; r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); es1371_src_write(es, dac + ES_SMPREG_INT_REGS, (es1371_src_read(es, dac + ES_SMPREG_INT_REGS) & 0x00ff) | ((freq >> 5) & 0xfc00)); es1371_src_write(es, dac + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); return (result); } static uint32_t es1371_wait_src_ready(struct es_info *es) { uint32_t t, r; for (t = 0; t < 0x1000; t++) { if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & ES1371_SRC_RAM_BUSY)) return (r); DELAY(1); } device_printf(es->dev, "%s: timed out 0x%x [0x%x]\n", __func__, ES1371_REG_SMPRATE, r); return (0); } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int es_pci_probe(device_t dev) { switch(pci_get_devid(dev)) { case ES1370_PCI_ID: device_set_desc(dev, "AudioPCI ES1370"); return (BUS_PROBE_DEFAULT); case ES1371_PCI_ID: switch(pci_get_revid(dev)) { case ES1371REV_ES1371_A: device_set_desc(dev, "AudioPCI ES1371-A"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1371_B: device_set_desc(dev, "AudioPCI ES1371-B"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_A: device_set_desc(dev, "AudioPCI ES1373-A"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_B: device_set_desc(dev, "AudioPCI ES1373-B"); return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_8: device_set_desc(dev, "AudioPCI ES1373-8"); return (BUS_PROBE_DEFAULT); case ES1371REV_CT5880_A: device_set_desc(dev, "Creative CT5880-A"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "AudioPCI ES1371-?"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); } case ES1371_PCI_ID2: device_set_desc(dev, "Strange AudioPCI ES1371-? (vid=3274)"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); case CT4730_PCI_ID: switch(pci_get_revid(dev)) { case CT4730REV_CT4730_A: device_set_desc(dev, "Creative SB AudioPCI CT4730/EV1938"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative SB AudioPCI CT4730-?"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); } case CT5880_PCI_ID: switch(pci_get_revid(dev)) { case CT5880REV_CT5880_C: device_set_desc(dev, "Creative CT5880-C"); return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_D: device_set_desc(dev, "Creative CT5880-D"); return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_E: device_set_desc(dev, "Creative CT5880-E"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative CT5880-?"); device_printf(dev, "unknown revision %d -- please report to " "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); return (BUS_PROBE_DEFAULT); } default: return (ENXIO); } } static int sysctl_es137x_spdif_enable(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; uint32_t r; int err, new_en; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); ES_LOCK(es); r = es_rd(es, ES1370_REG_STATUS, 4); ES_UNLOCK(es); new_en = (r & ENABLE_SPDIF) ? 1 : 0; err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) return (err); if (new_en < 0 || new_en > 1) return (EINVAL); ES_LOCK(es); if (new_en) { r |= ENABLE_SPDIF; es->ctrl |= SPDIFEN_B; es->ctrl |= RECEN_B; } else { r &= ~ENABLE_SPDIF; es->ctrl &= ~SPDIFEN_B; es->ctrl &= ~RECEN_B; } es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es_wr(es, ES1370_REG_STATUS, r, 4); ES_UNLOCK(es); return (0); } static int sysctl_es137x_latency_timer(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; uint32_t val; int err; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); ES_LOCK(es); val = pci_read_config(dev, PCIR_LATTIMER, 1); ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val > 255) return (EINVAL); ES_LOCK(es); pci_write_config(dev, PCIR_LATTIMER, val, 1); ES_UNLOCK(es); return (0); } static int sysctl_es137x_fixed_rate(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; uint32_t val; int err; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); ES_LOCK(es); val = ES_FIXED_RATE(es->escfg); if (val < es_caps.minspeed) val = 0; ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val != 0 && (val < es_caps.minspeed || val > es_caps.maxspeed)) return (EINVAL); ES_LOCK(es); if (es->ctrl & (CTRL_DAC2_EN|CTRL_ADC_EN)) { ES_UNLOCK(es); return (EBUSY); } if (val) { if (val != ES_FIXED_RATE(es->escfg)) { es->escfg = ES_SET_FIXED_RATE(es->escfg, val); es->ch[ES_DAC2].caps.maxspeed = val; es->ch[ES_DAC2].caps.minspeed = val; es->ch[ES_ADC].caps.maxspeed = val; es->ch[ES_ADC].caps.minspeed = val; es->ctrl &= ~CTRL_PCLKDIV; es->ctrl |= DAC2_SRTODIV(val) << CTRL_SH_PCLKDIV; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); } } else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); es->ch[ES_DAC2].caps = es_caps; es->ch[ES_ADC].caps = es_caps; } ES_UNLOCK(es); return (0); } static int sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) { struct es_info *es; struct snddev_info *d; struct snd_mixer *m; device_t dev; uint32_t val, set; int recsrc, level, err; dev = oidp->oid_arg1; d = device_get_softc(dev); if (!PCM_REGISTERED(d) || d->mixer_dev == NULL || d->mixer_dev->si_drv1 == NULL) return (EINVAL); es = d->devinfo; if (es == NULL) return (EINVAL); ES_LOCK(es); set = ES_SINGLE_PCM_MIX(es->escfg); val = set; ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (!(val == 0 || val == 1)) return (EINVAL); if (val == set) return (0); PCM_ACQUIRE_QUICK(d); m = (d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; if (m == NULL) { PCM_RELEASE_QUICK(d); return (ENODEV); } if (mixer_busy(m) != 0) { PCM_RELEASE_QUICK(d); return (EBUSY); } level = mix_get(m, SOUND_MIXER_PCM); recsrc = mix_getrecsrc(m); if (level < 0 || recsrc < 0) { PCM_RELEASE_QUICK(d); return (ENXIO); } ES_LOCK(es); if (es->ctrl & (CTRL_ADC_EN | CTRL_DAC1_EN | CTRL_DAC2_EN)) { ES_UNLOCK(es); PCM_RELEASE_QUICK(d); return (EBUSY); } if (val) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); ES_UNLOCK(es); if (!val) { mix_setdevs(m, mix_getdevs(m) | (1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(m) | (1 << SOUND_MIXER_SYNTH)); err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, (level >> 8) & 0x7f); } else { err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, (level >> 8) & 0x7f); mix_setdevs(m, mix_getdevs(m) & ~(1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(m) & ~(1 << SOUND_MIXER_SYNTH)); } if (!err) { level = recsrc; if (recsrc & (1 << SOUND_MIXER_PCM)) recsrc |= 1 << SOUND_MIXER_SYNTH; else if (recsrc & (1 << SOUND_MIXER_SYNTH)) recsrc |= 1 << SOUND_MIXER_PCM; if (level != recsrc) err = mix_setrecsrc(m, recsrc); } PCM_RELEASE_QUICK(d); return (err); } static int sysctl_es_polling(SYSCTL_HANDLER_ARGS) { struct es_info *es; device_t dev; int err, val; dev = oidp->oid_arg1; es = pcm_getdevinfo(dev); if (es == NULL) return (EINVAL); ES_LOCK(es); val = es->polling; ES_UNLOCK(es); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); ES_LOCK(es); if (val != es->polling) { if (es_chan_active(es) != 0) err = EBUSY; else if (val == 0) es->polling = 0; else es->polling = 1; } ES_UNLOCK(es); return (err); } static void es_init_sysctls(device_t dev) { struct es_info *es; int r, devid, revid; devid = pci_get_devid(dev); revid = pci_get_revid(dev); es = pcm_getdevinfo(dev); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_spdif_enable, "I", "Enable S/PDIF output on primary playback channel"); } else if (devid == ES1370_PCI_ID) { /* * Enable fixed rate sysctl if both DAC2 / ADC enabled. */ if (es->ch[ES_DAC2].channel != NULL && es->ch[ES_ADC].channel != NULL) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fixed_rate", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_fixed_rate, "I", "Enable fixed rate playback/recording"); } /* * Enable single pcm mixer sysctl if both DAC1/2 enabled. */ if (es->ch[ES_DAC1].channel != NULL && es->ch[ES_DAC2].channel != NULL) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "single_pcm_mixer", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_single_pcm_mixer, "I", "Single PCM mixer controller for both DAC1/DAC2"); } } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "latency_timer", &r) == 0 && !(r < 0 || r > 255)) pci_write_config(dev, PCIR_LATTIMER, r, 1); /* XXX: this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "latency_timer", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es137x_latency_timer, "I", "PCI Latency Timer configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_es_polling, "I", "Enable polling mode"); } static int es_pci_attach(device_t dev) { struct es_info *es = NULL; int mapped, i, numplay, dac_cfg; char status[SND_STATUSLEN]; struct ac97_info *codec = NULL; kobj_class_t ct = NULL; uint32_t devid; es = malloc(sizeof *es, M_DEVBUF, M_WAITOK | M_ZERO); es->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_es137x softc"); es->dev = dev; es->escfg = 0; mapped = 0; pci_enable_busmaster(dev); if (mapped == 0) { es->regid = MEM_MAP_REG; es->regtype = SYS_RES_MEMORY; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, RF_ACTIVE); if (es->reg) mapped++; } if (mapped == 0) { es->regid = PCIR_BAR(0); es->regtype = SYS_RES_IOPORT; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, RF_ACTIVE); if (es->reg) mapped++; } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto bad; } es->st = rman_get_bustag(es->reg); es->sh = rman_get_bushandle(es->reg); callout_init(&es->poll_timer, 1); es->poll_ticks = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) es->polling = 1; else es->polling = 0; es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= ES_BLK_ALIGN; if (i < ES_BLK_MIN) i = ES_BLK_MIN; es->blkcnt = es->bufsz / i; i = 0; while (es->blkcnt >> i) i++; es->blkcnt = 1 << (i - 1); if (es->blkcnt < ES_DMA_SEGS_MIN) es->blkcnt = ES_DMA_SEGS_MIN; else if (es->blkcnt > ES_DMA_SEGS_MAX) es->blkcnt = ES_DMA_SEGS_MAX; } else es->blkcnt = 2; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &dac_cfg) == 0) { if (dac_cfg < 0 || dac_cfg > 3) dac_cfg = ES_DEFAULT_DAC_CFG; } else dac_cfg = ES_DEFAULT_DAC_CFG; switch (dac_cfg) { case 0: /* Enable all DAC: DAC1, DAC2 */ numplay = 2; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); break; case 1: /* Only DAC1 */ numplay = 1; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); break; case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ numplay = 2; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); break; case 2: /* Only DAC2 */ default: numplay = 1; es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); break; } es->escfg = ES_SET_NUMPLAY(es->escfg, numplay); es->escfg = ES_SET_NUMREC(es->escfg, 1); devid = pci_get_devid(dev); switch (devid) { case ES1371_PCI_ID: case ES1371_PCI_ID2: case CT5880_PCI_ID: case CT4730_PCI_ID: es1371_init(es); codec = AC97_CREATE(dev, es, es1371_ac97); if (codec == NULL) goto bad; /* our init routine does everything for us */ /* set to NULL; flag mixer_init not to run the ac97_init */ /* ac97_mixer.init = NULL; */ if (mixer_init(dev, ac97_getmixerclass(), codec)) goto bad; ct = &eschan1371_class; break; case ES1370_PCI_ID: es1370_init(es); /* * Disable fixed rate operation if DAC2 disabled. * This is a special case for es1370 only, where the * speed of both ADC and DAC2 locked together. */ if (!ES_DAC2_ENABLED(es->escfg)) es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; break; default: goto bad; /* NOTREACHED */ } es->irqid = 0; es->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &es->irqid, RF_ACTIVE | RF_SHAREABLE); if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, es, &es->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/es->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &es->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", (es->regtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(es->reg), rman_get_start(es->irq), device_get_nameunit(device_get_parent(dev))); pcm_init(dev, es); for (i = 0; i < numplay; i++) pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_addchan(dev, PCMDIR_REC, ct, es); es_init_sysctls(dev); if (pcm_register(dev, status)) goto bad; es->escfg = ES_SET_GP(es->escfg, 0); if (numplay == 1) device_printf(dev, "\n", ES_DAC_FIRST(es->escfg) + 1); else if (numplay == 2) device_printf(dev, "\n", ES_DAC_FIRST(es->escfg) + 1, ES_DAC_SECOND(es->escfg) + 1); return (0); bad: if (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); if (es->ih) bus_teardown_intr(dev, es->irq, es->ih); if (es->irq) bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); if (codec) ac97_destroy(codec); if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); if (es->lock) snd_mtxfree(es->lock); if (es) free(es, M_DEVBUF); return (ENXIO); } static int es_pci_detach(device_t dev) { int r; struct es_info *es; r = pcm_unregister(dev); if (r) return (r); es = pcm_getdevinfo(dev); if (es != NULL && es->num != 0) { ES_LOCK(es); es->polling = 0; callout_stop(&es->poll_timer); ES_UNLOCK(es); callout_drain(&es->poll_timer); } bus_teardown_intr(dev, es->irq, es->ih); bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); bus_release_resource(dev, es->regtype, es->regid, es->reg); bus_dma_tag_destroy(es->parent_dmat); snd_mtxfree(es->lock); free(es, M_DEVBUF); return (0); } static device_method_t es_methods[] = { /* Device interface */ DEVMETHOD(device_probe, es_pci_probe), DEVMETHOD(device_attach, es_pci_attach), DEVMETHOD(device_detach, es_pci_detach), { 0, 0 } }; static driver_t es_driver = { "pcm", es_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_es137x, pci, es_driver, 0, 0); MODULE_DEPEND(snd_es137x, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_es137x, 1); diff --git a/sys/dev/sound/pci/fm801.c b/sys/dev/sound/pci/fm801.c index 3537c7807ded..39d12f8505d1 100644 --- a/sys/dev/sound/pci/fm801.c +++ b/sys/dev/sound/pci/fm801.c @@ -1,757 +1,757 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Dmitry Dicky diwil@dataart.com * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #define PCI_VENDOR_FORTEMEDIA 0x1319 #define PCI_DEVICE_FORTEMEDIA1 0x08011319 /* Audio controller */ #define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* Joystick controller */ #define FM_PCM_VOLUME 0x00 #define FM_FM_VOLUME 0x02 #define FM_I2S_VOLUME 0x04 #define FM_RECORD_SOURCE 0x06 #define FM_PLAY_CTL 0x08 #define FM_PLAY_RATE_MASK 0x0f00 #define FM_PLAY_BUF1_LAST 0x0001 #define FM_PLAY_BUF2_LAST 0x0002 #define FM_PLAY_START 0x0020 #define FM_PLAY_PAUSE 0x0040 #define FM_PLAY_STOPNOW 0x0080 #define FM_PLAY_16BIT 0x4000 #define FM_PLAY_STEREO 0x8000 #define FM_PLAY_DMALEN 0x0a #define FM_PLAY_DMABUF1 0x0c #define FM_PLAY_DMABUF2 0x10 #define FM_REC_CTL 0x14 #define FM_REC_RATE_MASK 0x0f00 #define FM_REC_BUF1_LAST 0x0001 #define FM_REC_BUF2_LAST 0x0002 #define FM_REC_START 0x0020 #define FM_REC_PAUSE 0x0040 #define FM_REC_STOPNOW 0x0080 #define FM_REC_16BIT 0x4000 #define FM_REC_STEREO 0x8000 #define FM_REC_DMALEN 0x16 #define FM_REC_DMABUF1 0x18 #define FM_REC_DMABUF2 0x1c #define FM_CODEC_CTL 0x22 #define FM_VOLUME 0x26 #define FM_VOLUME_MUTE 0x8000 #define FM_CODEC_CMD 0x2a #define FM_CODEC_CMD_READ 0x0080 #define FM_CODEC_CMD_VALID 0x0100 #define FM_CODEC_CMD_BUSY 0x0200 #define FM_CODEC_DATA 0x2c #define FM_IO_CTL 0x52 #define FM_CARD_CTL 0x54 #define FM_INTMASK 0x56 #define FM_INTMASK_PLAY 0x0001 #define FM_INTMASK_REC 0x0002 #define FM_INTMASK_VOL 0x0040 #define FM_INTMASK_MPU 0x0080 #define FM_INTSTATUS 0x5a #define FM_INTSTATUS_PLAY 0x0100 #define FM_INTSTATUS_REC 0x0200 #define FM_INTSTATUS_VOL 0x4000 #define FM_INTSTATUS_MPU 0x8000 #define FM801_DEFAULT_BUFSZ 4096 /* Other values do not work!!! */ /* debug purposes */ #define DPRINT if(0) printf /* static int fm801ch_setup(struct pcm_channel *c); */ static u_int32_t fmts[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps fm801ch_caps = { 5500, 48000, fmts, 0 }; struct fm801_info; struct fm801_chinfo { struct fm801_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t spd, dir, fmt; /* speed, direction, format */ u_int32_t shift; }; struct fm801_info { int type; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; device_t dev; int num; u_int32_t unit; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; u_int32_t play_flip, play_nextblk, play_start, play_blksize, play_fmt, play_shift, play_size; u_int32_t rec_flip, rec_nextblk, rec_start, rec_blksize, rec_fmt, rec_shift, rec_size; unsigned int bufsz; struct fm801_chinfo pch, rch; device_t radio; }; /* Bus Read / Write routines */ static u_int32_t fm801_rd(struct fm801_info *fm801, int regno, int size) { switch(size) { case 1: return (bus_space_read_1(fm801->st, fm801->sh, regno)); case 2: return (bus_space_read_2(fm801->st, fm801->sh, regno)); case 4: return (bus_space_read_4(fm801->st, fm801->sh, regno)); default: return 0xffffffff; } } static void fm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(fm801->st, fm801->sh, regno, data); break; case 2: bus_space_write_2(fm801->st, fm801->sh, regno, data); break; case 4: bus_space_write_4(fm801->st, fm801->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* * ac97 codec routines */ #define TIMO 50 static int fm801_rdcd(kobj_t obj, void *devinfo, int regno) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; int i; for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 rdcd: 1 - DELAY\n"); } if (i >= TIMO) { printf("fm801 rdcd: codec busy\n"); return 0; } fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2); for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID); i++) { DELAY(10000); DPRINT("fm801 rdcd: 2 - DELAY\n"); } if (i >= TIMO) { printf("fm801 rdcd: write codec invalid\n"); return 0; } return fm801_rd(fm801,FM_CODEC_DATA,2); } static int fm801_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; int i; DPRINT("fm801_wrcd reg 0x%x val 0x%x\n",regno, data); /* if(regno == AC97_REG_RECSEL) return; */ /* Poll until codec is ready */ for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 rdcd: 1 - DELAY\n"); } if (i >= TIMO) { printf("fm801 wrcd: read codec busy\n"); return -1; } fm801_wr(fm801,FM_CODEC_DATA,data, 2); fm801_wr(fm801,FM_CODEC_CMD, regno,2); /* wait until codec is ready */ for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) { DELAY(10000); DPRINT("fm801 wrcd: 2 - DELAY\n"); } if (i >= TIMO) { printf("fm801 wrcd: read codec busy\n"); return -1; } DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data); return 0; } static kobj_method_t fm801_ac97_methods[] = { KOBJMETHOD(ac97_read, fm801_rdcd), KOBJMETHOD(ac97_write, fm801_wrcd), DEVMETHOD_END }; AC97_DECLARE(fm801_ac97); /* -------------------------------------------------------------------- */ /* * The interrupt handler */ static void fm801_intr(void *p) { struct fm801_info *fm801 = (struct fm801_info *)p; u_int32_t intsrc = fm801_rd(fm801, FM_INTSTATUS, 2); DPRINT("\nfm801_intr intsrc 0x%x ", intsrc); if(intsrc & FM_INTSTATUS_PLAY) { fm801->play_flip++; if(fm801->play_flip & 1) { fm801_wr(fm801, FM_PLAY_DMABUF1, fm801->play_start,4); } else fm801_wr(fm801, FM_PLAY_DMABUF2, fm801->play_nextblk,4); chn_intr(fm801->pch.channel); } if(intsrc & FM_INTSTATUS_REC) { fm801->rec_flip++; if(fm801->rec_flip & 1) { fm801_wr(fm801, FM_REC_DMABUF1, fm801->rec_start,4); } else fm801_wr(fm801, FM_REC_DMABUF2, fm801->rec_nextblk,4); chn_intr(fm801->rch.channel); } if ( intsrc & FM_INTSTATUS_MPU ) { /* This is a TODOish thing... */ fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_MPU,2); } if ( intsrc & FM_INTSTATUS_VOL ) { /* This is a TODOish thing... */ fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_VOL,2); } DPRINT("fm801_intr clear\n\n"); fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 2); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * fm801ch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct fm801_info *fm801 = (struct fm801_info *)devinfo; struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch; DPRINT("fm801ch_init, direction = %d\n", dir); ch->parent = fm801; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, 0, fm801->bufsz) != 0) return NULL; return (void *)ch; } static int fm801ch_setformat(kobj_t obj, void *data, u_int32_t format) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; DPRINT("fm801ch_setformat 0x%x : %s, %s, %s, %s\n", format, (AFMT_CHANNEL(format) > 1)?"stereo":"mono", (format & AFMT_16BIT) ? "16bit":"8bit", (format & AFMT_SIGNED)? "signed":"unsigned", (format & AFMT_BIGENDIAN)?"bigendiah":"littleendian" ); if(ch->dir == PCMDIR_PLAY) { fm801->play_fmt = (AFMT_CHANNEL(format) > 1)? FM_PLAY_STEREO : 0; fm801->play_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0; return 0; } if(ch->dir == PCMDIR_REC ) { fm801->rec_fmt = (AFMT_CHANNEL(format) > 1)? FM_REC_STEREO:0; fm801->rec_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0; return 0; } return 0; } struct { u_int32_t limit; u_int32_t rate; } fm801_rates[11] = { { 6600, 5500 }, { 8750, 8000 }, { 10250, 9600 }, { 13200, 11025 }, { 17500, 16000 }, { 20500, 19200 }, { 26500, 22050 }, { 35000, 32000 }, { 41000, 38400 }, { 46000, 44100 }, { 48000, 48000 }, /* anything above -> 48000 */ }; static u_int32_t fm801ch_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; int i; for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ; if(ch->dir == PCMDIR_PLAY) { fm801->pch.spd = fm801_rates[i].rate; fm801->play_shift = (i<<8); fm801->play_shift &= FM_PLAY_RATE_MASK; } if(ch->dir == PCMDIR_REC ) { fm801->rch.spd = fm801_rates[i].rate; fm801->rec_shift = (i<<8); fm801->rec_shift &= FM_REC_RATE_MASK; } ch->spd = fm801_rates[i].rate; return fm801_rates[i].rate; } static u_int32_t fm801ch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; /* * Don't mind for play_flip, set the blocksize to the * desired values in any case - otherwise sound playback * breaks here. */ if(ch->dir == PCMDIR_PLAY) fm801->play_blksize = blocksize; if(ch->dir == PCMDIR_REC) fm801->rec_blksize = blocksize; DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir); return blocksize; } static int fm801ch_trigger(kobj_t obj, void *data, int go) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; - u_int32_t baseaddr = sndbuf_getbufaddr(ch->buffer); + u_int32_t baseaddr = ch->buffer->buf_addr; u_int32_t k1; DPRINT("fm801ch_trigger go %d , ", go); if (!PCMTRIG_COMMON(go)) return 0; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { fm801->play_start = baseaddr; fm801->play_nextblk = fm801->play_start + fm801->play_blksize; fm801->play_flip = 0; fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2); fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4); fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4); fm801_wr(fm801, FM_PLAY_CTL, FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt | fm801->play_shift, 2 ); } else { fm801->play_flip = 0; k1 = fm801_rd(fm801, FM_PLAY_CTL,2); fm801_wr(fm801, FM_PLAY_CTL, (k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) | FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 ); } } else if(ch->dir == PCMDIR_REC) { if (go == PCMTRIG_START) { fm801->rec_start = baseaddr; fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize; fm801->rec_flip = 0; fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2); fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4); fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4); fm801_wr(fm801, FM_REC_CTL, FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt | fm801->rec_shift, 2 ); } else { fm801->rec_flip = 0; k1 = fm801_rd(fm801, FM_REC_CTL,2); fm801_wr(fm801, FM_REC_CTL, (k1 & ~(FM_REC_STOPNOW | FM_REC_START)) | FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2); } } return 0; } /* Almost ALSA copy */ static u_int32_t fm801ch_getptr(kobj_t obj, void *data) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; u_int32_t result = 0; if (ch->dir == PCMDIR_PLAY) { result = fm801_rd(fm801, (fm801->play_flip&1) ? FM_PLAY_DMABUF2:FM_PLAY_DMABUF1, 4) - fm801->play_start; } if (ch->dir == PCMDIR_REC) { result = fm801_rd(fm801, (fm801->rec_flip&1) ? FM_REC_DMABUF2:FM_REC_DMABUF1, 4) - fm801->rec_start; } return result; } static struct pcmchan_caps * fm801ch_getcaps(kobj_t obj, void *data) { return &fm801ch_caps; } static kobj_method_t fm801ch_methods[] = { KOBJMETHOD(channel_init, fm801ch_init), KOBJMETHOD(channel_setformat, fm801ch_setformat), KOBJMETHOD(channel_setspeed, fm801ch_setspeed), KOBJMETHOD(channel_setblocksize, fm801ch_setblocksize), KOBJMETHOD(channel_trigger, fm801ch_trigger), KOBJMETHOD(channel_getptr, fm801ch_getptr), KOBJMETHOD(channel_getcaps, fm801ch_getcaps), DEVMETHOD_END }; CHANNEL_DECLARE(fm801ch); /* -------------------------------------------------------------------- */ /* * Init routine is taken from an original NetBSD driver */ static int fm801_init(struct fm801_info *fm801) { u_int32_t k1; /* reset codec */ fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2); DELAY(100000); fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2); DELAY(100000); fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2); fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2); fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2); fm801_wr(fm801, 0x40,0x107f,2); /* enable legacy audio */ fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2); /* Unmask playback, record and mpu interrupts, mask the rest */ k1 = fm801_rd((void *)fm801, FM_INTMASK,2); fm801_wr(fm801, FM_INTMASK, (k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) | FM_INTMASK_VOL,2); fm801_wr(fm801, FM_INTSTATUS, FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU | FM_INTSTATUS_VOL,2); DPRINT("FM801 init Ok\n"); return 0; } static int fm801_pci_attach(device_t dev) { struct ac97_info *codec = NULL; struct fm801_info *fm801; int i; int mapped = 0; char status[SND_STATUSLEN]; fm801 = malloc(sizeof(*fm801), M_DEVBUF, M_WAITOK | M_ZERO); fm801->type = pci_get_devid(dev); pci_enable_busmaster(dev); for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) { fm801->regid = PCIR_BAR(i); fm801->regtype = SYS_RES_MEMORY; fm801->reg = bus_alloc_resource_any(dev, fm801->regtype, &fm801->regid, RF_ACTIVE); if(!fm801->reg) { fm801->regtype = SYS_RES_IOPORT; fm801->reg = bus_alloc_resource_any(dev, fm801->regtype, &fm801->regid, RF_ACTIVE); } if(fm801->reg) { fm801->st = rman_get_bustag(fm801->reg); fm801->sh = rman_get_bushandle(fm801->reg); mapped++; } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto oops; } fm801->bufsz = pcm_getbuffersize(dev, 4096, FM801_DEFAULT_BUFSZ, 65536); fm801_init(fm801); codec = AC97_CREATE(dev, fm801, fm801_ac97); if (codec == NULL) goto oops; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto oops; fm801->irqid = 0; fm801->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &fm801->irqid, RF_ACTIVE | RF_SHAREABLE); if (!fm801->irq || snd_setup_intr(dev, fm801->irq, 0, fm801_intr, fm801, &fm801->ih)) { device_printf(dev, "unable to map interrupt\n"); goto oops; } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/fm801->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &fm801->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto oops; } snprintf(status, SND_STATUSLEN, "%s 0x%jx irq %jd on %s", (fm801->regtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(fm801->reg), rman_get_start(fm801->irq), device_get_nameunit(device_get_parent(dev))); #define FM801_MAXPLAYCH 1 pcm_init(dev, fm801); pcm_addchan(dev, PCMDIR_PLAY, &fm801ch_class, fm801); pcm_addchan(dev, PCMDIR_REC, &fm801ch_class, fm801); if (pcm_register(dev, status)) goto oops; fm801->radio = device_add_child(dev, "radio", DEVICE_UNIT_ANY); bus_attach_children(dev); return 0; oops: if (codec) ac97_destroy(codec); if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg); if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih); if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq); if (fm801->parent_dmat) bus_dma_tag_destroy(fm801->parent_dmat); free(fm801, M_DEVBUF); return ENXIO; } static int fm801_pci_detach(device_t dev) { int r; struct fm801_info *fm801; DPRINT("Forte Media FM801 detach\n"); fm801 = pcm_getdevinfo(dev); r = bus_generic_detach(dev); if (r) return r; r = pcm_unregister(dev); if (r) return r; bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg); bus_teardown_intr(dev, fm801->irq, fm801->ih); bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq); bus_dma_tag_destroy(fm801->parent_dmat); free(fm801, M_DEVBUF); return 0; } static int fm801_pci_probe( device_t dev ) { int id; if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) { device_set_desc(dev, "Forte Media FM801 Audio Controller"); return BUS_PROBE_DEFAULT; } /* if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) { device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)"); return ENXIO; } */ return ENXIO; } static struct resource * fm801_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct fm801_info *fm801; fm801 = pcm_getdevinfo(bus); if (type == SYS_RES_IOPORT && *rid == PCIR_BAR(0)) return (fm801->reg); return (NULL); } static int fm801_release_resource(device_t bus, device_t child, struct resource *r) { return (0); } static device_method_t fm801_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fm801_pci_probe), DEVMETHOD(device_attach, fm801_pci_attach), DEVMETHOD(device_detach, fm801_pci_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_alloc_resource, fm801_alloc_resource), DEVMETHOD(bus_release_resource, fm801_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD_END }; static driver_t fm801_driver = { "pcm", fm801_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_fm801, pci, fm801_driver, 0, 0); MODULE_DEPEND(snd_fm801, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_fm801, 1); diff --git a/sys/dev/sound/pci/hda/hdaa.c b/sys/dev/sound/pci/hda/hdaa.c index 5dbb5c4f4453..14231107e17a 100644 --- a/sys/dev/sound/pci/hda/hdaa.c +++ b/sys/dev/sound/pci/hda/hdaa.c @@ -1,7174 +1,7174 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 Stephane E. Potvin * Copyright (c) 2006 Ariff Abdullah * Copyright (c) 2008-2012 Alexander Motin * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * Intel High Definition Audio (Audio function) driver for FreeBSD. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" #define hdaa_lock(devinfo) snd_mtxlock((devinfo)->lock) #define hdaa_unlock(devinfo) snd_mtxunlock((devinfo)->lock) #define hdaa_lockassert(devinfo) snd_mtxassert((devinfo)->lock) static const struct { const char *key; uint32_t value; } hdaa_quirks_tab[] = { { "softpcmvol", HDAA_QUIRK_SOFTPCMVOL }, { "fixedrate", HDAA_QUIRK_FIXEDRATE }, { "forcestereo", HDAA_QUIRK_FORCESTEREO }, { "eapdinv", HDAA_QUIRK_EAPDINV }, { "senseinv", HDAA_QUIRK_SENSEINV }, { "ivref50", HDAA_QUIRK_IVREF50 }, { "ivref80", HDAA_QUIRK_IVREF80 }, { "ivref100", HDAA_QUIRK_IVREF100 }, { "ovref50", HDAA_QUIRK_OVREF50 }, { "ovref80", HDAA_QUIRK_OVREF80 }, { "ovref100", HDAA_QUIRK_OVREF100 }, { "ivref", HDAA_QUIRK_IVREF }, { "ovref", HDAA_QUIRK_OVREF }, { "vref", HDAA_QUIRK_VREF }, }; #define HDA_PARSE_MAXDEPTH 10 MALLOC_DEFINE(M_HDAA, "hdaa", "HDA Audio"); static const char *HDA_COLORS[16] = {"Unknown", "Black", "Grey", "Blue", "Green", "Red", "Orange", "Yellow", "Purple", "Pink", "Res.A", "Res.B", "Res.C", "Res.D", "White", "Other"}; static const char *HDA_DEVS[16] = {"Line-out", "Speaker", "Headphones", "CD", "SPDIF-out", "Digital-out", "Modem-line", "Modem-handset", "Line-in", "AUX", "Mic", "Telephony", "SPDIF-in", "Digital-in", "Res.E", "Other"}; static const char *HDA_CONNS[4] = {"Jack", "None", "Fixed", "Both"}; static const char *HDA_CONNECTORS[16] = { "Unknown", "1/8", "1/4", "ATAPI", "RCA", "Optical", "Digital", "Analog", "DIN", "XLR", "RJ-11", "Combo", "0xc", "0xd", "0xe", "Other" }; static const char *HDA_LOCS[64] = { "0x00", "Rear", "Front", "Left", "Right", "Top", "Bottom", "Rear-panel", "Drive-bay", "0x09", "0x0a", "0x0b", "0x0c", "0x0d", "0x0e", "0x0f", "Internal", "0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "Riser", "0x18", "Onboard", "0x1a", "0x1b", "0x1c", "0x1d", "0x1e", "0x1f", "External", "Ext-Rear", "Ext-Front", "Ext-Left", "Ext-Right", "Ext-Top", "Ext-Bottom", "0x07", "0x28", "0x29", "0x2a", "0x2b", "0x2c", "0x2d", "0x2e", "0x2f", "Other", "0x31", "0x32", "0x33", "0x34", "0x35", "Other-Bott", "Lid-In", "Lid-Out", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "0x3f" }; static const char *HDA_GPIO_ACTIONS[8] = { "keep", "set", "clear", "disable", "input", "0x05", "0x06", "0x07"}; static const char *HDA_HDMI_CODING_TYPES[18] = { "undefined", "LPCM", "AC-3", "MPEG1", "MP3", "MPEG2", "AAC-LC", "DTS", "ATRAC", "DSD", "E-AC-3", "DTS-HD", "MLP", "DST", "WMAPro", "HE-AAC", "HE-AACv2", "MPEG-Surround" }; /* Default */ static uint32_t hdaa_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps hdaa_caps = {48000, 48000, hdaa_fmt, 0}; static const struct { uint32_t rate; int valid; uint16_t base; uint16_t mul; uint16_t div; } hda_rate_tab[] = { { 8000, 1, 0x0000, 0x0000, 0x0500 }, /* (48000 * 1) / 6 */ { 9600, 0, 0x0000, 0x0000, 0x0400 }, /* (48000 * 1) / 5 */ { 12000, 0, 0x0000, 0x0000, 0x0300 }, /* (48000 * 1) / 4 */ { 16000, 1, 0x0000, 0x0000, 0x0200 }, /* (48000 * 1) / 3 */ { 18000, 0, 0x0000, 0x1000, 0x0700 }, /* (48000 * 3) / 8 */ { 19200, 0, 0x0000, 0x0800, 0x0400 }, /* (48000 * 2) / 5 */ { 24000, 0, 0x0000, 0x0000, 0x0100 }, /* (48000 * 1) / 2 */ { 28800, 0, 0x0000, 0x1000, 0x0400 }, /* (48000 * 3) / 5 */ { 32000, 1, 0x0000, 0x0800, 0x0200 }, /* (48000 * 2) / 3 */ { 36000, 0, 0x0000, 0x1000, 0x0300 }, /* (48000 * 3) / 4 */ { 38400, 0, 0x0000, 0x1800, 0x0400 }, /* (48000 * 4) / 5 */ { 48000, 1, 0x0000, 0x0000, 0x0000 }, /* (48000 * 1) / 1 */ { 64000, 0, 0x0000, 0x1800, 0x0200 }, /* (48000 * 4) / 3 */ { 72000, 0, 0x0000, 0x1000, 0x0100 }, /* (48000 * 3) / 2 */ { 96000, 1, 0x0000, 0x0800, 0x0000 }, /* (48000 * 2) / 1 */ { 144000, 0, 0x0000, 0x1000, 0x0000 }, /* (48000 * 3) / 1 */ { 192000, 1, 0x0000, 0x1800, 0x0000 }, /* (48000 * 4) / 1 */ { 8820, 0, 0x4000, 0x0000, 0x0400 }, /* (44100 * 1) / 5 */ { 11025, 1, 0x4000, 0x0000, 0x0300 }, /* (44100 * 1) / 4 */ { 12600, 0, 0x4000, 0x0800, 0x0600 }, /* (44100 * 2) / 7 */ { 14700, 0, 0x4000, 0x0000, 0x0200 }, /* (44100 * 1) / 3 */ { 17640, 0, 0x4000, 0x0800, 0x0400 }, /* (44100 * 2) / 5 */ { 18900, 0, 0x4000, 0x1000, 0x0600 }, /* (44100 * 3) / 7 */ { 22050, 1, 0x4000, 0x0000, 0x0100 }, /* (44100 * 1) / 2 */ { 25200, 0, 0x4000, 0x1800, 0x0600 }, /* (44100 * 4) / 7 */ { 26460, 0, 0x4000, 0x1000, 0x0400 }, /* (44100 * 3) / 5 */ { 29400, 0, 0x4000, 0x0800, 0x0200 }, /* (44100 * 2) / 3 */ { 33075, 0, 0x4000, 0x1000, 0x0300 }, /* (44100 * 3) / 4 */ { 35280, 0, 0x4000, 0x1800, 0x0400 }, /* (44100 * 4) / 5 */ { 44100, 1, 0x4000, 0x0000, 0x0000 }, /* (44100 * 1) / 1 */ { 58800, 0, 0x4000, 0x1800, 0x0200 }, /* (44100 * 4) / 3 */ { 66150, 0, 0x4000, 0x1000, 0x0100 }, /* (44100 * 3) / 2 */ { 88200, 1, 0x4000, 0x0800, 0x0000 }, /* (44100 * 2) / 1 */ { 132300, 0, 0x4000, 0x1000, 0x0000 }, /* (44100 * 3) / 1 */ { 176400, 1, 0x4000, 0x1800, 0x0000 }, /* (44100 * 4) / 1 */ }; #define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0])) const static char *ossnames[] = SOUND_DEVICE_NAMES; /**************************************************************************** * Function prototypes ****************************************************************************/ static int hdaa_pcmchannel_setup(struct hdaa_chan *); static void hdaa_widget_connection_select(struct hdaa_widget *, uint8_t); static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *, uint32_t, int, int); static struct hdaa_audio_ctl *hdaa_audio_ctl_amp_get(struct hdaa_devinfo *, nid_t, int, int, int); static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *, nid_t, int, int, int, int, int, int); static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf); static char * hdaa_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len) { int i, first = 1; bzero(buf, len); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mask & (1 << i)) { if (first == 0) strlcat(buf, ", ", len); strlcat(buf, ossnames[i], len); first = 0; } } return (buf); } static struct hdaa_audio_ctl * hdaa_audio_ctl_each(struct hdaa_devinfo *devinfo, int *index) { if (devinfo == NULL || index == NULL || devinfo->ctl == NULL || devinfo->ctlcnt < 1 || *index < 0 || *index >= devinfo->ctlcnt) return (NULL); return (&devinfo->ctl[(*index)++]); } static struct hdaa_audio_ctl * hdaa_audio_ctl_amp_get(struct hdaa_devinfo *devinfo, nid_t nid, int dir, int index, int cnt) { struct hdaa_audio_ctl *ctl; int i, found = 0; if (devinfo == NULL || devinfo->ctl == NULL) return (NULL); i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0) continue; if (ctl->widget->nid != nid) continue; if (dir && ctl->ndir != dir) continue; if (index >= 0 && ctl->ndir == HDAA_CTL_IN && ctl->dir == ctl->ndir && ctl->index != index) continue; found++; if (found == cnt || cnt <= 0) return (ctl); } return (NULL); } static const struct matrix { struct pcmchan_matrix m; int analog; } matrixes[] = { { SND_CHN_MATRIX_MAP_1_0, 1 }, { SND_CHN_MATRIX_MAP_2_0, 1 }, { SND_CHN_MATRIX_MAP_2_1, 0 }, { SND_CHN_MATRIX_MAP_3_0, 0 }, { SND_CHN_MATRIX_MAP_3_1, 0 }, { SND_CHN_MATRIX_MAP_4_0, 1 }, { SND_CHN_MATRIX_MAP_4_1, 0 }, { SND_CHN_MATRIX_MAP_5_0, 0 }, { SND_CHN_MATRIX_MAP_5_1, 1 }, { SND_CHN_MATRIX_MAP_6_0, 0 }, { SND_CHN_MATRIX_MAP_6_1, 0 }, { SND_CHN_MATRIX_MAP_7_0, 0 }, { SND_CHN_MATRIX_MAP_7_1, 1 }, }; static const char *channel_names[] = SND_CHN_T_NAMES; /* * Connected channels change handler. */ static void hdaa_channels_handler(struct hdaa_audio_as *as) { struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_chan *ch = &devinfo->chans[as->chans[0]]; struct hdaa_widget *w; uint8_t *eld; int total, sub, assume, channels; size_t i; uint16_t cpins, upins, tpins; cpins = upins = 0; eld = NULL; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL) continue; if (w->wclass.pin.connected == 1) cpins |= (1 << i); else if (w->wclass.pin.connected != 0) upins |= (1 << i); if (w->eld != NULL && w->eld_len >= 8) eld = w->eld; } tpins = cpins | upins; if (as->hpredir >= 0) tpins &= 0x7fff; if (tpins == 0) tpins = as->pinset; total = sub = assume = channels = 0; if (eld) { /* Map CEA speakers to sound(4) channels. */ if (eld[7] & 0x01) /* Front Left/Right */ channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; if (eld[7] & 0x02) /* Low Frequency Effect */ channels |= SND_CHN_T_MASK_LF; if (eld[7] & 0x04) /* Front Center */ channels |= SND_CHN_T_MASK_FC; if (eld[7] & 0x08) { /* Rear Left/Right */ /* If we have both RLR and RLRC, report RLR as side. */ if (eld[7] & 0x40) /* Rear Left/Right Center */ channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR; else channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; } if (eld[7] & 0x10) /* Rear center */ channels |= SND_CHN_T_MASK_BC; if (eld[7] & 0x20) /* Front Left/Right Center */ channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC; if (eld[7] & 0x40) /* Rear Left/Right Center */ channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; } else if (as->pinset != 0 && (tpins & 0xffe0) == 0) { /* Map UAA speakers to sound(4) channels. */ if (tpins & 0x0001) channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; if (tpins & 0x0002) channels |= SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF; if (tpins & 0x0004) channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; if (tpins & 0x0008) channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC; if (tpins & 0x0010) { /* If there is no back pin, report side as back. */ if ((as->pinset & 0x0004) == 0) channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; else channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR; } } else if (as->mixed) { /* Mixed assoc can be only stereo or theoretically mono. */ if (ch->channels == 1) channels |= SND_CHN_T_MASK_FC; else channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; } if (channels) { /* We have some usable channels info. */ HDA_BOOTVERBOSE( device_printf(pdevinfo->dev, "%s channel set is: ", as->dir == HDAA_CTL_OUT ? "Playback" : "Recording"); for (i = 0; i < SND_CHN_T_MAX; i++) if (channels & (1 << i)) printf("%s, ", channel_names[i]); printf("\n"); ); /* Look for maximal fitting matrix. */ for (i = 0; i < nitems(matrixes); i++) { if (as->pinset != 0 && matrixes[i].analog == 0) continue; if ((matrixes[i].m.mask & ~channels) == 0) { total = matrixes[i].m.channels; sub = matrixes[i].m.ext; } } } if (total == 0) { assume = 1; total = ch->channels; sub = (total == 6 || total == 8) ? 1 : 0; } HDA_BOOTVERBOSE( device_printf(pdevinfo->dev, "%s channel matrix is: %s%d.%d (%s)\n", as->dir == HDAA_CTL_OUT ? "Playback" : "Recording", assume ? "unknown, assuming " : "", total - sub, sub, cpins != 0 ? "connected" : (upins != 0 ? "unknown" : "disconnected")); ); } /* * Headphones redirection change handler. */ static void hdaa_hpredir_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_audio_as *as = &devinfo->as[w->bindas]; struct hdaa_widget *w1; struct hdaa_audio_ctl *ctl; uint32_t val; int j, connected = w->wclass.pin.connected; HDA_BOOTVERBOSE( device_printf((as->pdevinfo && as->pdevinfo->dev) ? as->pdevinfo->dev : devinfo->dev, "Redirect output to: %s\n", connected ? "headphones": "main"); ); /* (Un)Mute headphone pin. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, -1, 1); if (ctl != NULL && ctl->mute) { /* If pin has muter - use it. */ val = connected ? 0 : 1; if (val != ctl->forcemute) { ctl->forcemute = val; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); } } else { /* If there is no muter - disable pin output. */ if (connected) val = w->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w->wclass.pin.ctrl) { w->wclass.pin.ctrl = val; hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid, w->wclass.pin.ctrl)); } } /* (Un)Mute other pins. */ for (j = 0; j < 15; j++) { if (as->pins[j] <= 0) continue; ctl = hdaa_audio_ctl_amp_get(devinfo, as->pins[j], HDAA_CTL_IN, -1, 1); if (ctl != NULL && ctl->mute) { /* If pin has muter - use it. */ val = connected ? 1 : 0; if (val == ctl->forcemute) continue; ctl->forcemute = val; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); continue; } /* If there is no muter - disable pin output. */ w1 = hdaa_widget_get(devinfo, as->pins[j]); if (w1 != NULL) { if (connected) val = w1->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w1->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w1->wclass.pin.ctrl) { w1->wclass.pin.ctrl = val; hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w1->nid, w1->wclass.pin.ctrl)); } } } } /* * Recording source change handler. */ static void hdaa_autorecsrc_handler(struct hdaa_audio_as *as, struct hdaa_widget *w) { struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo; struct hdaa_widget *w1; int i, mask, fullmask, prio, bestprio; char buf[128]; if (!as->mixed || pdevinfo == NULL || pdevinfo->mixer == NULL) return; /* Don't touch anything if we asked not to. */ if (pdevinfo->autorecsrc == 0 || (pdevinfo->autorecsrc == 1 && w != NULL)) return; /* Don't touch anything if "mix" or "speaker" selected. */ if (pdevinfo->recsrc & (SOUND_MASK_IMIX | SOUND_MASK_SPEAKER)) return; /* Don't touch anything if several selected. */ if (ffs(pdevinfo->recsrc) != fls(pdevinfo->recsrc)) return; devinfo = pdevinfo->devinfo; mask = fullmask = 0; bestprio = 0; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w1 = hdaa_widget_get(devinfo, as->pins[i]); if (w1 == NULL || w1->enable == 0) continue; if (w1->wclass.pin.connected == 0) continue; prio = (w1->wclass.pin.connected == 1) ? 2 : 1; if (prio < bestprio) continue; if (prio > bestprio) { mask = 0; bestprio = prio; } mask |= (1 << w1->ossdev); fullmask |= (1 << w1->ossdev); } if (mask == 0) return; /* Prefer newly connected input. */ if (w != NULL && (mask & (1 << w->ossdev))) mask = (1 << w->ossdev); /* Prefer previously selected input */ if (mask & pdevinfo->recsrc) mask &= pdevinfo->recsrc; /* Prefer mic. */ if (mask & SOUND_MASK_MIC) mask = SOUND_MASK_MIC; /* Prefer monitor (2nd mic). */ if (mask & SOUND_MASK_MONITOR) mask = SOUND_MASK_MONITOR; /* Just take first one. */ mask = (1 << (ffs(mask) - 1)); HDA_BOOTVERBOSE( hdaa_audio_ctl_ossmixer_mask2allname(mask, buf, sizeof(buf)); device_printf(pdevinfo->dev, "Automatically set rec source to: %s\n", buf); ); hdaa_unlock(devinfo); mix_setrecsrc(pdevinfo->mixer, mask); hdaa_lock(devinfo); } /* * Jack presence detection event handler. */ static void hdaa_presence_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_audio_as *as, *asp; char buf[32]; uint32_t res; int connected, old, i; bool active; if (w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) return; if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); connected = (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) != 0; if (devinfo->quirks & HDAA_QUIRK_SENSEINV) connected = !connected; old = w->wclass.pin.connected; if (connected == old) return; w->wclass.pin.connected = connected; as = &devinfo->as[w->bindas]; if (as->hpredir >= 0 && as->pins[15] == w->nid) hdaa_hpredir_handler(w); if (as->dir == HDAA_CTL_IN && old != 2) hdaa_autorecsrc_handler(as, w); if (old != 2) hdaa_channels_handler(as); if (connected || old != 2) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Pin sense: nid=%d sense=0x%08x (%sconnected)\n", w->nid, res, !connected ? "dis" : ""); ); if (as->hpredir >= 0) return; for (i = 0, active = false; i < devinfo->num_devs; i++) { if (device_get_unit(devinfo->devs[i].dev) == snd_unit) { active = true; break; } } /* Proceed only if we are currently using this codec. */ if (!active) return; for (i = 0; i < devinfo->ascnt; i++) { asp = &devinfo->as[i]; if (!asp->enable) continue; if ((connected && asp->index == as->index) || (!connected && asp->dir == as->dir)) { snprintf(buf, sizeof(buf), "cdev=dsp%d", device_get_unit(asp->pdevinfo->dev)); devctl_notify("SND", "CONN", asp->dir == HDAA_CTL_IN ? "IN" : "OUT", buf); break; } } } } /* * Callback for poll based presence detection. */ static void hdaa_jack_poll_callback(void *arg) { struct hdaa_devinfo *devinfo = arg; struct hdaa_widget *w; int i; hdaa_lock(devinfo); if (devinfo->poll_ival == 0) { hdaa_unlock(devinfo); return; } for (i = 0; i < devinfo->ascnt; i++) { if (devinfo->as[i].hpredir < 0) continue; w = hdaa_widget_get(devinfo, devinfo->as[i].pins[15]); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_presence_handler(w); } callout_reset(&devinfo->poll_jack, devinfo->poll_ival, hdaa_jack_poll_callback, devinfo); hdaa_unlock(devinfo); } static void hdaa_eld_dump(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; device_t dev = devinfo->dev; uint8_t *sad; int mnl, i, sadc, fmt; if (w->eld == NULL || w->eld_len < 4) return; device_printf(dev, "ELD nid=%d: ELD_Ver=%u Baseline_ELD_Len=%u\n", w->nid, w->eld[0] >> 3, w->eld[2]); if ((w->eld[0] >> 3) != 0x02) return; mnl = w->eld[4] & 0x1f; device_printf(dev, "ELD nid=%d: CEA_EDID_Ver=%u MNL=%u\n", w->nid, w->eld[4] >> 5, mnl); sadc = w->eld[5] >> 4; device_printf(dev, "ELD nid=%d: SAD_Count=%u Conn_Type=%u S_AI=%u HDCP=%u\n", w->nid, sadc, (w->eld[5] >> 2) & 0x3, (w->eld[5] >> 1) & 0x1, w->eld[5] & 0x1); device_printf(dev, "ELD nid=%d: Aud_Synch_Delay=%ums\n", w->nid, w->eld[6] * 2); device_printf(dev, "ELD nid=%d: Channels=0x%b\n", w->nid, w->eld[7], "\020\07RLRC\06FLRC\05RC\04RLR\03FC\02LFE\01FLR"); device_printf(dev, "ELD nid=%d: Port_ID=0x%02x%02x%02x%02x%02x%02x%02x%02x\n", w->nid, w->eld[8], w->eld[9], w->eld[10], w->eld[11], w->eld[12], w->eld[13], w->eld[14], w->eld[15]); device_printf(dev, "ELD nid=%d: Manufacturer_Name=0x%02x%02x\n", w->nid, w->eld[16], w->eld[17]); device_printf(dev, "ELD nid=%d: Product_Code=0x%02x%02x\n", w->nid, w->eld[18], w->eld[19]); device_printf(dev, "ELD nid=%d: Monitor_Name_String='%.*s'\n", w->nid, mnl, &w->eld[20]); for (i = 0; i < sadc; i++) { sad = &w->eld[20 + mnl + i * 3]; fmt = (sad[0] >> 3) & 0x0f; if (fmt == HDA_HDMI_CODING_TYPE_REF_CTX) { fmt = (sad[2] >> 3) & 0x1f; if (fmt < 1 || fmt > 3) fmt = 0; else fmt += 14; } device_printf(dev, "ELD nid=%d: %s %dch freqs=0x%b", w->nid, HDA_HDMI_CODING_TYPES[fmt], (sad[0] & 0x07) + 1, sad[1], "\020\007192\006176\00596\00488\00348\00244\00132"); switch (fmt) { case HDA_HDMI_CODING_TYPE_LPCM: printf(" sizes=0x%b", sad[2] & 0x07, "\020\00324\00220\00116"); break; case HDA_HDMI_CODING_TYPE_AC3: case HDA_HDMI_CODING_TYPE_MPEG1: case HDA_HDMI_CODING_TYPE_MP3: case HDA_HDMI_CODING_TYPE_MPEG2: case HDA_HDMI_CODING_TYPE_AACLC: case HDA_HDMI_CODING_TYPE_DTS: case HDA_HDMI_CODING_TYPE_ATRAC: printf(" max_bitrate=%d", sad[2] * 8000); break; case HDA_HDMI_CODING_TYPE_WMAPRO: printf(" profile=%d", sad[2] & 0x07); break; } printf("\n"); } } static void hdaa_eld_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; uint32_t res; int i; if (w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) return; if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); if ((w->eld != 0) == ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) != 0)) return; if (w->eld != NULL) { w->eld_len = 0; free(w->eld, M_HDAA); w->eld = NULL; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Pin sense: nid=%d sense=0x%08x " "(%sconnected, ELD %svalid)\n", w->nid, res, (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis", (res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) ? "" : "in"); ); if ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) == 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_HDMI_DIP_SIZE(0, w->nid, 0x08)); if (res == HDA_INVALID) return; w->eld_len = res & 0xff; if (w->eld_len != 0) w->eld = malloc(w->eld_len, M_HDAA, M_ZERO | M_NOWAIT); if (w->eld == NULL) { w->eld_len = 0; return; } for (i = 0; i < w->eld_len; i++) { res = hda_command(devinfo->dev, HDA_CMD_GET_HDMI_ELDD(0, w->nid, i)); if (res & 0x80000000) w->eld[i] = res & 0xff; } HDA_BOOTVERBOSE( hdaa_eld_dump(w); ); hdaa_channels_handler(&devinfo->as[w->bindas]); } /* * Pin sense initializer. */ static void hdaa_sense_init(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, poll = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) { if (w->unsol < 0) w->unsol = HDAC_UNSOL_ALLOC( device_get_parent(devinfo->dev), devinfo->dev, w->nid); hda_command(devinfo->dev, HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE | w->unsol)); } as = &devinfo->as[w->bindas]; if (as->hpredir >= 0 && as->pins[15] == w->nid) { if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) { device_printf(devinfo->dev, "No presence detection support at nid %d\n", w->nid); } else { if (w->unsol < 0) poll = 1; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Headphones redirection for " "association %d nid=%d using %s.\n", w->bindas, w->nid, (w->unsol < 0) ? "polling" : "unsolicited responses"); ); } } hdaa_presence_handler(w); if (!HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) && !HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) continue; hdaa_eld_handler(w); } if (poll) { callout_reset(&devinfo->poll_jack, 1, hdaa_jack_poll_callback, devinfo); } } static void hdaa_sense_deinit(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; callout_stop(&devinfo->poll_jack); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->unsol < 0) continue; hda_command(devinfo->dev, HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, 0)); HDAC_UNSOL_FREE( device_get_parent(devinfo->dev), devinfo->dev, w->unsol); w->unsol = -1; } } uint32_t hdaa_widget_pin_patch(uint32_t config, const char *str) { char buf[256]; char *key, *value, *rest, *bad; int ival, i; strlcpy(buf, str, sizeof(buf)); rest = buf; while ((key = strsep(&rest, "=")) != NULL) { value = strsep(&rest, " \t"); if (value == NULL) break; ival = strtol(value, &bad, 10); if (strcmp(key, "seq") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_SEQUENCE_SHIFT) & HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK); } else if (strcmp(key, "as") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_ASSOCIATION_SHIFT) & HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK); } else if (strcmp(key, "misc") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_MISC_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_MISC_SHIFT) & HDA_CONFIG_DEFAULTCONF_MISC_MASK); } else if (strcmp(key, "color") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_COLOR_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT) & HDA_CONFIG_DEFAULTCONF_COLOR_MASK); } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_COLORS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT); break; } } } else if (strcmp(key, "ctype") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT) & HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK); } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_CONNECTORS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT); break; } } } else if (strcmp(key, "device") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT) & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK); continue; } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_DEVS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT); break; } } } else if (strcmp(key, "loc") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_LOCATION_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT) & HDA_CONFIG_DEFAULTCONF_LOCATION_MASK); continue; } for (i = 0; i < 64; i++) { if (strcasecmp(HDA_LOCS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT); break; } } } else if (strcmp(key, "conn") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT) & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); continue; } for (i = 0; i < 4; i++) { if (strcasecmp(HDA_CONNS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT); break; } } } } return (config); } uint32_t hdaa_gpio_patch(uint32_t gpio, const char *str) { char buf[256]; char *key, *value, *rest; int ikey, i; strlcpy(buf, str, sizeof(buf)); rest = buf; while ((key = strsep(&rest, "=")) != NULL) { value = strsep(&rest, " \t"); if (value == NULL) break; ikey = strtol(key, NULL, 10); if (ikey < 0 || ikey > 7) continue; for (i = 0; i < 7; i++) { if (strcasecmp(HDA_GPIO_ACTIONS[i], value) == 0) { gpio &= ~HDAA_GPIO_MASK(ikey); gpio |= i << HDAA_GPIO_SHIFT(ikey); break; } } } return (gpio); } static void hdaa_local_patch_pin(struct hdaa_widget *w) { device_t dev = w->devinfo->dev; const char *res = NULL; uint32_t config, orig; char buf[32]; config = orig = w->wclass.pin.config; snprintf(buf, sizeof(buf), "cad%u.nid%u.config", hda_get_codec_id(dev), w->nid); if (resource_string_value(device_get_name( device_get_parent(device_get_parent(dev))), device_get_unit(device_get_parent(device_get_parent(dev))), buf, &res) == 0) { if (strncmp(res, "0x", 2) == 0) { config = strtol(res + 2, NULL, 16); } else { config = hdaa_widget_pin_patch(config, res); } } snprintf(buf, sizeof(buf), "nid%u.config", w->nid); if (resource_string_value(device_get_name(dev), device_get_unit(dev), buf, &res) == 0) { if (strncmp(res, "0x", 2) == 0) { config = strtol(res + 2, NULL, 16); } else { config = hdaa_widget_pin_patch(config, res); } } HDA_BOOTVERBOSE( if (config != orig) device_printf(w->devinfo->dev, "Patching pin config nid=%u 0x%08x -> 0x%08x\n", w->nid, orig, config); ); w->wclass.pin.newconf = w->wclass.pin.config = config; } static void hdaa_dump_audio_formats_sb(struct sbuf *sb, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { sbuf_printf(sb, " Stream cap: 0x%08x", cap); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) sbuf_printf(sb, " AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) sbuf_printf(sb, " FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) sbuf_printf(sb, " PCM"); sbuf_printf(sb, "\n"); } cap = pcmcap; if (cap != 0) { sbuf_printf(sb, " PCM cap: 0x%08x", cap); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) sbuf_printf(sb, " 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) sbuf_printf(sb, " 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) sbuf_printf(sb, " 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) sbuf_printf(sb, " 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) sbuf_printf(sb, " 32"); sbuf_printf(sb, " bits,"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) sbuf_printf(sb, " 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) sbuf_printf(sb, " 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) sbuf_printf(sb, " 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) sbuf_printf(sb, " 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) sbuf_printf(sb, " 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) sbuf_printf(sb, " 44"); sbuf_printf(sb, " 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) sbuf_printf(sb, " 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) sbuf_printf(sb, " 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) sbuf_printf(sb, " 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) sbuf_printf(sb, " 192"); sbuf_printf(sb, " KHz\n"); } } static void hdaa_dump_pin_sb(struct sbuf *sb, struct hdaa_widget *w) { uint32_t pincap, conf; pincap = w->wclass.pin.cap; sbuf_printf(sb, " Pin cap: 0x%08x", pincap); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) sbuf_printf(sb, " ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) sbuf_printf(sb, " TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) sbuf_printf(sb, " PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) sbuf_printf(sb, " HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) sbuf_printf(sb, " OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) sbuf_printf(sb, " IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) sbuf_printf(sb, " BAL"); if (HDA_PARAM_PIN_CAP_HDMI(pincap)) sbuf_printf(sb, " HDMI"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { sbuf_printf(sb, " VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) sbuf_printf(sb, " 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) sbuf_printf(sb, " 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) sbuf_printf(sb, " 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) sbuf_printf(sb, " GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) sbuf_printf(sb, " HIZ"); sbuf_printf(sb, " ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) sbuf_printf(sb, " EAPD"); if (HDA_PARAM_PIN_CAP_DP(pincap)) sbuf_printf(sb, " DP"); if (HDA_PARAM_PIN_CAP_HBR(pincap)) sbuf_printf(sb, " HBR"); sbuf_printf(sb, "\n"); conf = w->wclass.pin.config; sbuf_printf(sb, " Pin config: 0x%08x", conf); sbuf_printf(sb, " as=%d seq=%d " "device=%s conn=%s ctype=%s loc=%s color=%s misc=%d\n", HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf)); sbuf_printf(sb, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) sbuf_printf(sb, " HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) sbuf_printf(sb, " IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) sbuf_printf(sb, " OUT"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03) sbuf_printf(sb, " HBR"); else if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) sbuf_printf(sb, " EPTs"); } else { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) sbuf_printf(sb, " VREFs"); } sbuf_printf(sb, "\n"); } static void hdaa_dump_amp_sb(struct sbuf *sb, uint32_t cap, const char *banner) { int offset, size, step; offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap); sbuf_printf(sb, " %s amp: 0x%08x " "mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n", banner, cap, HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), step, size, offset, ((0 - offset) * (size + 1)) / 4, ((step - offset) * (size + 1)) / 4); } static int hdaa_sysctl_caps(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo; struct hdaa_widget *w, *cw; struct sbuf sb; char buf[64]; int error, j; w = (struct hdaa_widget *)oidp->oid_arg1; devinfo = w->devinfo; sbuf_new_for_sysctl(&sb, NULL, 256, req); sbuf_printf(&sb, "%s%s\n", w->name, (w->enable == 0) ? " [DISABLED]" : ""); sbuf_printf(&sb, " Widget cap: 0x%08x", w->param.widget_cap); if (w->param.widget_cap & 0x0ee1) { if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap)) sbuf_printf(&sb, " LRSWAP"); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap)) sbuf_printf(&sb, " PWR"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) sbuf_printf(&sb, " DIGITAL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) sbuf_printf(&sb, " UNSOL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) sbuf_printf(&sb, " PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) sbuf_printf(&sb, " STRIPE(x%d)", 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) sbuf_printf(&sb, " STEREO"); else if (j > 1) sbuf_printf(&sb, " %dCH", j + 1); } sbuf_printf(&sb, "\n"); if (w->bindas != -1) { sbuf_printf(&sb, " Association: %d (0x%04x)\n", w->bindas, w->bindseqmask); } if (w->ossmask != 0 || w->ossdev >= 0) { sbuf_printf(&sb, " OSS: %s", hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) sbuf_printf(&sb, " (%s)", ossnames[w->ossdev]); sbuf_printf(&sb, "\n"); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_dump_audio_formats_sb(&sb, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) hdaa_dump_pin_sb(&sb, w); if (w->param.eapdbtl != HDA_INVALID) { sbuf_printf(&sb, " EAPD: 0x%08x%s%s%s\n", w->param.eapdbtl, (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_LR_SWAP) ? " LRSWAP" : "", (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD) ? " EAPD" : "", (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_BTL) ? " BTL" : ""); } if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdaa_dump_amp_sb(&sb, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdaa_dump_amp_sb(&sb, w->param.inamp_cap, " Input"); if (w->nconns > 0) sbuf_printf(&sb, " Connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); sbuf_printf(&sb, " + %s<- nid=%d [%s]", (w->connsenable[j] == 0)?"[DISABLED] ":"", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) sbuf_printf(&sb, " [UNKNOWN]"); else if (cw->enable == 0) sbuf_printf(&sb, " [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) sbuf_printf(&sb, " (selected)"); sbuf_printf(&sb, "\n"); } error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } static int hdaa_sysctl_config(SYSCTL_HANDLER_ARGS) { char buf[256]; int error; uint32_t conf; conf = *(uint32_t *)oidp->oid_arg1; snprintf(buf, sizeof(buf), "0x%08x as=%d seq=%d " "device=%s conn=%s ctype=%s loc=%s color=%s misc=%d", conf, HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) conf = strtol(buf + 2, NULL, 16); else conf = hdaa_widget_pin_patch(conf, buf); *(uint32_t *)oidp->oid_arg1 = conf; return (0); } static void hdaa_config_fetch(const char *str, uint32_t *on, uint32_t *off) { size_t k; int i = 0, j, len, inv; for (;;) { while (str[i] != '\0' && (str[i] == ',' || isspace(str[i]) != 0)) i++; if (str[i] == '\0') return; j = i; while (str[j] != '\0' && !(str[j] == ',' || isspace(str[j]) != 0)) j++; len = j - i; if (len > 2 && strncmp(str + i, "no", 2) == 0) inv = 2; else inv = 0; for (k = 0; len > inv && k < nitems(hdaa_quirks_tab); k++) { if (strncmp(str + i + inv, hdaa_quirks_tab[k].key, len - inv) != 0) continue; if (len - inv != strlen(hdaa_quirks_tab[k].key)) continue; if (inv == 0) { *on |= hdaa_quirks_tab[k].value; *off &= ~hdaa_quirks_tab[k].value; } else { *off |= hdaa_quirks_tab[k].value; *on &= ~hdaa_quirks_tab[k].value; } break; } i = j; } } static int hdaa_sysctl_quirks(SYSCTL_HANDLER_ARGS) { char buf[256]; int error, n = 0; size_t i; uint32_t quirks, quirks_off; quirks = *(uint32_t *)oidp->oid_arg1; buf[0] = 0; for (i = 0; i < nitems(hdaa_quirks_tab); i++) { if ((quirks & hdaa_quirks_tab[i].value) != 0) n += snprintf(buf + n, sizeof(buf) - n, "%s%s", n != 0 ? "," : "", hdaa_quirks_tab[i].key); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) quirks = strtol(buf + 2, NULL, 16); else { quirks = 0; hdaa_config_fetch(buf, &quirks, &quirks_off); } *(uint32_t *)oidp->oid_arg1 = quirks; return (0); } static void hdaa_local_patch(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; const char *res = NULL; uint32_t quirks_on = 0, quirks_off = 0, x; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) hdaa_local_patch_pin(w); } if (resource_string_value(device_get_name(devinfo->dev), device_get_unit(devinfo->dev), "config", &res) == 0) { if (res != NULL && strlen(res) > 0) hdaa_config_fetch(res, &quirks_on, &quirks_off); devinfo->quirks |= quirks_on; devinfo->quirks &= ~quirks_off; } if (devinfo->newquirks == -1) devinfo->newquirks = devinfo->quirks; else devinfo->quirks = devinfo->newquirks; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "Config options: 0x%08x\n", devinfo->quirks); ); if (resource_string_value(device_get_name(devinfo->dev), device_get_unit(devinfo->dev), "gpio_config", &res) == 0) { if (strncmp(res, "0x", 2) == 0) { devinfo->gpio = strtol(res + 2, NULL, 16); } else { devinfo->gpio = hdaa_gpio_patch(devinfo->gpio, res); } } if (devinfo->newgpio == -1) devinfo->newgpio = devinfo->gpio; else devinfo->gpio = devinfo->newgpio; if (devinfo->newgpo == -1) devinfo->newgpo = devinfo->gpo; else devinfo->gpo = devinfo->newgpo; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "GPIO config options:"); for (i = 0; i < 7; i++) { x = (devinfo->gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); if (x != 0) printf(" %d=%s", i, HDA_GPIO_ACTIONS[x]); } printf("\n"); ); } static void hdaa_widget_connection_parse(struct hdaa_widget *w) { uint32_t res; int i, j, max, ents, entnum; nid_t nid = w->nid; nid_t cnid, addcnid, prevcnid; w->nconns = 0; res = hda_command(w->devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_CONN_LIST_LENGTH)); ents = HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(res); if (ents < 1) return; entnum = HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(res) ? 2 : 4; max = (sizeof(w->conns) / sizeof(w->conns[0])) - 1; prevcnid = 0; #define CONN_RMASK(e) (1 << ((32 / (e)) - 1)) #define CONN_NMASK(e) (CONN_RMASK(e) - 1) #define CONN_RESVAL(r, e, n) ((r) >> ((32 / (e)) * (n))) #define CONN_RANGE(r, e, n) (CONN_RESVAL(r, e, n) & CONN_RMASK(e)) #define CONN_CNID(r, e, n) (CONN_RESVAL(r, e, n) & CONN_NMASK(e)) for (i = 0; i < ents; i += entnum) { res = hda_command(w->devinfo->dev, HDA_CMD_GET_CONN_LIST_ENTRY(0, nid, i)); for (j = 0; j < entnum; j++) { cnid = CONN_CNID(res, entnum, j); if (cnid == 0) { if (w->nconns < ents) device_printf(w->devinfo->dev, "WARNING: nid=%d has zero cnid " "entnum=%d j=%d index=%d " "entries=%d found=%d res=0x%08x\n", nid, entnum, j, i, ents, w->nconns, res); else goto getconns_out; } if (cnid < w->devinfo->startnode || cnid >= w->devinfo->endnode) { HDA_BOOTVERBOSE( device_printf(w->devinfo->dev, "WARNING: nid=%d has cnid outside " "of the AFG range j=%d " "entnum=%d index=%d res=0x%08x\n", nid, j, entnum, i, res); ); } if (CONN_RANGE(res, entnum, j) == 0) addcnid = cnid; else if (prevcnid == 0 || prevcnid >= cnid) { device_printf(w->devinfo->dev, "WARNING: Invalid child range " "nid=%d index=%d j=%d entnum=%d " "prevcnid=%d cnid=%d res=0x%08x\n", nid, i, j, entnum, prevcnid, cnid, res); addcnid = cnid; } else addcnid = prevcnid + 1; while (addcnid <= cnid) { if (w->nconns > max) { device_printf(w->devinfo->dev, "Adding %d (nid=%d): " "Max connection reached! max=%d\n", addcnid, nid, max + 1); goto getconns_out; } w->connsenable[w->nconns] = 1; w->conns[w->nconns++] = addcnid++; } prevcnid = cnid; } } getconns_out: return; } static void hdaa_widget_parse(struct hdaa_widget *w) { device_t dev = w->devinfo->dev; uint32_t wcap, cap; nid_t nid = w->nid; char buf[64]; w->param.widget_cap = wcap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_AUDIO_WIDGET_CAP)); w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(wcap); hdaa_widget_connection_parse(w); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.outamp_cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP)); else w->param.outamp_cap = w->devinfo->outamp_cap; } else w->param.outamp_cap = 0; if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.inamp_cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP)); else w->param.inamp_cap = w->devinfo->inamp_cap; } else w->param.inamp_cap = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { if (HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(wcap)) { cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS)); w->param.supp_stream_formats = (cap != 0) ? cap : w->devinfo->supp_stream_formats; cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE)); w->param.supp_pcm_size_rate = (cap != 0) ? cap : w->devinfo->supp_pcm_size_rate; } else { w->param.supp_stream_formats = w->devinfo->supp_stream_formats; w->param.supp_pcm_size_rate = w->devinfo->supp_pcm_size_rate; } if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { w->wclass.conv.stripecap = hda_command(dev, HDA_CMD_GET_STRIPE_CONTROL(0, w->nid)) >> 20; } else w->wclass.conv.stripecap = 1; } else { w->param.supp_stream_formats = 0; w->param.supp_pcm_size_rate = 0; } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { w->wclass.pin.original = w->wclass.pin.newconf = w->wclass.pin.config = hda_command(dev, HDA_CMD_GET_CONFIGURATION_DEFAULT(0, w->nid)); w->wclass.pin.cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, w->nid, HDA_PARAM_PIN_CAP)); w->wclass.pin.ctrl = hda_command(dev, HDA_CMD_GET_PIN_WIDGET_CTRL(0, nid)); w->wclass.pin.connected = 2; if (HDA_PARAM_PIN_CAP_EAPD_CAP(w->wclass.pin.cap)) { w->param.eapdbtl = hda_command(dev, HDA_CMD_GET_EAPD_BTL_ENABLE(0, nid)); w->param.eapdbtl &= 0x7; w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; } else w->param.eapdbtl = HDA_INVALID; } w->unsol = -1; hdaa_unlock(w->devinfo); snprintf(buf, sizeof(buf), "nid%d", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, w, 0, hdaa_sysctl_caps, "A", "Node capabilities"); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { snprintf(buf, sizeof(buf), "nid%d_config", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, &w->wclass.pin.newconf, 0, hdaa_sysctl_config, "A", "Current pin configuration"); snprintf(buf, sizeof(buf), "nid%d_original", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, &w->wclass.pin.original, 0, hdaa_sysctl_config, "A", "Original pin configuration"); } hdaa_lock(w->devinfo); } static void hdaa_widget_postprocess(struct hdaa_widget *w) { const char *typestr; w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(w->param.widget_cap); switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: typestr = "audio output"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: typestr = "audio input"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: typestr = "audio mixer"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: typestr = "audio selector"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: typestr = "pin"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET: typestr = "power widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET: typestr = "volume widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: typestr = "beep widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET: typestr = "vendor widget"; break; default: typestr = "unknown type"; break; } strlcpy(w->name, typestr, sizeof(w->name)); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { uint32_t config; const char *devstr; int conn, color; config = w->wclass.pin.config; devstr = HDA_DEVS[(config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) >> HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT]; conn = (config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) >> HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT; color = (config & HDA_CONFIG_DEFAULTCONF_COLOR_MASK) >> HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT; strlcat(w->name, ": ", sizeof(w->name)); strlcat(w->name, devstr, sizeof(w->name)); strlcat(w->name, " (", sizeof(w->name)); if (conn == 0 && color != 0 && color != 15) { strlcat(w->name, HDA_COLORS[color], sizeof(w->name)); strlcat(w->name, " ", sizeof(w->name)); } strlcat(w->name, HDA_CONNS[conn], sizeof(w->name)); strlcat(w->name, ")", sizeof(w->name)); } } struct hdaa_widget * hdaa_widget_get(struct hdaa_devinfo *devinfo, nid_t nid) { if (devinfo == NULL || devinfo->widget == NULL || nid < devinfo->startnode || nid >= devinfo->endnode) return (NULL); return (&devinfo->widget[nid - devinfo->startnode]); } static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *devinfo, nid_t nid, int index, int lmute, int rmute, int left, int right, int dir) { uint16_t v = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "Setting amplifier nid=%d index=%d %s mute=%d/%d vol=%d/%d\n", nid,index,dir ? "in" : "out",lmute,rmute,left,right); ); if (left != right || lmute != rmute) { v = (1 << (15 - dir)) | (1 << 13) | (index << 8) | (lmute << 7) | left; hda_command(devinfo->dev, HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v)); v = (1 << (15 - dir)) | (1 << 12) | (index << 8) | (rmute << 7) | right; } else v = (1 << (15 - dir)) | (3 << 12) | (index << 8) | (lmute << 7) | left; hda_command(devinfo->dev, HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v)); } static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *ctl, uint32_t mute, int left, int right) { nid_t nid; int lmute, rmute; nid = ctl->widget->nid; /* Save new values if valid. */ if (mute != HDAA_AMP_MUTE_DEFAULT) ctl->muted = mute; if (left != HDAA_AMP_VOL_DEFAULT) ctl->left = left; if (right != HDAA_AMP_VOL_DEFAULT) ctl->right = right; /* Prepare effective values */ if (ctl->forcemute) { lmute = 1; rmute = 1; left = 0; right = 0; } else { lmute = HDAA_AMP_LEFT_MUTED(ctl->muted); rmute = HDAA_AMP_RIGHT_MUTED(ctl->muted); left = ctl->left; right = ctl->right; } /* Apply effective values */ if (ctl->dir & HDAA_CTL_OUT) hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index, lmute, rmute, left, right, 0); if (ctl->dir & HDAA_CTL_IN) hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index, lmute, rmute, left, right, 1); } static void hdaa_widget_connection_select(struct hdaa_widget *w, uint8_t index) { if (w == NULL || w->nconns < 1 || index > (w->nconns - 1)) return; HDA_BOOTHVERBOSE( device_printf(w->devinfo->dev, "Setting selector nid=%d index=%d\n", w->nid, index); ); hda_command(w->devinfo->dev, HDA_CMD_SET_CONNECTION_SELECT_CONTROL(0, w->nid, index)); w->selconn = index; } /**************************************************************************** * Device Methods ****************************************************************************/ static void * hdaa_channel_init(kobj_t obj, void *data, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct hdaa_chan *ch = data; struct hdaa_pcm_devinfo *pdevinfo = ch->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; hdaa_lock(devinfo); if (devinfo->quirks & HDAA_QUIRK_FIXEDRATE) { ch->caps.minspeed = ch->caps.maxspeed = 48000; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; } ch->dir = dir; ch->b = b; ch->c = c; ch->blksz = pdevinfo->chan_size / pdevinfo->chan_blkcnt; ch->blkcnt = pdevinfo->chan_blkcnt; hdaa_unlock(devinfo); if (sndbuf_alloc(ch->b, bus_get_dma_tag(devinfo->dev), hda_get_dma_nocache(devinfo->dev) ? BUS_DMA_NOCACHE : BUS_DMA_COHERENT, pdevinfo->chan_size) != 0) return (NULL); return (ch); } static int hdaa_channel_setformat(kobj_t obj, void *data, uint32_t format) { struct hdaa_chan *ch = data; int i; for (i = 0; ch->caps.fmtlist[i] != 0; i++) { if (format == ch->caps.fmtlist[i]) { ch->fmt = format; return (0); } } return (EINVAL); } static uint32_t hdaa_channel_setspeed(kobj_t obj, void *data, uint32_t speed) { struct hdaa_chan *ch = data; uint32_t spd = 0, threshold; int i; /* First look for equal or multiple frequency. */ for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; if (speed != 0 && spd / speed * speed == spd) { ch->spd = spd; return (spd); } } /* If no match, just find nearest. */ for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; threshold = spd + ((ch->pcmrates[i + 1] != 0) ? ((ch->pcmrates[i + 1] - spd) >> 1) : 0); if (speed < threshold) break; } ch->spd = spd; return (spd); } static uint16_t hdaa_stream_format(struct hdaa_chan *ch) { int i; uint16_t fmt; fmt = 0; if (ch->fmt & AFMT_S16_LE) fmt |= ch->bit16 << 4; else if (ch->fmt & AFMT_S32_LE) fmt |= ch->bit32 << 4; else fmt |= 1 << 4; for (i = 0; i < HDA_RATE_TAB_LEN; i++) { if (hda_rate_tab[i].valid && ch->spd == hda_rate_tab[i].rate) { fmt |= hda_rate_tab[i].base; fmt |= hda_rate_tab[i].mul; fmt |= hda_rate_tab[i].div; break; } } fmt |= (AFMT_CHANNEL(ch->fmt) - 1); return (fmt); } static int hdaa_allowed_stripes(uint16_t fmt) { static const int bits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 }; int size; size = bits[(fmt >> 4) & 0x03]; size *= (fmt & 0x0f) + 1; size *= ((fmt >> 11) & 0x07) + 1; return (0xffffffffU >> (32 - fls(size / 8))); } static void hdaa_audio_setup(struct hdaa_chan *ch) { struct hdaa_audio_as *as = &ch->devinfo->as[ch->as]; struct hdaa_widget *w, *wp; int i, j, k, chn, cchn, totalchn, totalextchn, c; uint16_t fmt, dfmt; /* Mapping channel pairs to codec pins/converters. */ const static uint16_t convmap[2][5] = /* 1.0 2.0 4.0 5.1 7.1 */ {{ 0x0010, 0x0001, 0x0201, 0x0231, 0x4231 }, /* no dup. */ { 0x0010, 0x0001, 0x2201, 0x2231, 0x4231 }}; /* side dup. */ /* Mapping formats to HDMI channel allocations. */ const static uint8_t hdmica[2][8] = /* 1 2 3 4 5 6 7 8 */ {{ 0x02, 0x00, 0x04, 0x08, 0x0a, 0x0e, 0x12, 0x12 }, /* x.0 */ { 0x01, 0x03, 0x01, 0x03, 0x09, 0x0b, 0x0f, 0x13 }}; /* x.1 */ /* Mapping formats to HDMI channels order. */ const static uint32_t hdmich[2][8] = /* 1 / 5 2 / 6 3 / 7 4 / 8 */ {{ 0xFFFF0F00, 0xFFFFFF10, 0xFFF2FF10, 0xFF32FF10, 0xFF324F10, 0xF5324F10, 0x54326F10, 0x54326F10 }, /* x.0 */ { 0xFFFFF000, 0xFFFF0100, 0xFFFFF210, 0xFFFF2310, 0xFF32F410, 0xFF324510, 0xF6324510, 0x76325410 }}; /* x.1 */ int convmapid = -1; nid_t nid; uint8_t csum; totalchn = AFMT_CHANNEL(ch->fmt); totalextchn = AFMT_EXTCHANNEL(ch->fmt); HDA_BOOTHVERBOSE( device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup fmt=%08x (%d.%d) speed=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->fmt, totalchn - totalextchn, totalextchn, ch->spd); ); fmt = hdaa_stream_format(ch); /* Set channels to I/O converters mapping for known speaker setups. */ if ((as->pinset == 0x0007 || as->pinset == 0x0013) || /* Standard 5.1 */ (as->pinset == 0x0017)) /* Standard 7.1 */ convmapid = (ch->dir == PCMDIR_PLAY); dfmt = HDA_CMD_SET_DIGITAL_CONV_FMT1_DIGEN; if (ch->fmt & AFMT_AC3) dfmt |= HDA_CMD_SET_DIGITAL_CONV_FMT1_NAUDIO; chn = 0; for (i = 0; ch->io[i] != -1; i++) { w = hdaa_widget_get(ch->devinfo, ch->io[i]); if (w == NULL) continue; /* If HP redirection is enabled, but failed to use same DAC, make last DAC to duplicate first one. */ if (as->fakeredir && i == (as->pincnt - 1)) { c = (ch->sid << 4); } else { /* Map channels to I/O converters, if set. */ if (convmapid >= 0) chn = (((convmap[convmapid][totalchn / 2] >> i * 4) & 0xf) - 1) * 2; if (chn < 0 || chn >= totalchn) { c = 0; } else { c = (ch->sid << 4) | chn; } } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_FMT(0, ch->io[i], fmt)); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { hda_command(ch->devinfo->dev, HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], dfmt)); } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], c)); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { hda_command(ch->devinfo->dev, HDA_CMD_SET_STRIPE_CONTROL(0, w->nid, ch->stripectl)); } cchn = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (cchn > 1 && chn < totalchn) { cchn = min(cchn, totalchn - chn - 1); hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_CHAN_COUNT(0, ch->io[i], cchn)); } HDA_BOOTHVERBOSE( device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup nid=%d: " "fmt=0x%04x, dfmt=0x%04x, chan=0x%04x, " "chan_count=0x%02x, stripe=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->io[i], fmt, dfmt, c, cchn, ch->stripectl); ); for (j = 0; j < 16; j++) { if (as->dacs[ch->asindex][j] != ch->io[i]) continue; nid = as->pins[j]; wp = hdaa_widget_get(ch->devinfo, nid); if (wp == NULL) continue; if (!HDA_PARAM_PIN_CAP_DP(wp->wclass.pin.cap) && !HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap)) continue; /* Set channel mapping. */ for (k = 0; k < 8; k++) { hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_CHAN_SLOT(0, nid, (((hdmich[totalextchn == 0 ? 0 : 1][totalchn - 1] >> (k * 4)) & 0xf) << 4) | k)); } /* * Enable High Bit Rate (HBR) Encoded Packet Type * (EPT), if supported and needed (8ch data). */ if (HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap) && HDA_PARAM_PIN_CAP_HBR(wp->wclass.pin.cap)) { wp->wclass.pin.ctrl &= ~HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK; if ((ch->fmt & AFMT_AC3) && (cchn == 7)) wp->wclass.pin.ctrl |= 0x03; hda_command(ch->devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, nid, wp->wclass.pin.ctrl)); } /* Stop audio infoframe transmission. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0x00)); /* Clear audio infoframe buffer. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); for (k = 0; k < 32; k++) hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); /* Write HDMI/DisplayPort audio infoframe. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); if (w->eld != NULL && w->eld_len >= 6 && ((w->eld[5] >> 2) & 0x3) == 1) { /* DisplayPort */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x1b)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x44)); } else { /* HDMI */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x01)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x0a)); csum = 0; csum -= 0x84 + 0x01 + 0x0a + (totalchn - 1) + hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1]; hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, csum)); } hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, totalchn - 1)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1])); /* Start audio infoframe transmission. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0xc0)); } chn += cchn + 1; } } /* * Greatest Common Divisor. */ static unsigned gcd(unsigned a, unsigned b) { u_int c; while (b != 0) { c = a; a = b; b = (c % b); } return (a); } /* * Least Common Multiple. */ static unsigned lcm(unsigned a, unsigned b) { return ((a * b) / gcd(a, b)); } static int hdaa_channel_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct hdaa_chan *ch = data; - blksz -= blksz % lcm(HDA_DMA_ALIGNMENT, sndbuf_getalign(ch->b)); + blksz -= blksz % lcm(HDA_DMA_ALIGNMENT, ch->b->align); - if (blksz > (sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN)) - blksz = sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN; + if (blksz > (ch->b->maxsize / HDA_BDL_MIN)) + blksz = ch->b->maxsize / HDA_BDL_MIN; if (blksz < HDA_BLK_MIN) blksz = HDA_BLK_MIN; if (blkcnt > HDA_BDL_MAX) blkcnt = HDA_BDL_MAX; if (blkcnt < HDA_BDL_MIN) blkcnt = HDA_BDL_MIN; - while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->b)) { + while ((blksz * blkcnt) > ch->b->maxsize) { if ((blkcnt >> 1) >= HDA_BDL_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= HDA_BLK_MIN) blksz >>= 1; else break; } - if ((sndbuf_getblksz(ch->b) != blksz || - sndbuf_getblkcnt(ch->b) != blkcnt) && + if ((ch->b->blksz != blksz || + ch->b->blkcnt != blkcnt) && sndbuf_resize(ch->b, blkcnt, blksz) != 0) device_printf(ch->devinfo->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); - ch->blksz = sndbuf_getblksz(ch->b); - ch->blkcnt = sndbuf_getblkcnt(ch->b); + ch->blksz = ch->b->blksz; + ch->blkcnt = ch->b->blkcnt; return (0); } static uint32_t hdaa_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct hdaa_chan *ch = data; hdaa_channel_setfragments(obj, data, blksz, ch->pdevinfo->chan_blkcnt); return (ch->blksz); } static void hdaa_channel_stop(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; struct hdaa_widget *w; int i; if ((ch->flags & HDAA_CHN_RUNNING) == 0) return; ch->flags &= ~HDAA_CHN_RUNNING; HDAC_STREAM_STOP(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); for (i = 0; ch->io[i] != -1; i++) { w = hdaa_widget_get(ch->devinfo, ch->io[i]); if (w == NULL) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { hda_command(devinfo->dev, HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], 0)); } hda_command(devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], 0)); } HDAC_STREAM_FREE(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); } static int hdaa_channel_start(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; uint32_t fmt; fmt = hdaa_stream_format(ch); ch->stripectl = fls(ch->stripecap & hdaa_allowed_stripes(fmt) & hda_get_stripes_mask(devinfo->dev)) - 1; ch->sid = HDAC_STREAM_ALLOC(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, fmt, ch->stripectl, &ch->dmapos); if (ch->sid <= 0) return (EBUSY); hdaa_audio_setup(ch); HDAC_STREAM_RESET(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); HDAC_STREAM_START(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid, - sndbuf_getbufaddr(ch->b), ch->blksz, ch->blkcnt); + ch->b->buf_addr, ch->blksz, ch->blkcnt); ch->flags |= HDAA_CHN_RUNNING; return (0); } static int hdaa_channel_trigger(kobj_t obj, void *data, int go) { struct hdaa_chan *ch = data; int error = 0; if (!PCMTRIG_COMMON(go)) return (0); hdaa_lock(ch->devinfo); switch (go) { case PCMTRIG_START: error = hdaa_channel_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: hdaa_channel_stop(ch); break; default: break; } hdaa_unlock(ch->devinfo); return (error); } static uint32_t hdaa_channel_getptr(kobj_t obj, void *data) { struct hdaa_chan *ch = data; struct hdaa_devinfo *devinfo = ch->devinfo; uint32_t ptr; hdaa_lock(devinfo); if (ch->dmapos != NULL) { ptr = *(ch->dmapos); } else { ptr = HDAC_STREAM_GETPTR( device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); } hdaa_unlock(devinfo); /* * Round to available space and force 128 bytes alignment. */ ptr %= ch->blksz * ch->blkcnt; ptr &= HDA_BLK_ALIGN; return (ptr); } static struct pcmchan_caps * hdaa_channel_getcaps(kobj_t obj, void *data) { return (&((struct hdaa_chan *)data)->caps); } static kobj_method_t hdaa_channel_methods[] = { KOBJMETHOD(channel_init, hdaa_channel_init), KOBJMETHOD(channel_setformat, hdaa_channel_setformat), KOBJMETHOD(channel_setspeed, hdaa_channel_setspeed), KOBJMETHOD(channel_setblocksize, hdaa_channel_setblocksize), KOBJMETHOD(channel_setfragments, hdaa_channel_setfragments), KOBJMETHOD(channel_trigger, hdaa_channel_trigger), KOBJMETHOD(channel_getptr, hdaa_channel_getptr), KOBJMETHOD(channel_getcaps, hdaa_channel_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdaa_channel); static int hdaa_audio_ctl_ossmixer_init(struct snd_mixer *m) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; uint32_t mask, recmask; int i, j; hdaa_lock(devinfo); pdevinfo->mixer = m; /* Make sure that in case of soft volume it won't stay muted. */ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { pdevinfo->left[i] = 100; pdevinfo->right[i] = 100; } /* Declare volume controls assigned to this association. */ mask = pdevinfo->ossmask; if (pdevinfo->playas >= 0) { /* Declate EAPD as ogain control. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDA_INVALID || w->bindas != pdevinfo->playas) continue; mask |= SOUND_MASK_OGAIN; break; } /* Declare soft PCM volume if needed. */ if ((mask & SOUND_MASK_PCM) == 0 || (devinfo->quirks & HDAA_QUIRK_SOFTPCMVOL) || pdevinfo->minamp[SOUND_MIXER_PCM] == pdevinfo->maxamp[SOUND_MIXER_PCM]) { mask |= SOUND_MASK_PCM; pcm_setflags(pdevinfo->dev, pcm_getflags(pdevinfo->dev) | SD_F_SOFTPCMVOL); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Forcing Soft PCM volume\n"); ); } /* Declare master volume if needed. */ if ((mask & SOUND_MASK_VOLUME) == 0) { mask |= SOUND_MASK_VOLUME; mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Forcing master volume with PCM\n"); ); } } /* Declare record sources available to this association. */ recmask = 0; if (pdevinfo->recas >= 0) { for (i = 0; i < 16; i++) { if (devinfo->as[pdevinfo->recas].dacs[0][i] < 0) continue; w = hdaa_widget_get(devinfo, devinfo->as[pdevinfo->recas].dacs[0][i]); if (w == NULL || w->enable == 0) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas != pdevinfo->recas && cw->bindas != -2) continue; recmask |= cw->ossmask; } } } recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1; mask &= (1 << SOUND_MIXER_NRDEVICES) - 1; pdevinfo->ossmask = mask; mix_setrecdevs(m, recmask); mix_setdevs(m, mask); hdaa_unlock(devinfo); return (0); } /* * Update amplification per pdevinfo per ossdev, calculate summary coefficient * and write it to codec, update *left and *right to reflect remaining error. */ static void hdaa_audio_ctl_dev_set(struct hdaa_audio_ctl *ctl, int ossdev, int mute, int *left, int *right) { int i, zleft, zright, sleft, sright, smute, lval, rval; ctl->devleft[ossdev] = *left; ctl->devright[ossdev] = *right; ctl->devmute[ossdev] = mute; smute = sleft = sright = zleft = zright = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { sleft += ctl->devleft[i]; sright += ctl->devright[i]; smute |= ctl->devmute[i]; if (i == ossdev) continue; zleft += ctl->devleft[i]; zright += ctl->devright[i]; } lval = QDB2VAL(ctl, sleft); rval = QDB2VAL(ctl, sright); hdaa_audio_ctl_amp_set(ctl, smute, lval, rval); *left -= VAL2QDB(ctl, lval) - VAL2QDB(ctl, QDB2VAL(ctl, zleft)); *right -= VAL2QDB(ctl, rval) - VAL2QDB(ctl, QDB2VAL(ctl, zright)); } /* * Trace signal from source, setting volumes on the way. */ static void hdaa_audio_ctl_source_volume(struct hdaa_pcm_devinfo *pdevinfo, int ossdev, nid_t nid, int index, int mute, int left, int right, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, conns = 0; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; conns++; } } /* If this is not a first step - use input mixer. Pins have common input ctl so care must be taken. */ if (depth > 0 && (conns == 1 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); } /* If widget has own ossdev - not traverse it. It will be traversed on its own. */ if (w->ossdev >= 0 && depth > 0) return; /* We must not traverse pin */ if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && depth > 0) return; /* * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. */ if (conns > 1 && (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || w->selconn != index)) return; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) { hdaa_audio_ctl_source_volume(pdevinfo, ossdev, wc->nid, j, mute, left, right, depth + 1); } } } return; } /* * Trace signal from destination, setting volumes on the way. */ static void hdaa_audio_ctl_dest_volume(struct hdaa_pcm_devinfo *pdevinfo, int ossdev, nid_t nid, int index, int mute, int left, int right, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, consumers, cleft, cright; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; if (depth > 0) { /* If this node produce output for several consumers, we can't touch it. */ consumers = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) consumers++; } } /* The only exception is if real HP redirection is configured and this is a duplication point. XXX: Actually exception is not completely correct. XXX: Duplication point check is not perfect. */ if ((consumers == 2 && (w->bindas < 0 || as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) return; /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); } /* We must not traverse pin */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && depth > 0) return; for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (index >= 0 && i != index) continue; cleft = left; cright = right; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &cleft, &cright); hdaa_audio_ctl_dest_volume(pdevinfo, ossdev, w->conns[i], -1, mute, cleft, cright, depth + 1); } } /* * Set volumes for the specified pdevinfo and ossdev. */ static void hdaa_audio_ctl_dev_volume(struct hdaa_pcm_devinfo *pdevinfo, unsigned dev) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; uint32_t mute; int lvol, rvol; int i, j; mute = 0; if (pdevinfo->left[dev] == 0) { mute |= HDAA_AMP_MUTE_LEFT; lvol = -4000; } else lvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) * pdevinfo->left[dev] + 50) / 100 + pdevinfo->minamp[dev]; if (pdevinfo->right[dev] == 0) { mute |= HDAA_AMP_MUTE_RIGHT; rvol = -4000; } else rvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) * pdevinfo->right[dev] + 50) / 100 + pdevinfo->minamp[dev]; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas < 0) { if (pdevinfo->index != 0) continue; } else { if (w->bindas != pdevinfo->playas && w->bindas != pdevinfo->recas) continue; } if (dev == SOUND_MIXER_RECLEV && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); continue; } if (dev == SOUND_MIXER_VOLUME && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && devinfo->as[w->bindas].dir == HDAA_CTL_OUT) { hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); continue; } if (dev == SOUND_MIXER_IGAIN && w->pflags & HDAA_ADC_MONITOR) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas == -1) continue; if (cw->bindas >= 0 && devinfo->as[cw->bindas].dir != HDAA_CTL_IN) continue; hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, j, mute, lvol, rvol, 0); } continue; } if (w->ossdev != dev) continue; hdaa_audio_ctl_source_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); if (dev == SOUND_MIXER_IMIX && (w->pflags & HDAA_IMIX_AS_DST)) hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); } } /* * OSS Mixer set method. */ static int hdaa_audio_ctl_ossmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; int i; hdaa_lock(devinfo); /* Save new values. */ pdevinfo->left[dev] = left; pdevinfo->right[dev] = right; /* 'ogain' is the special case implemented with EAPD. */ if (dev == SOUND_MIXER_OGAIN) { uint32_t orig; w = NULL; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDA_INVALID) continue; break; } if (i >= devinfo->endnode) { hdaa_unlock(devinfo); return (-1); } orig = w->param.eapdbtl; if (left == 0) w->param.eapdbtl &= ~HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; else w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; if (orig != w->param.eapdbtl) { uint32_t val; val = w->param.eapdbtl; if (devinfo->quirks & HDAA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hda_command(devinfo->dev, HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val)); } hdaa_unlock(devinfo); return (left | (left << 8)); } /* Recalculate all controls related to this OSS device. */ hdaa_audio_ctl_dev_volume(pdevinfo, dev); hdaa_unlock(devinfo); return (left | (right << 8)); } /* * Set mixer settings to our own default values: * +20dB for mics, -10dB for analog vol, mute for igain, 0dB for others. */ static void hdaa_audio_ctl_set_defaults(struct hdaa_pcm_devinfo *pdevinfo) { int amp, vol, dev; for (dev = 0; dev < SOUND_MIXER_NRDEVICES; dev++) { if ((pdevinfo->ossmask & (1 << dev)) == 0) continue; /* If the value was overridden, leave it as is. */ if (resource_int_value(device_get_name(pdevinfo->dev), device_get_unit(pdevinfo->dev), ossnames[dev], &vol) == 0) continue; vol = -1; if (dev == SOUND_MIXER_OGAIN) vol = 100; else if (dev == SOUND_MIXER_IGAIN) vol = 0; else if (dev == SOUND_MIXER_MIC || dev == SOUND_MIXER_MONITOR) amp = 20 * 4; /* +20dB */ else if (dev == SOUND_MIXER_VOLUME && !pdevinfo->digital) amp = -10 * 4; /* -10dB */ else amp = 0; if (vol < 0 && (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) <= 0) { vol = 100; } else if (vol < 0) { vol = ((amp - pdevinfo->minamp[dev]) * 100 + (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) / 2) / (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]); vol = imin(imax(vol, 1), 100); } mix_set(pdevinfo->mixer, dev, vol, vol); } } /* * Recursively commutate specified record source. */ static uint32_t hdaa_audio_ctl_recsel_comm(struct hdaa_pcm_devinfo *pdevinfo, uint32_t src, nid_t nid, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; char buf[64]; int i, muted; uint32_t res = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->bindas == -1) continue; /* Call recursively to trace signal to it's source if needed. */ if ((src & cw->ossmask) != 0) { if (cw->ossdev < 0) { res |= hdaa_audio_ctl_recsel_comm(pdevinfo, src, w->conns[i], depth + 1); } else { res |= cw->ossmask; } } /* We have two special cases: mixers and others (selectors). */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl == NULL) continue; /* If we have input control on this node mute them * according to requested sources. */ muted = (src & cw->ossmask) ? 0 : 1; if (muted != ctl->forcemute) { ctl->forcemute = muted; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); } HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Recsel (%s): nid %d source %d %s\n", hdaa_audio_ctl_ossmixer_mask2allname( src, buf, sizeof(buf)), nid, i, muted?"mute":"unmute"); ); } else { if (w->nconns == 1) break; if ((src & cw->ossmask) == 0) continue; /* If we found requested source - select it and exit. */ hdaa_widget_connection_select(w, i); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Recsel (%s): nid %d source %d select\n", hdaa_audio_ctl_ossmixer_mask2allname( src, buf, sizeof(buf)), nid, i); ); break; } } return (res); } static uint32_t hdaa_audio_ctl_ossmixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; struct hdaa_audio_as *as; struct hdaa_audio_ctl *ctl; struct hdaa_chan *ch; int i, j; uint32_t ret = 0xffffffff; hdaa_lock(devinfo); if (pdevinfo->recas < 0) { hdaa_unlock(devinfo); return (0); } as = &devinfo->as[pdevinfo->recas]; /* For non-mixed associations we always recording everything. */ if (!as->mixed) { hdaa_unlock(devinfo); return (mix_getrecdevs(m)); } /* Commutate requested recsrc for each ADC. */ for (j = 0; j < as->num_chans; j++) { ch = &devinfo->chans[as->chans[j]]; for (i = 0; ch->io[i] >= 0; i++) { w = hdaa_widget_get(devinfo, ch->io[i]); if (w == NULL || w->enable == 0) continue; ret &= hdaa_audio_ctl_recsel_comm(pdevinfo, src, ch->io[i], 0); } } if (ret == 0xffffffff) ret = 0; /* * Some controls could be shared. Reset volumes for controls * related to previously chosen devices, as they may no longer * affect the signal. */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || !(ctl->ossmask & pdevinfo->recsrc)) continue; if (!((pdevinfo->playas >= 0 && ctl->widget->bindas == pdevinfo->playas) || (pdevinfo->recas >= 0 && ctl->widget->bindas == pdevinfo->recas) || (pdevinfo->index == 0 && ctl->widget->bindas == -2))) continue; for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if (pdevinfo->recsrc & (1 << j)) { ctl->devleft[j] = 0; ctl->devright[j] = 0; ctl->devmute[j] = 0; } } } /* * Some controls could be shared. Set volumes for controls * related to devices selected both previously and now. */ for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if ((ret | pdevinfo->recsrc) & (1 << j)) hdaa_audio_ctl_dev_volume(pdevinfo, j); } pdevinfo->recsrc = ret; hdaa_unlock(devinfo); return (ret); } static kobj_method_t hdaa_audio_ctl_ossmixer_methods[] = { KOBJMETHOD(mixer_init, hdaa_audio_ctl_ossmixer_init), KOBJMETHOD(mixer_set, hdaa_audio_ctl_ossmixer_set), KOBJMETHOD(mixer_setrecsrc, hdaa_audio_ctl_ossmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(hdaa_audio_ctl_ossmixer); static void hdaa_dump_gpi(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data, wake, unsol, sticky; if (HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPI_DATA(0, devinfo->nid)); wake = hda_command(dev, HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(0, devinfo->nid)); unsol = hda_command(dev, HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(0, devinfo->nid)); sticky = hda_command(dev, HDA_CMD_GET_GPI_STICKY_MASK(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); i++) { device_printf(dev, " GPI%d:%s%s%s state=%d", i, (sticky & (1 << i)) ? " sticky" : "", (unsol & (1 << i)) ? " unsol" : "", (wake & (1 << i)) ? " wake" : "", (data >> i) & 1); } } } static void hdaa_dump_gpio(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data, dir, enable, wake, unsol, sticky; if (HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); enable = hda_command(dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); dir = hda_command(dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); wake = hda_command(dev, HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(0, devinfo->nid)); unsol = hda_command(dev, HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(0, devinfo->nid)); sticky = hda_command(dev, HDA_CMD_GET_GPIO_STICKY_MASK(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); i++) { device_printf(dev, " GPIO%d: ", i); if ((enable & (1 << i)) == 0) { printf("disabled\n"); continue; } if ((dir & (1 << i)) == 0) { printf("input%s%s%s", (sticky & (1 << i)) ? " sticky" : "", (unsol & (1 << i)) ? " unsol" : "", (wake & (1 << i)) ? " wake" : ""); } else printf("output"); printf(" state=%d\n", (data >> i) & 1); } } } static void hdaa_dump_gpo(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data; if (HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); i++) { device_printf(dev, " GPO%d: state=%d\n", i, (data >> i) & 1); } } } static void hdaa_audio_parse(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; uint32_t res; int i; nid_t nid; nid = devinfo->nid; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_GPIO_COUNT)); devinfo->gpio_cap = res; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "NumGPIO=%d NumGPO=%d " "NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap)); hdaa_dump_gpi(devinfo); hdaa_dump_gpio(devinfo); hdaa_dump_gpo(devinfo); ); res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS)); devinfo->supp_stream_formats = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE)); devinfo->supp_pcm_size_rate = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP)); devinfo->outamp_cap = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP)); devinfo->inamp_cap = res; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) device_printf(devinfo->dev, "Ghost widget! nid=%d!\n", i); else { w->devinfo = devinfo; w->nid = i; w->enable = 1; w->selconn = -1; w->pflags = 0; w->ossdev = -1; w->bindas = -1; w->param.eapdbtl = HDA_INVALID; hdaa_widget_parse(w); } } } static void hdaa_audio_postprocess(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; hdaa_widget_postprocess(w); } } static void hdaa_audio_ctl_parse(struct hdaa_devinfo *devinfo) { struct hdaa_audio_ctl *ctls; struct hdaa_widget *w, *cw; int i, j, cnt, max, ocap, icap; int mute, offset, step, size; /* XXX This is redundant */ max = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->param.outamp_cap != 0) max++; if (w->param.inamp_cap != 0) { switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; max++; } break; default: max++; break; } } } devinfo->ctlcnt = max; if (max < 1) return; ctls = malloc(sizeof(*ctls) * max, M_HDAA, M_ZERO | M_NOWAIT); if (ctls == NULL) { /* Blekh! */ device_printf(devinfo->dev, "unable to allocate ctls!\n"); devinfo->ctlcnt = 0; return; } cnt = 0; for (i = devinfo->startnode; cnt < max && i < devinfo->endnode; i++) { if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; ocap = w->param.outamp_cap; icap = w->param.inamp_cap; if (ocap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(ocap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(ocap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(ocap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(ocap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "BUGGY outamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) ctls[cnt].ndir = HDAA_CTL_IN; else ctls[cnt].ndir = HDAA_CTL_OUT; ctls[cnt++].dir = HDAA_CTL_OUT; } if (icap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(icap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(icap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(icap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(icap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "BUGGY inamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].childwidget = cw; ctls[cnt].index = j; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; ctls[cnt].ndir = HDAA_CTL_IN; ctls[cnt++].dir = HDAA_CTL_IN; } break; default: if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) ctls[cnt].ndir = HDAA_CTL_OUT; else ctls[cnt].ndir = HDAA_CTL_IN; ctls[cnt++].dir = HDAA_CTL_IN; break; } } } devinfo->ctl = ctls; } static void hdaa_audio_as_parse(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, j, cnt, max, type, dir, assoc, seq, first, hpredir; /* Count present associations */ max = 0; for (j = 1; j < 16; j++) { for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config) != j) continue; max++; if (j != 15) /* There could be many 1-pin assocs #15 */ break; } } devinfo->ascnt = max; if (max < 1) return; as = malloc(sizeof(*as) * max, M_HDAA, M_ZERO | M_NOWAIT); if (as == NULL) { /* Blekh! */ device_printf(devinfo->dev, "unable to allocate assocs!\n"); devinfo->ascnt = 0; return; } for (i = 0; i < max; i++) { as[i].hpredir = -1; as[i].digital = 0; as[i].num_chans = 1; as[i].location = -1; } /* Scan associations skipping as=0. */ cnt = 0; for (j = 1; j < 16 && cnt < max; j++) { first = 16; hpredir = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; assoc = HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config); seq = HDA_CONFIG_DEFAULTCONF_SEQUENCE(w->wclass.pin.config); if (assoc != j) { continue; } KASSERT(cnt < max, ("%s: Associations overflow (%d of %d)", __func__, cnt, max)); type = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; /* Get pin direction. */ if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER || type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT) dir = HDAA_CTL_OUT; else dir = HDAA_CTL_IN; /* If this is a first pin - create new association. */ if (as[cnt].pincnt == 0) { as[cnt].enable = 1; as[cnt].index = j; as[cnt].dir = dir; } if (seq < first) first = seq; /* Check association correctness. */ if (as[cnt].pins[seq] != 0) { device_printf(devinfo->dev, "%s: Duplicate pin %d (%d) " "in association %d! Disabling association.\n", __func__, seq, w->nid, j); as[cnt].enable = 0; } if (dir != as[cnt].dir) { device_printf(devinfo->dev, "%s: Pin %d has wrong " "direction for association %d! Disabling " "association.\n", __func__, w->nid, j); as[cnt].enable = 0; } if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { as[cnt].digital |= 0x1; if (HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) as[cnt].digital |= 0x2; if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap)) as[cnt].digital |= 0x4; } if (as[cnt].location == -1) { as[cnt].location = HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config); } else if (as[cnt].location != HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config)) { as[cnt].location = -2; } /* Headphones with seq=15 may mean redirection. */ if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT && seq == 15) hpredir = 1; as[cnt].pins[seq] = w->nid; as[cnt].pincnt++; /* Association 15 is a multiple unassociated pins. */ if (j == 15) cnt++; } if (j != 15 && as[cnt].pincnt > 0) { if (hpredir && as[cnt].pincnt > 1) as[cnt].hpredir = first; cnt++; } } for (i = 0; i < max; i++) { if (as[i].dir == HDAA_CTL_IN && (as[i].pincnt == 1 || as[i].pins[14] > 0 || as[i].pins[15] > 0)) as[i].mixed = 1; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "%d associations found:\n", max); for (i = 0; i < max; i++) { device_printf(devinfo->dev, "Association %d (%d) %s%s:\n", i, as[i].index, (as[i].dir == HDAA_CTL_IN)?"in":"out", as[i].enable?"":" (disabled)"); for (j = 0; j < 16; j++) { if (as[i].pins[j] == 0) continue; device_printf(devinfo->dev, " Pin nid=%d seq=%d\n", as[i].pins[j], j); } } ); devinfo->as = as; } /* * Trace path from DAC to pin. */ static nid_t hdaa_audio_trace_dac(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid, int dupseq, int min, int only, int depth) { struct hdaa_widget *w; int i, im = -1; nid_t m = 0, ret; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); } ); /* Use only unused widgets */ if (w->bindas >= 0 && w->bindas != as) { HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d busy by association %d\n", depth + 1, "", w->nid, w->bindas); } ); return (0); } if (dupseq < 0) { if (w->bindseqmask != 0) { HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); } ); return (0); } } else { /* If this is headphones - allow duplicate first pin. */ if (w->bindseqmask != 0 && (w->bindseqmask & (1 << dupseq)) == 0) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); ); return (0); } } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: /* Do not traverse input. AD1988 has digital monitor for which we are not ready. */ break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: /* If we are tracing HP take only dac of first pin. */ if ((only == 0 || only == w->nid) && (w->nid >= min) && (dupseq < 0 || w->nid == devinfo->as[as].dacs[0][dupseq])) m = w->nid; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Find reachable DACs with smallest nid respecting constraints. */ for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (w->selconn != -1 && w->selconn != i) continue; if ((ret = hdaa_audio_trace_dac(devinfo, as, seq, w->conns[i], dupseq, min, only, depth + 1)) != 0) { if (m == 0 || ret < m) { m = ret; im = i; } if (only || dupseq >= 0) break; } } if (im >= 0 && only && ((w->nconns > 1 && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) w->selconn = im; break; } if (m && only) { w->bindas = as; w->bindseqmask |= (1 << seq); } HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, m); } ); return (m); } /* * Trace path from widget to ADC. */ static nid_t hdaa_audio_trace_adc(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid, int mixed, int min, int only, int depth, int *length, int onlylength) { struct hdaa_widget *w, *wc; int i, j, im, lm = HDA_PARSE_MAXDEPTH; nid_t m = 0, ret; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); ); /* Use only unused widgets */ if (w->bindas >= 0 && w->bindas != as) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by association %d\n", depth + 1, "", w->nid, w->bindas); ); return (0); } if (!mixed && w->bindseqmask != 0) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); ); return (0); } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: if ((only == 0 || only == w->nid) && (w->nid >= min) && (onlylength == 0 || onlylength == depth)) { m = w->nid; *length = depth; } break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Try to find reachable ADCs with specified nid. */ for (j = devinfo->startnode; j < devinfo->endnode; j++) { wc = hdaa_widget_get(devinfo, j); if (wc == NULL || wc->enable == 0) continue; im = -1; for (i = 0; i < wc->nconns; i++) { if (wc->connsenable[i] == 0) continue; if (wc->conns[i] != nid) continue; if ((ret = hdaa_audio_trace_adc(devinfo, as, seq, j, mixed, min, only, depth + 1, length, onlylength)) != 0) { if (m == 0 || ret < m || (ret == m && *length < lm)) { m = ret; im = i; lm = *length; } else *length = lm; if (only) break; } } if (im >= 0 && only && ((wc->nconns > 1 && wc->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) wc->selconn = im; } break; } if (m && only) { w->bindas = as; w->bindseqmask |= (1 << seq); } HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, m); ); return (m); } /* * Erase trace path of the specified association. */ static void hdaa_audio_undo_trace(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_widget *w; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == as) { if (seq >= 0) { w->bindseqmask &= ~(1 << seq); if (w->bindseqmask == 0) { w->bindas = -1; w->selconn = -1; } } else { w->bindas = -1; w->bindseqmask = 0; w->selconn = -1; } } } } /* * Trace association path from DAC to output */ static int hdaa_audio_trace_as_out(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_audio_as *ases = devinfo->as; int i, hpredir; nid_t min, res; /* Find next pin */ for (i = seq; i < 16 && ases[as].pins[i] == 0; i++) ; /* Check if there is no any left. If so - we succeeded. */ if (i == 16) return (1); hpredir = (i == 15 && ases[as].fakeredir == 0)?ases[as].hpredir:-1; min = 0; do { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d with min nid %d", ases[as].pins[i], min); if (hpredir >= 0) printf(" and hpredir %d", hpredir); printf("\n"); ); /* Trace this pin taking min nid into account. */ res = hdaa_audio_trace_dac(devinfo, as, i, ases[as].pins[i], hpredir, min, 0, 0); if (res == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d seq %d with min " "nid %d", ases[as].pins[i], i, min); if (hpredir >= 0) printf(" and hpredir %d", hpredir); printf("\n"); ); return (0); } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to DAC %d", ases[as].pins[i], res); if (hpredir >= 0) printf(" and hpredir %d", hpredir); if (ases[as].fakeredir) printf(" with fake redirection"); printf("\n"); ); /* Trace again to mark the path */ hdaa_audio_trace_dac(devinfo, as, i, ases[as].pins[i], hpredir, min, res, 0); ases[as].dacs[0][i] = res; /* We succeeded, so call next. */ if (hdaa_audio_trace_as_out(devinfo, as, i + 1)) return (1); /* If next failed, we should retry with next min */ hdaa_audio_undo_trace(devinfo, as, i); ases[as].dacs[0][i] = 0; min = res + 1; } while (1); } /* * Check equivalency of two DACs. */ static int hdaa_audio_dacs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2) { struct hdaa_devinfo *devinfo = w1->devinfo; struct hdaa_widget *w3; int i, j, c1, c2; if (memcmp(&w1->param, &w2->param, sizeof(w1->param))) return (0); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w3 = hdaa_widget_get(devinfo, i); if (w3 == NULL || w3->enable == 0) continue; if (w3->bindas != w1->bindas) continue; if (w3->nconns == 0) continue; c1 = c2 = -1; for (j = 0; j < w3->nconns; j++) { if (w3->connsenable[j] == 0) continue; if (w3->conns[j] == w1->nid) c1 = j; if (w3->conns[j] == w2->nid) c2 = j; } if (c1 < 0) continue; if (c2 < 0) return (0); if (w3->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) return (0); } return (1); } /* * Check equivalency of two ADCs. */ static int hdaa_audio_adcs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2) { struct hdaa_devinfo *devinfo = w1->devinfo; struct hdaa_widget *w3, *w4; int i; if (memcmp(&w1->param, &w2->param, sizeof(w1->param))) return (0); if (w1->nconns != 1 || w2->nconns != 1) return (0); if (w1->conns[0] == w2->conns[0]) return (1); w3 = hdaa_widget_get(devinfo, w1->conns[0]); if (w3 == NULL || w3->enable == 0) return (0); w4 = hdaa_widget_get(devinfo, w2->conns[0]); if (w4 == NULL || w4->enable == 0) return (0); if (w3->bindas == w4->bindas && w3->bindseqmask == w4->bindseqmask) return (1); if (w4->bindas >= 0) return (0); if (w3->type != w4->type) return (0); if (memcmp(&w3->param, &w4->param, sizeof(w3->param))) return (0); if (w3->nconns != w4->nconns) return (0); for (i = 0; i < w3->nconns; i++) { if (w3->conns[i] != w4->conns[i]) return (0); } return (1); } /* * Look for equivalent DAC/ADC to implement second channel. */ static void hdaa_audio_adddac(struct hdaa_devinfo *devinfo, int asid) { struct hdaa_audio_as *as = &devinfo->as[asid]; struct hdaa_widget *w1, *w2; int i, pos; nid_t nid1, nid2; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Looking for additional %sC " "for association %d (%d)\n", (as->dir == HDAA_CTL_OUT) ? "DA" : "AD", asid, as->index); ); /* Find the existing DAC position and return if found more the one. */ pos = -1; for (i = 0; i < 16; i++) { if (as->dacs[0][i] <= 0) continue; if (pos >= 0 && as->dacs[0][i] != as->dacs[0][pos]) return; pos = i; } nid1 = as->dacs[0][pos]; w1 = hdaa_widget_get(devinfo, nid1); w2 = NULL; for (nid2 = devinfo->startnode; nid2 < devinfo->endnode; nid2++) { w2 = hdaa_widget_get(devinfo, nid2); if (w2 == NULL || w2->enable == 0) continue; if (w2->bindas >= 0) continue; if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) { if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) continue; if (hdaa_audio_dacs_equal(w1, w2)) break; } else { if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (hdaa_audio_adcs_equal(w1, w2)) break; } } if (nid2 >= devinfo->endnode) return; w2->bindas = w1->bindas; w2->bindseqmask = w1->bindseqmask; if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " ADC %d considered equal to ADC %d\n", nid2, nid1); ); w1 = hdaa_widget_get(devinfo, w1->conns[0]); w2 = hdaa_widget_get(devinfo, w2->conns[0]); w2->bindas = w1->bindas; w2->bindseqmask = w1->bindseqmask; } else { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " DAC %d considered equal to DAC %d\n", nid2, nid1); ); } for (i = 0; i < 16; i++) { if (as->dacs[0][i] <= 0) continue; as->dacs[as->num_chans][i] = nid2; } as->num_chans++; } /* * Trace association path from input to ADC */ static int hdaa_audio_trace_as_in(struct hdaa_devinfo *devinfo, int as) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w; int i, j, k, length; for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->bindas >= 0 && w->bindas != as) continue; /* Find next pin */ for (i = 0; i < 16; i++) { if (ases[as].pins[i] == 0) continue; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d to ADC %d\n", ases[as].pins[i], j); ); /* Trace this pin taking goal into account. */ if (hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 1, 0, j, 0, &length, 0) == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d to ADC %d, undo traces\n", ases[as].pins[i], j); ); hdaa_audio_undo_trace(devinfo, as, -1); for (k = 0; k < 16; k++) ases[as].dacs[0][k] = 0; break; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to ADC %d\n", ases[as].pins[i], j); ); ases[as].dacs[0][i] = j; } if (i == 16) return (1); } return (0); } /* * Trace association path from input to multiple ADCs */ static int hdaa_audio_trace_as_in_mch(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_audio_as *ases = devinfo->as; int i, length; nid_t min, res; /* Find next pin */ for (i = seq; i < 16 && ases[as].pins[i] == 0; i++) ; /* Check if there is no any left. If so - we succeeded. */ if (i == 16) return (1); min = 0; do { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d with min nid %d", ases[as].pins[i], min); printf("\n"); ); /* Trace this pin taking min nid into account. */ res = hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 0, min, 0, 0, &length, 0); if (res == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d seq %d with min " "nid %d", ases[as].pins[i], i, min); printf("\n"); ); return (0); } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to ADC %d\n", ases[as].pins[i], res); ); /* Trace again to mark the path */ hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 0, min, res, 0, &length, length); ases[as].dacs[0][i] = res; /* We succeeded, so call next. */ if (hdaa_audio_trace_as_in_mch(devinfo, as, i + 1)) return (1); /* If next failed, we should retry with next min */ hdaa_audio_undo_trace(devinfo, as, i); ases[as].dacs[0][i] = 0; min = res + 1; } while (1); } /* * Trace input monitor path from mixer to output association. */ static int hdaa_audio_trace_to_out(struct hdaa_devinfo *devinfo, nid_t nid, int depth) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w, *wc; int i, j; nid_t res = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); ); /* Use only unused widgets */ if (depth > 0 && w->bindas != -1) { if (w->bindas < 0 || ases[w->bindas].dir == HDAA_CTL_OUT) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d found output association %d\n", depth + 1, "", w->nid, w->bindas); ); if (w->bindas >= 0) w->pflags |= HDAA_ADC_MONITOR; return (1); } else { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by input association %d\n", depth + 1, "", w->nid, w->bindas); ); return (0); } } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: /* Do not traverse input. AD1988 has digital monitor for which we are not ready. */ break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Try to find reachable ADCs with specified nid. */ for (j = devinfo->startnode; j < devinfo->endnode; j++) { wc = hdaa_widget_get(devinfo, j); if (wc == NULL || wc->enable == 0) continue; for (i = 0; i < wc->nconns; i++) { if (wc->connsenable[i] == 0) continue; if (wc->conns[i] != nid) continue; if (hdaa_audio_trace_to_out(devinfo, j, depth + 1) != 0) { res = 1; if (wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && wc->selconn == -1) wc->selconn = i; } } } break; } if (res && w->bindas == -1) w->bindas = -2; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, res); ); return (res); } /* * Trace extra associations (beeper, monitor) */ static void hdaa_audio_trace_as_extra(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int j; /* Input monitor */ /* Find mixer associated with input, but supplying signal for output associations. Hope it will be input monitor. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing input monitor\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d is input monitor\n", w->nid); ); w->ossdev = SOUND_MIXER_IMIX; } } /* Other inputs monitor */ /* Find input pins supplying signal for output associations. Hope it will be input monitoring. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing other input monitors\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d is input monitor\n", w->nid); ); } } /* Beeper */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing beeper\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) continue; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d traced to out\n", j); ); } w->bindas = -2; } } /* * Bind assotiations to PCM channels */ static void hdaa_audio_bind_as(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int i, j, cnt = 0, free; for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable) cnt += as[j].num_chans; } if (devinfo->num_chans == 0) { devinfo->chans = malloc(sizeof(struct hdaa_chan) * cnt, M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->chans == NULL) { device_printf(devinfo->dev, "Channels memory allocation failed!\n"); return; } } else { devinfo->chans = (struct hdaa_chan *)realloc(devinfo->chans, sizeof(struct hdaa_chan) * (devinfo->num_chans + cnt), M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->chans == NULL) { devinfo->num_chans = 0; device_printf(devinfo->dev, "Channels memory allocation failed!\n"); return; } /* Fixup relative pointers after realloc */ for (j = 0; j < devinfo->num_chans; j++) devinfo->chans[j].caps.fmtlist = devinfo->chans[j].fmtlist; } free = devinfo->num_chans; devinfo->num_chans += cnt; for (j = free; j < free + cnt; j++) { devinfo->chans[j].devinfo = devinfo; devinfo->chans[j].as = -1; } /* Assign associations in order of their numbers, */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; for (i = 0; i < as[j].num_chans; i++) { devinfo->chans[free].as = j; devinfo->chans[free].asindex = i; devinfo->chans[free].dir = (as[j].dir == HDAA_CTL_IN) ? PCMDIR_REC : PCMDIR_PLAY; hdaa_pcmchannel_setup(&devinfo->chans[free]); as[j].chans[i] = free; free++; } } } static void hdaa_audio_disable_nonaudio(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; /* Disable power and volume widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to it's" " non-audio type.\n", w->nid); ); } } } static void hdaa_audio_disable_useless(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int done, found, i, j, k; /* Disable useless pins. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling pin nid %d due" " to None connectivity.\n", w->nid); ); } else if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK) == 0) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unassociated" " pin nid %d.\n", w->nid); ); } } } do { done = 1; /* Disable and mute controls for disabled widgets. */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0) continue; if (ctl->widget->enable == 0 || (ctl->childwidget != NULL && ctl->childwidget->enable == 0)) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; if (ctl->ndir == HDAA_CTL_IN) ctl->widget->connsenable[ctl->index] = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling ctl %d nid %d cnid %d due" " to disabled widget.\n", i, ctl->widget->nid, (ctl->childwidget != NULL)? ctl->childwidget->nid:-1); ); } } /* Disable useless widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; /* Disable inputs with disabled child widgets. */ for (j = 0; j < w->nconns; j++) { if (w->connsenable[j]) { cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) { w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d connection %d due" " to disabled child widget.\n", i, j); ); } } } if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; /* Disable mixers and selectors without inputs. */ found = 0; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j]) { found = 1; break; } } if (found == 0) { w->enable = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to all it's" " inputs disabled.\n", w->nid); ); } /* Disable nodes without consumers. */ if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; found = 0; for (k = devinfo->startnode; k < devinfo->endnode; k++) { cw = hdaa_widget_get(devinfo, k); if (cw == NULL || cw->enable == 0) continue; for (j = 0; j < cw->nconns; j++) { if (cw->connsenable[j] && cw->conns[j] == i) { found = 1; break; } } } if (found == 0) { w->enable = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to all it's" " consumers disabled.\n", w->nid); ); } } } while (done == 0); } static void hdaa_audio_disable_unas(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int i, j, k; /* Disable unassosiated widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == -1) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unassociated nid %d.\n", w->nid); ); } } /* Disable input connections on input pin and * output on output. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->bindas < 0) continue; if (as[w->bindas].dir == HDAA_CTL_IN) { for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling connection to input pin " "nid %d conn %d.\n", i, j); ); } ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, -1, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } } else { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } for (k = devinfo->startnode; k < devinfo->endnode; k++) { cw = hdaa_widget_get(devinfo, k); if (cw == NULL || cw->enable == 0) continue; for (j = 0; j < cw->nconns; j++) { if (cw->connsenable[j] && cw->conns[j] == i) { cw->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling connection from output pin " "nid %d conn %d cnid %d.\n", k, j, i); ); if (cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && cw->nconns > 1) continue; ctl = hdaa_audio_ctl_amp_get(devinfo, k, HDAA_CTL_IN, j, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } } } } } } } static void hdaa_audio_disable_notselected(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int i, j; /* On playback path we can safely disable all unseleted inputs. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->nconns <= 1) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; if (w->bindas < 0 || as[w->bindas].dir == HDAA_CTL_IN) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; if (w->selconn < 0 || w->selconn == j) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unselected connection " "nid %d conn %d.\n", i, j); ); } } } static void hdaa_audio_disable_crossas(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int i, j; /* Disable crossassociatement and unwanted crosschannel connections. */ /* ... using selectors */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->nconns <= 1) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; /* Allow any -> mix */ if (w->bindas == -2) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || w->enable == 0) continue; /* Allow mix -> out. */ if (cw->bindas == -2 && w->bindas >= 0 && ases[w->bindas].dir == HDAA_CTL_OUT) continue; /* Allow mix -> mixed-in. */ if (cw->bindas == -2 && w->bindas >= 0 && ases[w->bindas].mixed) continue; /* Allow in -> mix. */ if ((w->pflags & HDAA_ADC_MONITOR) && cw->bindas >= 0 && ases[cw->bindas].dir == HDAA_CTL_IN) continue; /* Allow if have common as/seqs. */ if (w->bindas == cw->bindas && (w->bindseqmask & cw->bindseqmask) != 0) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling crossassociatement connection " "nid %d conn %d cnid %d.\n", i, j, cw->nid); ); } } /* ... using controls */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->childwidget == NULL) continue; /* Allow any -> mix */ if (ctl->widget->bindas == -2) continue; /* Allow mix -> out. */ if (ctl->childwidget->bindas == -2 && ctl->widget->bindas >= 0 && ases[ctl->widget->bindas].dir == HDAA_CTL_OUT) continue; /* Allow mix -> mixed-in. */ if (ctl->childwidget->bindas == -2 && ctl->widget->bindas >= 0 && ases[ctl->widget->bindas].mixed) continue; /* Allow in -> mix. */ if ((ctl->widget->pflags & HDAA_ADC_MONITOR) && ctl->childwidget->bindas >= 0 && ases[ctl->childwidget->bindas].dir == HDAA_CTL_IN) continue; /* Allow if have common as/seqs. */ if (ctl->widget->bindas == ctl->childwidget->bindas && (ctl->widget->bindseqmask & ctl->childwidget->bindseqmask) != 0) continue; ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; if (ctl->ndir == HDAA_CTL_IN) ctl->widget->connsenable[ctl->index] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling crossassociatement connection " "ctl %d nid %d cnid %d.\n", i, ctl->widget->nid, ctl->childwidget->nid); ); } } /* * Find controls to control amplification for source and calculate possible * amplification range. */ static int hdaa_audio_ctl_source_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, int ossdev, int ctlable, int depth, int *minamp, int *maxamp) { struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, conns = 0, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (found); /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; conns++; } } /* If this is not a first step - use input mixer. Pins have common input ctl so care must be taken. */ if (depth > 0 && ctlable && (conns == 1 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } /* If widget has own ossdev - not traverse it. It will be traversed on its own. */ if (w->ossdev >= 0 && depth > 0) return (found); /* We must not traverse pin */ if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && depth > 0) return (found); /* record that this widget exports such signal, */ w->ossmask |= (1 << ossdev); /* * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. */ if (conns > 1 && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) ctlable = 0; if (ctlable) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } cminamp = cmaxamp = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) { tminamp = tmaxamp = 0; found += hdaa_audio_ctl_source_amp(devinfo, wc->nid, j, ossdev, ctlable, depth + 1, &tminamp, &tmaxamp); if (cminamp == 0 && cmaxamp == 0) { cminamp = tminamp; cmaxamp = tmaxamp; } else if (tminamp != tmaxamp) { cminamp = imax(cminamp, tminamp); cmaxamp = imin(cmaxamp, tmaxamp); } } } } if (*minamp == *maxamp && cminamp < cmaxamp) { *minamp += cminamp; *maxamp += cmaxamp; } return (found); } /* * Find controls to control amplification for destination and calculate * possible amplification range. */ static int hdaa_audio_ctl_dest_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, int ossdev, int depth, int *minamp, int *maxamp) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, consumers, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (found); if (depth > 0) { /* If this node produce output for several consumers, we can't touch it. */ consumers = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) consumers++; } } /* The only exception is if real HP redirection is configured and this is a duplication point. XXX: Actually exception is not completely correct. XXX: Duplication point check is not perfect. */ if ((consumers == 2 && (w->bindas < 0 || as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) return (found); /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } /* We must not traverse pin */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && depth > 0) return (found); cminamp = cmaxamp = 0; for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (index >= 0 && i != index) continue; tminamp = tmaxamp = 0; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { tminamp += MINQDB(ctl); tmaxamp += MAXQDB(ctl); } } found += hdaa_audio_ctl_dest_amp(devinfo, w->conns[i], -1, ossdev, depth + 1, &tminamp, &tmaxamp); if (cminamp == 0 && cmaxamp == 0) { cminamp = tminamp; cmaxamp = tmaxamp; } else if (tminamp != tmaxamp) { cminamp = imax(cminamp, tminamp); cmaxamp = imin(cmaxamp, tmaxamp); } } if (*minamp == *maxamp && cminamp < cmaxamp) { *minamp += cminamp; *maxamp += cmaxamp; } return (found); } /* * Assign OSS names to sound sources */ static void hdaa_audio_assign_names(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int i, j; int type = -1, use, used = 0; static const int types[7][13] = { { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, -1 }, /* line */ { SOUND_MIXER_MONITOR, SOUND_MIXER_MIC, -1 }, /* int mic */ { SOUND_MIXER_MIC, SOUND_MIXER_MONITOR, -1 }, /* ext mic */ { SOUND_MIXER_CD, -1 }, /* cd */ { SOUND_MIXER_SPEAKER, -1 }, /* speaker */ { SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, -1 }, /* digital */ { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, SOUND_MIXER_PHONEIN, SOUND_MIXER_PHONEOUT, SOUND_MIXER_VIDEO, SOUND_MIXER_RADIO, SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, SOUND_MIXER_MONITOR, -1 } /* others */ }; /* Surely known names */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == -1) continue; use = -1; switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (as[w->bindas].dir == HDAA_CTL_OUT) break; type = -1; switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: type = 0; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) break; type = 1; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: type = 3; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: type = 4; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN: case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_IN: type = 5; break; } if (type == -1) break; j = 0; while (types[type][j] >= 0 && (used & (1 << types[type][j])) != 0) { j++; } if (types[type][j] >= 0) use = types[type][j]; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: use = SOUND_MIXER_PCM; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: use = SOUND_MIXER_SPEAKER; break; default: break; } if (use >= 0) { w->ossdev = use; used |= (1 << use); } } /* Semi-known names */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev >= 0) continue; if (w->bindas == -1) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (as[w->bindas].dir == HDAA_CTL_OUT) continue; type = -1; switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: case HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_AUX: type = 0; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: type = 2; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT: type = 5; break; } if (type == -1) break; j = 0; while (types[type][j] >= 0 && (used & (1 << types[type][j])) != 0) { j++; } if (types[type][j] >= 0) { w->ossdev = types[type][j]; used |= (1 << types[type][j]); } } /* Others */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev >= 0) continue; if (w->bindas == -1) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (as[w->bindas].dir == HDAA_CTL_OUT) continue; j = 0; while (types[6][j] >= 0 && (used & (1 << types[6][j])) != 0) { j++; } if (types[6][j] >= 0) { w->ossdev = types[6][j]; used |= (1 << types[6][j]); } } } static void hdaa_audio_build_tree(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int j, res; /* Trace all associations in order of their numbers. */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing association %d (%d)\n", j, as[j].index); ); if (as[j].dir == HDAA_CTL_OUT) { retry: res = hdaa_audio_trace_as_out(devinfo, j, 0); if (res == 0 && as[j].hpredir >= 0 && as[j].fakeredir == 0) { /* If CODEC can't do analog HP redirection try to make it using one more DAC. */ as[j].fakeredir = 1; goto retry; } } else if (as[j].mixed) res = hdaa_audio_trace_as_in(devinfo, j); else res = hdaa_audio_trace_as_in_mch(devinfo, j, 0); if (res) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Association %d (%d) trace succeeded\n", j, as[j].index); ); } else { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Association %d (%d) trace failed\n", j, as[j].index); ); as[j].enable = 0; } } /* Look for additional DACs/ADCs. */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; hdaa_audio_adddac(devinfo, j); } /* Trace mixer and beeper pseudo associations. */ hdaa_audio_trace_as_extra(devinfo); } /* * Store in pdevinfo new data about whether and how we can control signal * for OSS device to/from specified widget. */ static void hdaa_adjust_amp(struct hdaa_widget *w, int ossdev, int found, int minamp, int maxamp) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_pcm_devinfo *pdevinfo; if (w->bindas >= 0) pdevinfo = devinfo->as[w->bindas].pdevinfo; else pdevinfo = &devinfo->devs[0]; if (found) pdevinfo->ossmask |= (1 << ossdev); if (minamp == 0 && maxamp == 0) return; if (pdevinfo->minamp[ossdev] == 0 && pdevinfo->maxamp[ossdev] == 0) { pdevinfo->minamp[ossdev] = minamp; pdevinfo->maxamp[ossdev] = maxamp; } else { pdevinfo->minamp[ossdev] = imax(pdevinfo->minamp[ossdev], minamp); pdevinfo->maxamp[ossdev] = imin(pdevinfo->maxamp[ossdev], maxamp); } } /* * Trace signals from/to all possible sources/destionstions to find possible * recording sources, OSS device control ranges and to assign controls. */ static void hdaa_audio_assign_mixers(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *cw; int i, j, minamp, maxamp, found; /* Assign mixers to the tree. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; minamp = maxamp = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET || (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && as[w->bindas].dir == HDAA_CTL_IN)) { if (w->ossdev < 0) continue; found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, w->ossdev, 1, 0, &minamp, &maxamp); hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, SOUND_MIXER_RECLEV, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_RECLEV, found, minamp, maxamp); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && as[w->bindas].dir == HDAA_CTL_OUT) { found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, SOUND_MIXER_VOLUME, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_VOLUME, found, minamp, maxamp); } if (w->ossdev == SOUND_MIXER_IMIX) { minamp = maxamp = 0; found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, w->ossdev, 1, 0, &minamp, &maxamp); if (minamp == maxamp) { /* If we are unable to control input monitor as source - try to control it as destination. */ found += hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, w->ossdev, 0, &minamp, &maxamp); w->pflags |= HDAA_IMIX_AS_DST; } hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp); } if (w->pflags & HDAA_ADC_MONITOR) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas == -1) continue; if (cw->bindas >= 0 && as[cw->bindas].dir != HDAA_CTL_IN) continue; minamp = maxamp = 0; found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, j, SOUND_MIXER_IGAIN, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_IGAIN, found, minamp, maxamp); } } } } static void hdaa_audio_prepare_pin_ctrl(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; uint32_t pincap; int i; for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && w->waspin == 0) continue; pincap = w->wclass.pin.cap; /* Disable everything. */ if (devinfo->init_clear) { w->wclass.pin.ctrl &= ~( HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); } if (w->enable == 0) { /* Pin is unused so left it disabled. */ continue; } else if (w->waspin) { /* Enable input for beeper input. */ w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; } else if (w->bindas < 0 || as[w->bindas].enable == 0) { /* Pin is unused so left it disabled. */ continue; } else if (as[w->bindas].dir == HDAA_CTL_IN) { /* Input pin, configure for input. */ if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; if ((devinfo->quirks & HDAA_QUIRK_IVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->quirks & HDAA_QUIRK_IVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->quirks & HDAA_QUIRK_IVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } else { /* Output pin, configure for output. */ if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap) && (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; if ((devinfo->quirks & HDAA_QUIRK_OVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->quirks & HDAA_QUIRK_OVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->quirks & HDAA_QUIRK_OVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } } } static void hdaa_audio_ctl_commit(struct hdaa_devinfo *devinfo) { struct hdaa_audio_ctl *ctl; int i, z; i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->ossmask != 0) { /* Mute disabled and mixer controllable controls. * Last will be initialized by mixer_init(). * This expected to reduce click on startup. */ hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_ALL, 0, 0); continue; } /* Init fixed controls to 0dB amplification. */ z = ctl->offset; if (z > ctl->step) z = ctl->step; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_NONE, z, z); } } static void hdaa_gpio_commit(struct hdaa_devinfo *devinfo) { uint32_t gdata, gmask, gdir; int i, numgpio; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); if (devinfo->gpio != 0 && numgpio != 0) { gdata = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); gmask = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); gdir = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); for (i = 0; i < numgpio; i++) { if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_SET(i)) { gdata |= (1 << i); gmask |= (1 << i); gdir |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_CLEAR(i)) { gdata &= ~(1 << i); gmask |= (1 << i); gdir |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_DISABLE(i)) { gmask &= ~(1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_INPUT(i)) { gmask |= (1 << i); gdir &= ~(1 << i); } } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "GPIO commit\n"); ); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_ENABLE_MASK(0, devinfo->nid, gmask)); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_DIRECTION(0, devinfo->nid, gdir)); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_DATA(0, devinfo->nid, gdata)); HDA_BOOTVERBOSE( hdaa_dump_gpio(devinfo); ); } } static void hdaa_gpo_commit(struct hdaa_devinfo *devinfo) { uint32_t gdata; int i, numgpo; numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); if (devinfo->gpo != 0 && numgpo != 0) { gdata = hda_command(devinfo->dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); for (i = 0; i < numgpo; i++) { if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_SET(i)) { gdata |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_CLEAR(i)) { gdata &= ~(1 << i); } } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "GPO commit\n"); ); hda_command(devinfo->dev, HDA_CMD_SET_GPO_DATA(0, devinfo->nid, gdata)); HDA_BOOTVERBOSE( hdaa_dump_gpo(devinfo); ); } } static void hdaa_audio_commit(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; /* Commit controls. */ hdaa_audio_ctl_commit(devinfo); /* Commit selectors, pins and EAPD. */ for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL) continue; if (w->selconn == -1) w->selconn = 0; if (w->nconns > 0) hdaa_widget_connection_select(w, w->selconn); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) { hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid, w->wclass.pin.ctrl)); } if (w->param.eapdbtl != HDA_INVALID) { uint32_t val; val = w->param.eapdbtl; if (devinfo->quirks & HDAA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hda_command(devinfo->dev, HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val)); } } hdaa_gpio_commit(devinfo); hdaa_gpo_commit(devinfo); } static void hdaa_powerup(struct hdaa_devinfo *devinfo) { int i; hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, devinfo->nid, HDA_CMD_POWER_STATE_D0)); DELAY(100); for (i = devinfo->startnode; i < devinfo->endnode; i++) { hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, i, HDA_CMD_POWER_STATE_D0)); } DELAY(1000); } static int hdaa_pcmchannel_setup(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; uint32_t cap, fmtcap, pcmcap; int i, j, ret, channels, onlystereo; uint16_t pinset; ch->caps = hdaa_caps; ch->caps.fmtlist = ch->fmtlist; ch->bit16 = 1; ch->bit32 = 0; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; ch->stripecap = 0xff; ret = 0; channels = 0; onlystereo = 1; pinset = 0; fmtcap = devinfo->supp_stream_formats; pcmcap = devinfo->supp_pcm_size_rate; for (i = 0; i < 16; i++) { /* Check as is correct */ if (ch->as < 0) break; /* Cound only present DACs */ if (as[ch->as].dacs[ch->asindex][i] <= 0) continue; /* Ignore duplicates */ for (j = 0; j < ret; j++) { if (ch->io[j] == as[ch->as].dacs[ch->asindex][i]) break; } if (j < ret) continue; w = hdaa_widget_get(devinfo, as[ch->as].dacs[ch->asindex][i]); if (w == NULL || w->enable == 0) continue; cap = w->param.supp_stream_formats; if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap) && !HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) continue; /* Many CODECs does not declare AC3 support on SPDIF. I don't beleave that they doesn't support it! */ if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) cap |= HDA_PARAM_SUPP_STREAM_FORMATS_AC3_MASK; if (ret == 0) { fmtcap = cap; pcmcap = w->param.supp_pcm_size_rate; } else { fmtcap &= cap; pcmcap &= w->param.supp_pcm_size_rate; } ch->io[ret++] = as[ch->as].dacs[ch->asindex][i]; ch->stripecap &= w->wclass.conv.stripecap; /* Do not count redirection pin/dac channels. */ if (i == 15 && as[ch->as].hpredir >= 0) continue; channels += HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) + 1; if (HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) != 1) onlystereo = 0; pinset |= (1 << i); } ch->io[ret] = -1; ch->channels = channels; if (as[ch->as].fakeredir) ret--; /* Standard speaks only about stereo pins and playback, ... */ if ((!onlystereo) || as[ch->as].mixed) pinset = 0; /* ..., but there it gives us info about speakers layout. */ as[ch->as].pinset = pinset; ch->supp_stream_formats = fmtcap; ch->supp_pcm_size_rate = pcmcap; /* * 8bit = 0 * 16bit = 1 * 20bit = 2 * 24bit = 3 * 32bit = 4 */ if (ret > 0) { i = 0; if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(fmtcap)) { if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(pcmcap)) ch->bit16 = 1; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(pcmcap)) ch->bit16 = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap)) ch->bit32 = 3; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap)) ch->bit32 = 2; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap)) ch->bit32 = 4; if (!(devinfo->quirks & HDAA_QUIRK_FORCESTEREO)) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 1, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 1, 0); } if (channels >= 2) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 2, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 2, 0); } if (channels >= 3 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 1); } if (channels >= 4) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 0); if (!onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 1); } } if (channels >= 5 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 1); } if (channels >= 6) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 1); if (!onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 0); } } if (channels >= 7 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 1); } if (channels >= 8) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 8, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 8, 1); } } if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(fmtcap)) { ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 2, 0); if (channels >= 8) { ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 8, 1); } } ch->fmtlist[i] = 0; i = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(pcmcap)) ch->pcmrates[i++] = 8000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(pcmcap)) ch->pcmrates[i++] = 11025; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(pcmcap)) ch->pcmrates[i++] = 16000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(pcmcap)) ch->pcmrates[i++] = 22050; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(pcmcap)) ch->pcmrates[i++] = 32000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(pcmcap)) ch->pcmrates[i++] = 44100; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(pcmcap)) */ ch->pcmrates[i++] = 48000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(pcmcap)) ch->pcmrates[i++] = 88200; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(pcmcap)) ch->pcmrates[i++] = 96000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(pcmcap)) ch->pcmrates[i++] = 176400; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(pcmcap)) ch->pcmrates[i++] = 192000; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(pcmcap)) */ ch->pcmrates[i] = 0; if (i > 0) { ch->caps.minspeed = ch->pcmrates[0]; ch->caps.maxspeed = ch->pcmrates[i - 1]; } } return (ret); } static void hdaa_prepare_pcms(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int i, j, k, apdev = 0, ardev = 0, dpdev = 0, drdev = 0; for (i = 0; i < devinfo->ascnt; i++) { if (as[i].enable == 0) continue; if (as[i].dir == HDAA_CTL_IN) { if (as[i].digital) drdev++; else ardev++; } else { if (as[i].digital) dpdev++; else apdev++; } } devinfo->num_devs = max(ardev, apdev) + max(drdev, dpdev); devinfo->devs = malloc(devinfo->num_devs * sizeof(struct hdaa_pcm_devinfo), M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->devs == NULL) { device_printf(devinfo->dev, "Unable to allocate memory for devices\n"); return; } for (i = 0; i < devinfo->num_devs; i++) { devinfo->devs[i].index = i; devinfo->devs[i].devinfo = devinfo; devinfo->devs[i].playas = -1; devinfo->devs[i].recas = -1; devinfo->devs[i].digital = 255; } for (i = 0; i < devinfo->ascnt; i++) { if (as[i].enable == 0) continue; for (j = 0; j < devinfo->num_devs; j++) { if (devinfo->devs[j].digital != 255 && (!devinfo->devs[j].digital) != (!as[i].digital)) continue; if (as[i].dir == HDAA_CTL_IN) { if (devinfo->devs[j].recas >= 0) continue; devinfo->devs[j].recas = i; } else { if (devinfo->devs[j].playas >= 0) continue; devinfo->devs[j].playas = i; } as[i].pdevinfo = &devinfo->devs[j]; for (k = 0; k < as[i].num_chans; k++) { devinfo->chans[as[i].chans[k]].pdevinfo = &devinfo->devs[j]; } devinfo->devs[j].digital = as[i].digital; break; } } } static void hdaa_create_pcms(struct hdaa_devinfo *devinfo) { int i; for (i = 0; i < devinfo->num_devs; i++) { struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i]; pdevinfo->dev = device_add_child(devinfo->dev, "pcm", DEVICE_UNIT_ANY); device_set_ivars(pdevinfo->dev, (void *)pdevinfo); } } static void hdaa_dump_ctls(struct hdaa_pcm_devinfo *pdevinfo, const char *banner, uint32_t flag) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_ctl *ctl; char buf[64]; int i, j, printed = 0; if (flag == 0) { flag = ~(SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_CD | SOUND_MASK_LINE | SOUND_MASK_RECLEV | SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | SOUND_MASK_IMIX | SOUND_MASK_MONITOR); } for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if ((flag & (1 << j)) == 0) continue; i = 0; printed = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget->enable == 0) continue; if (!((pdevinfo->playas >= 0 && ctl->widget->bindas == pdevinfo->playas) || (pdevinfo->recas >= 0 && ctl->widget->bindas == pdevinfo->recas) || (ctl->widget->bindas == -2 && pdevinfo->index == 0))) continue; if ((ctl->ossmask & (1 << j)) == 0) continue; if (printed == 0) { if (banner != NULL) { device_printf(pdevinfo->dev, "%s", banner); } else { device_printf(pdevinfo->dev, "Unknown Ctl"); } printf(" (OSS: %s)", hdaa_audio_ctl_ossmixer_mask2allname(1 << j, buf, sizeof(buf))); if (pdevinfo->ossmask & (1 << j)) { printf(": %+d/%+ddB\n", pdevinfo->minamp[j] / 4, pdevinfo->maxamp[j] / 4); } else printf("\n"); printed = 1; } device_printf(pdevinfo->dev, " +- ctl %2d (nid %3d %s", i, ctl->widget->nid, (ctl->ndir == HDAA_CTL_IN)?"in ":"out"); if (ctl->ndir == HDAA_CTL_IN && ctl->ndir == ctl->dir) printf(" %2d): ", ctl->index); else printf("): "); if (ctl->step > 0) { printf("%+d/%+ddB (%d steps)%s\n", MINQDB(ctl) / 4, MAXQDB(ctl) / 4, ctl->step + 1, ctl->mute?" + mute":""); } else printf("%s\n", ctl->mute?"mute":""); } } if (printed) device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_audio_formats(device_t dev, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { device_printf(dev, " Stream cap: 0x%08x", cap); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) printf(" AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) printf(" FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) printf(" PCM"); printf("\n"); } cap = pcmcap; if (cap != 0) { device_printf(dev, " PCM cap: 0x%08x", cap); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) printf(" 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) printf(" 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) printf(" 32"); printf(" bits,"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) printf(" 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) printf(" 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) printf(" 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) printf(" 44"); printf(" 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) printf(" 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) printf(" 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) printf(" 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) printf(" 192"); printf(" KHz\n"); } } static void hdaa_dump_pin(struct hdaa_widget *w) { uint32_t pincap; pincap = w->wclass.pin.cap; device_printf(w->devinfo->dev, " Pin cap: 0x%08x", pincap); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) printf(" ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) printf(" TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) printf(" PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) printf(" HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) printf(" OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) printf(" IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) printf(" BAL"); if (HDA_PARAM_PIN_CAP_HDMI(pincap)) printf(" HDMI"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { printf(" VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) printf(" 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) printf(" 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) printf(" 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) printf(" GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) printf(" HIZ"); printf(" ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) printf(" EAPD"); if (HDA_PARAM_PIN_CAP_DP(pincap)) printf(" DP"); if (HDA_PARAM_PIN_CAP_HBR(pincap)) printf(" HBR"); printf("\n"); device_printf(w->devinfo->dev, " Pin config: 0x%08x\n", w->wclass.pin.config); device_printf(w->devinfo->dev, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) printf(" HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) printf(" IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) printf(" OUT"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03) printf(" HBR"); else if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) printf(" EPTs"); } else { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) printf(" VREFs"); } printf("\n"); } static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf) { device_printf(w->devinfo->dev, "%2d %08x %-2d %-2d " "%-13s %-5s %-7s %-10s %-7s %d%s\n", w->nid, conf, HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf), (w->enable == 0)?" DISA":""); } static void hdaa_dump_pin_configs(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; device_printf(devinfo->dev, "nid 0x as seq " "device conn jack loc color misc\n"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_dump_pin_config(w, w->wclass.pin.config); } } static void hdaa_dump_amp(device_t dev, uint32_t cap, const char *banner) { int offset, size, step; offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap); device_printf(dev, " %s amp: 0x%08x " "mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n", banner, cap, HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), step, size, offset, ((0 - offset) * (size + 1)) / 4, ((step - offset) * (size + 1)) / 4); } static void hdaa_dump_nodes(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w, *cw; char buf[64]; int i, j; device_printf(devinfo->dev, "\n"); device_printf(devinfo->dev, "Default parameters:\n"); hdaa_dump_audio_formats(devinfo->dev, devinfo->supp_stream_formats, devinfo->supp_pcm_size_rate); hdaa_dump_amp(devinfo->dev, devinfo->inamp_cap, " Input"); hdaa_dump_amp(devinfo->dev, devinfo->outamp_cap, "Output"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) { device_printf(devinfo->dev, "Ghost widget nid=%d\n", i); continue; } device_printf(devinfo->dev, "\n"); device_printf(devinfo->dev, " nid: %d%s\n", w->nid, (w->enable == 0) ? " [DISABLED]" : ""); device_printf(devinfo->dev, " Name: %s\n", w->name); device_printf(devinfo->dev, " Widget cap: 0x%08x", w->param.widget_cap); if (w->param.widget_cap & 0x0ee1) { if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap)) printf(" LRSWAP"); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap)) printf(" PWR"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) printf(" DIGITAL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) printf(" UNSOL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) printf(" PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) printf(" STRIPE(x%d)", 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) printf(" STEREO"); else if (j > 1) printf(" %dCH", j + 1); } printf("\n"); if (w->bindas != -1) { device_printf(devinfo->dev, " Association: %d (0x%04x)\n", w->bindas, w->bindseqmask); } if (w->ossmask != 0 || w->ossdev >= 0) { device_printf(devinfo->dev, " OSS: %s", hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) printf(" (%s)", ossnames[w->ossdev]); printf("\n"); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_dump_audio_formats(devinfo->dev, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) hdaa_dump_pin(w); if (w->param.eapdbtl != HDA_INVALID) device_printf(devinfo->dev, " EAPD: 0x%08x\n", w->param.eapdbtl); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdaa_dump_amp(devinfo->dev, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdaa_dump_amp(devinfo->dev, w->param.inamp_cap, " Input"); if (w->nconns > 0) device_printf(devinfo->dev, " Connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); device_printf(devinfo->dev, " + %s<- nid=%d [%s]", (w->connsenable[j] == 0)?"[DISABLED] ":"", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) printf(" [UNKNOWN]"); else if (cw->enable == 0) printf(" [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) printf(" (selected)"); printf("\n"); } } } static void hdaa_dump_dst_nid(struct hdaa_pcm_devinfo *pdevinfo, nid_t nid, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; char buf[64]; int i; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; if (depth == 0) device_printf(pdevinfo->dev, "%*s", 4, ""); else device_printf(pdevinfo->dev, "%*s + <- ", 4 + (depth - 1) * 7, ""); printf("nid=%d [%s]", w->nid, w->name); if (depth > 0) { if (w->ossmask == 0) { printf("\n"); return; } printf(" [src: %s]", hdaa_audio_ctl_ossmixer_mask2allname( w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) { printf("\n"); return; } } printf("\n"); for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->bindas == -1) continue; hdaa_dump_dst_nid(pdevinfo, w->conns[i], depth + 1); } } static void hdaa_dump_dac(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as; struct hdaa_widget *w; nid_t *nids; int chid, i; if (pdevinfo->playas < 0) return; device_printf(pdevinfo->dev, "Playback:\n"); chid = devinfo->as[pdevinfo->playas].chans[0]; hdaa_dump_audio_formats(pdevinfo->dev, devinfo->chans[chid].supp_stream_formats, devinfo->chans[chid].supp_pcm_size_rate); for (i = 0; i < devinfo->as[pdevinfo->playas].num_chans; i++) { chid = devinfo->as[pdevinfo->playas].chans[i]; device_printf(pdevinfo->dev, " DAC:"); for (nids = devinfo->chans[chid].io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL || w->enable == 0) continue; device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, as->pins[i], 0); } device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_adc(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; nid_t *nids; int chid, i; if (pdevinfo->recas < 0) return; device_printf(pdevinfo->dev, "Record:\n"); chid = devinfo->as[pdevinfo->recas].chans[0]; hdaa_dump_audio_formats(pdevinfo->dev, devinfo->chans[chid].supp_stream_formats, devinfo->chans[chid].supp_pcm_size_rate); for (i = 0; i < devinfo->as[pdevinfo->recas].num_chans; i++) { chid = devinfo->as[pdevinfo->recas].chans[i]; device_printf(pdevinfo->dev, " ADC:"); for (nids = devinfo->chans[chid].io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->bindas != pdevinfo->recas) continue; device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, i, 0); } device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_mix(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; int i; int printed = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev != SOUND_MIXER_IMIX) continue; if (w->bindas != pdevinfo->recas) continue; if (printed == 0) { printed = 1; device_printf(pdevinfo->dev, "Input Mix:\n"); } device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, i, 0); } if (printed) device_printf(pdevinfo->dev, "\n"); } static void hdaa_pindump(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; uint32_t res, pincap, delay; int i; device_printf(dev, "Dumping AFG pins:\n"); device_printf(dev, "nid 0x as seq " "device conn jack loc color misc\n"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_dump_pin_config(w, w->wclass.pin.config); pincap = w->wclass.pin.cap; device_printf(dev, " Caps: %2s %3s %2s %4s %4s", HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)?"IN":"", HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)?"OUT":"", HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)?"HP":"", HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)?"EAPD":"", HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)?"VREF":""); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap) || HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) { if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) { delay = 0; hda_command(dev, HDA_CMD_SET_PIN_SENSE(0, w->nid, 0)); do { res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); if (res != 0x7fffffff && res != 0xffffffff) break; DELAY(10); } while (++delay < 10000); } else { delay = 0; res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); } printf(" Sense: 0x%08x (%sconnected%s)", res, (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis", (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap) && (res & HDA_CMD_GET_PIN_SENSE_ELD_VALID)) ? ", ELD valid" : ""); if (delay > 0) printf(" delay %dus", delay * 10); } printf("\n"); } device_printf(dev, "NumGPIO=%d NumGPO=%d NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap)); hdaa_dump_gpi(devinfo); hdaa_dump_gpio(devinfo); hdaa_dump_gpo(devinfo); } static void hdaa_configure(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_audio_ctl *ctl; int i; HDA_BOOTHVERBOSE( device_printf(dev, "Applying built-in patches...\n"); ); hdaa_patch(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying local patches...\n"); ); hdaa_local_patch(devinfo); hdaa_audio_postprocess(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing Ctls...\n"); ); hdaa_audio_ctl_parse(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling nonaudio...\n"); ); hdaa_audio_disable_nonaudio(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTVERBOSE( device_printf(dev, "Patched pins configuration:\n"); hdaa_dump_pin_configs(devinfo); ); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing pin associations...\n"); ); hdaa_audio_as_parse(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Building AFG tree...\n"); ); hdaa_audio_build_tree(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling unassociated " "widgets...\n"); ); hdaa_audio_disable_unas(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling nonselected " "inputs...\n"); ); hdaa_audio_disable_notselected(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling " "crossassociatement connections...\n"); ); hdaa_audio_disable_crossas(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Binding associations to channels...\n"); ); hdaa_audio_bind_as(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Assigning names to signal sources...\n"); ); hdaa_audio_assign_names(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Preparing PCM devices...\n"); ); hdaa_prepare_pcms(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Assigning mixers to the tree...\n"); ); hdaa_audio_assign_mixers(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Preparing pin controls...\n"); ); hdaa_audio_prepare_pin_ctrl(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "AFG commit...\n"); ); hdaa_audio_commit(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying direct built-in patches...\n"); ); hdaa_patch_direct(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Creating PCM devices...\n"); ); hdaa_unlock(devinfo); hdaa_create_pcms(devinfo); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense init...\n"); ); hdaa_sense_init(devinfo); HDA_BOOTVERBOSE( if (devinfo->quirks != 0) { device_printf(dev, "FG config/quirks:"); for (i = 0; i < nitems(hdaa_quirks_tab); i++) { if ((devinfo->quirks & hdaa_quirks_tab[i].value) == hdaa_quirks_tab[i].value) printf(" %s", hdaa_quirks_tab[i].key); } printf("\n"); } ); HDA_BOOTHVERBOSE( device_printf(dev, "\n"); device_printf(dev, "+-----------+\n"); device_printf(dev, "| HDA NODES |\n"); device_printf(dev, "+-----------+\n"); hdaa_dump_nodes(devinfo); device_printf(dev, "\n"); device_printf(dev, "+----------------+\n"); device_printf(dev, "| HDA AMPLIFIERS |\n"); device_printf(dev, "+----------------+\n"); device_printf(dev, "\n"); i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { device_printf(dev, "%3d: nid %3d %s (%s) index %d", i, (ctl->widget != NULL) ? ctl->widget->nid : -1, (ctl->ndir == HDAA_CTL_IN)?"in ":"out", (ctl->dir == HDAA_CTL_IN)?"in ":"out", ctl->index); if (ctl->childwidget != NULL) printf(" cnid %3d", ctl->childwidget->nid); else printf(" "); printf(" ossmask=0x%08x\n", ctl->ossmask); device_printf(dev, " mute: %d step: %3d size: %3d off: %3d%s\n", ctl->mute, ctl->step, ctl->size, ctl->offset, (ctl->enable == 0) ? " [DISABLED]" : ((ctl->ossmask == 0) ? " [UNUSED]" : "")); } device_printf(dev, "\n"); ); } static void hdaa_unconfigure(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; int i, j; HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense deinit...\n"); ); hdaa_sense_deinit(devinfo); free(devinfo->ctl, M_HDAA); devinfo->ctl = NULL; devinfo->ctlcnt = 0; free(devinfo->as, M_HDAA); devinfo->as = NULL; devinfo->ascnt = 0; free(devinfo->devs, M_HDAA); devinfo->devs = NULL; devinfo->num_devs = 0; free(devinfo->chans, M_HDAA); devinfo->chans = NULL; devinfo->num_chans = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; w->enable = 1; w->selconn = -1; w->pflags = 0; w->bindas = -1; w->bindseqmask = 0; w->ossdev = -1; w->ossmask = 0; for (j = 0; j < w->nconns; j++) w->connsenable[j] = 1; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) w->wclass.pin.config = w->wclass.pin.newconf; if (w->eld != NULL) { w->eld_len = 0; free(w->eld, M_HDAA); w->eld = NULL; } } } static int hdaa_sysctl_gpi_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpi; uint32_t data = 0; buf[0] = 0; hdaa_lock(devinfo); numgpi = HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); if (numgpi > 0) { data = hda_command(dev, HDA_CMD_GET_GPI_DATA(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpi; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d", n != 0 ? " " : "", i, ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpio_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpio; uint32_t data = 0, enable = 0, dir = 0; buf[0] = 0; hdaa_lock(devinfo); numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); if (numgpio > 0) { data = hda_command(dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); enable = hda_command(dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); dir = hda_command(dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpio; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=", n != 0 ? " " : "", i); if ((enable & (1 << i)) == 0) { n += snprintf(buf + n, sizeof(buf) - n, "disabled"); continue; } n += snprintf(buf + n, sizeof(buf) - n, "%sput(%d)", ((dir >> i) & 1) ? "out" : "in", ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpio_config(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; char buf[256]; int error, n = 0, i, numgpio; uint32_t gpio, x; gpio = devinfo->newgpio; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); buf[0] = 0; for (i = 0; i < numgpio; i++) { x = (gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s", n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) gpio = strtol(buf + 2, NULL, 16); else gpio = hdaa_gpio_patch(gpio, buf); hdaa_lock(devinfo); devinfo->newgpio = devinfo->gpio = gpio; hdaa_gpio_commit(devinfo); hdaa_unlock(devinfo); return (0); } static int hdaa_sysctl_gpo_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpo; uint32_t data = 0; buf[0] = 0; hdaa_lock(devinfo); numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); if (numgpo > 0) { data = hda_command(dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpo; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d", n != 0 ? " " : "", i, ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpo_config(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; char buf[256]; int error, n = 0, i, numgpo; uint32_t gpo, x; gpo = devinfo->newgpo; numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); buf[0] = 0; for (i = 0; i < numgpo; i++) { x = (gpo & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s", n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) gpo = strtol(buf + 2, NULL, 16); else gpo = hdaa_gpio_patch(gpo, buf); hdaa_lock(devinfo); devinfo->newgpo = devinfo->gpo = gpo; hdaa_gpo_commit(devinfo); hdaa_unlock(devinfo); return (0); } static int hdaa_sysctl_reconfig(SYSCTL_HANDLER_ARGS) { device_t dev; struct hdaa_devinfo *devinfo; int error, val; dev = oidp->oid_arg1; devinfo = device_get_softc(dev); if (devinfo == NULL) return (EINVAL); val = 0; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL || val == 0) return (error); HDA_BOOTHVERBOSE( device_printf(dev, "Reconfiguration...\n"); ); bus_topo_lock(); if ((error = device_delete_children(dev)) != 0) { bus_topo_unlock(); return (error); } hdaa_lock(devinfo); hdaa_unconfigure(dev); hdaa_configure(dev); hdaa_unlock(devinfo); bus_attach_children(dev); HDA_BOOTHVERBOSE( device_printf(dev, "Reconfiguration done\n"); ); bus_topo_unlock(); return (0); } static int hdaa_suspend(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int i; HDA_BOOTHVERBOSE( device_printf(dev, "Suspend...\n"); ); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Stop streams...\n"); ); for (i = 0; i < devinfo->num_chans; i++) { if (devinfo->chans[i].flags & HDAA_CHN_RUNNING) { devinfo->chans[i].flags |= HDAA_CHN_SUSPEND; hdaa_channel_stop(&devinfo->chans[i]); } } HDA_BOOTHVERBOSE( device_printf(dev, "Power down FG" " nid=%d to the D3 state...\n", devinfo->nid); ); hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, devinfo->nid, HDA_CMD_POWER_STATE_D3)); callout_stop(&devinfo->poll_jack); hdaa_unlock(devinfo); callout_drain(&devinfo->poll_jack); HDA_BOOTHVERBOSE( device_printf(dev, "Suspend done\n"); ); return (0); } static int hdaa_resume(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int i; HDA_BOOTHVERBOSE( device_printf(dev, "Resume...\n"); ); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Power up audio FG nid=%d...\n", devinfo->nid); ); hdaa_powerup(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "AFG commit...\n"); ); hdaa_audio_commit(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying direct built-in patches...\n"); ); hdaa_patch_direct(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense init...\n"); ); hdaa_sense_init(devinfo); hdaa_unlock(devinfo); for (i = 0; i < devinfo->num_devs; i++) { struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i]; HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "OSS mixer reinitialization...\n"); ); if (mixer_reinit(pdevinfo->dev) == -1) device_printf(pdevinfo->dev, "unable to reinitialize the mixer\n"); } hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Start streams...\n"); ); for (i = 0; i < devinfo->num_chans; i++) { if (devinfo->chans[i].flags & HDAA_CHN_SUSPEND) { devinfo->chans[i].flags &= ~HDAA_CHN_SUSPEND; hdaa_channel_start(&devinfo->chans[i]); } } hdaa_unlock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Resume done\n"); ); return (0); } static int hdaa_probe(device_t dev) { const char *pdesc; if (hda_get_node_type(dev) != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) return (ENXIO); pdesc = device_get_desc(device_get_parent(dev)); device_set_descf(dev, "%.*s Audio Function Group", (int)(strlen(pdesc) - 10), pdesc); return (BUS_PROBE_DEFAULT); } static int hdaa_attach(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); uint32_t res; nid_t nid = hda_get_node_id(dev); devinfo->dev = dev; devinfo->lock = HDAC_GET_MTX(device_get_parent(dev), dev); devinfo->nid = nid; devinfo->newquirks = -1; devinfo->newgpio = -1; devinfo->newgpo = -1; callout_init(&devinfo->poll_jack, 1); devinfo->poll_ival = hz; hdaa_lock(devinfo); res = hda_command(dev, HDA_CMD_GET_PARAMETER(0 , nid, HDA_PARAM_SUB_NODE_COUNT)); hdaa_unlock(devinfo); devinfo->nodecnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(res); devinfo->startnode = HDA_PARAM_SUB_NODE_COUNT_START(res); devinfo->endnode = devinfo->startnode + devinfo->nodecnt; HDA_BOOTVERBOSE( device_printf(dev, "Subsystem ID: 0x%08x\n", hda_get_subsystem_id(dev)); ); HDA_BOOTHVERBOSE( device_printf(dev, "Audio Function Group at nid=%d: %d subnodes %d-%d\n", nid, devinfo->nodecnt, devinfo->startnode, devinfo->endnode - 1); ); if (devinfo->nodecnt > 0) devinfo->widget = malloc(sizeof(*(devinfo->widget)) * devinfo->nodecnt, M_HDAA, M_WAITOK | M_ZERO); else devinfo->widget = NULL; hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Powering up...\n"); ); hdaa_powerup(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing audio FG...\n"); ); hdaa_audio_parse(devinfo); HDA_BOOTVERBOSE( device_printf(dev, "Original pins configuration:\n"); hdaa_dump_pin_configs(devinfo); ); hdaa_configure(dev); hdaa_unlock(devinfo); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, &devinfo->newquirks, 0, hdaa_sysctl_quirks, "A", "Configuration options"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpi_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpi_state, "A", "GPI state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpio_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpio_state, "A", "GPIO state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpio_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpio_config, "A", "GPIO configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpo_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpo_state, "A", "GPO state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpo_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpo_config, "A", "GPO configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "reconfig", CTLTYPE_INT | CTLFLAG_RW, dev, 0, hdaa_sysctl_reconfig, "I", "Reprocess configuration"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "init_clear", CTLFLAG_RW, &devinfo->init_clear, 1,"Clear initial pin widget configuration"); bus_attach_children(dev); return (0); } static int hdaa_detach(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int error; if ((error = bus_generic_detach(dev)) != 0) return (error); hdaa_lock(devinfo); hdaa_unconfigure(dev); devinfo->poll_ival = 0; callout_stop(&devinfo->poll_jack); hdaa_unlock(devinfo); callout_drain(&devinfo->poll_jack); free(devinfo->widget, M_HDAA); return (0); } static int hdaa_print_child(device_t dev, device_t child) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(child); struct hdaa_audio_as *as; int retval, first = 1, i; retval = bus_print_child_header(dev, child); retval += printf(" at nid "); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; retval += printf("%s%d", first ? "" : ",", as->pins[i]); first = 0; } } if (pdevinfo->recas >= 0) { if (pdevinfo->playas >= 0) { retval += printf(" and "); first = 1; } as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; retval += printf("%s%d", first ? "" : ",", as->pins[i]); first = 0; } } retval += bus_print_child_footer(dev, child); return (retval); } static int hdaa_child_location(device_t dev, device_t child, struct sbuf *sb) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(child); struct hdaa_audio_as *as; int first = 1, i; sbuf_printf(sb, "nid="); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; sbuf_printf(sb, "%s%d", first ? "" : ",", as->pins[i]); first = 0; } } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; sbuf_printf(sb, "%s%d", first ? "" : ",", as->pins[i]); first = 0; } } return (0); } static void hdaa_stream_intr(device_t dev, int dir, int stream) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_chan *ch; int i; for (i = 0; i < devinfo->num_chans; i++) { ch = &devinfo->chans[i]; if (!(ch->flags & HDAA_CHN_RUNNING)) continue; if (ch->dir == ((dir == 1) ? PCMDIR_PLAY : PCMDIR_REC) && ch->sid == stream) { hdaa_unlock(devinfo); chn_intr(ch->c); hdaa_lock(devinfo); } } } static void hdaa_unsol_intr(device_t dev, uint32_t resp) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; int i, tag, flags; HDA_BOOTHVERBOSE( device_printf(dev, "Unsolicited response %08x\n", resp); ); tag = resp >> 26; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->unsol != tag) continue; if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) || HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) flags = resp & 0x03; else flags = 0x01; if (flags & 0x01) hdaa_presence_handler(w); if (flags & 0x02) hdaa_eld_handler(w); } } static device_method_t hdaa_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdaa_probe), DEVMETHOD(device_attach, hdaa_attach), DEVMETHOD(device_detach, hdaa_detach), DEVMETHOD(device_suspend, hdaa_suspend), DEVMETHOD(device_resume, hdaa_resume), /* Bus interface */ DEVMETHOD(bus_print_child, hdaa_print_child), DEVMETHOD(bus_child_location, hdaa_child_location), DEVMETHOD(hdac_stream_intr, hdaa_stream_intr), DEVMETHOD(hdac_unsol_intr, hdaa_unsol_intr), DEVMETHOD(hdac_pindump, hdaa_pindump), DEVMETHOD_END }; static driver_t hdaa_driver = { "hdaa", hdaa_methods, sizeof(struct hdaa_devinfo), }; DRIVER_MODULE(snd_hda, hdacc, hdaa_driver, NULL, NULL); static void hdaa_chan_formula(struct hdaa_devinfo *devinfo, int asid, char *buf, int buflen) { struct hdaa_audio_as *as; int c; as = &devinfo->as[asid]; c = devinfo->chans[as->chans[0]].channels; if (c == 1) snprintf(buf, buflen, "mono"); else if (c == 2) { if (as->hpredir < 0) buf[0] = 0; else snprintf(buf, buflen, "2.0"); } else if (as->pinset == 0x0003) snprintf(buf, buflen, "3.1"); else if (as->pinset == 0x0005 || as->pinset == 0x0011) snprintf(buf, buflen, "4.0"); else if (as->pinset == 0x0007 || as->pinset == 0x0013) snprintf(buf, buflen, "5.1"); else if (as->pinset == 0x0017) snprintf(buf, buflen, "7.1"); else snprintf(buf, buflen, "%dch", c); if (as->hpredir >= 0) strlcat(buf, "+HP", buflen); } static int hdaa_chan_type(struct hdaa_devinfo *devinfo, int asid) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, t = -1, t1; as = &devinfo->as[asid]; for (i = 0; i < 16; i++) { w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; t1 = HDA_CONFIG_DEFAULTCONF_DEVICE(w->wclass.pin.config); if (t == -1) t = t1; else if (t != t1) { t = -2; break; } } return (t); } static int hdaa_sysctl_32bit(SYSCTL_HANDLER_ARGS) { struct hdaa_audio_as *as = (struct hdaa_audio_as *)oidp->oid_arg1; struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_chan *ch; int error, val, i; uint32_t pcmcap; ch = &devinfo->chans[as->chans[0]]; val = (ch->bit32 == 4) ? 32 : ((ch->bit32 == 3) ? 24 : ((ch->bit32 == 2) ? 20 : 0)); error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); pcmcap = ch->supp_pcm_size_rate; if (val == 32 && HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap)) ch->bit32 = 4; else if (val == 24 && HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap)) ch->bit32 = 3; else if (val == 20 && HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap)) ch->bit32 = 2; else return (EINVAL); for (i = 1; i < as->num_chans; i++) devinfo->chans[as->chans[i]].bit32 = ch->bit32; return (0); } static int hdaa_pcm_probe(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; const char *pdesc; char chans1[8], chans2[8]; int loc1, loc2, t1, t2; if (pdevinfo->playas >= 0) loc1 = devinfo->as[pdevinfo->playas].location; else loc1 = devinfo->as[pdevinfo->recas].location; if (pdevinfo->recas >= 0) loc2 = devinfo->as[pdevinfo->recas].location; else loc2 = loc1; if (loc1 != loc2) loc1 = -2; if (loc1 >= 0 && HDA_LOCS[loc1][0] == '0') loc1 = -2; chans1[0] = 0; chans2[0] = 0; t1 = t2 = -1; if (pdevinfo->playas >= 0) { hdaa_chan_formula(devinfo, pdevinfo->playas, chans1, sizeof(chans1)); t1 = hdaa_chan_type(devinfo, pdevinfo->playas); } if (pdevinfo->recas >= 0) { hdaa_chan_formula(devinfo, pdevinfo->recas, chans2, sizeof(chans2)); t2 = hdaa_chan_type(devinfo, pdevinfo->recas); } if (chans1[0] != 0 || chans2[0] != 0) { if (chans1[0] == 0 && pdevinfo->playas >= 0) snprintf(chans1, sizeof(chans1), "2.0"); else if (chans2[0] == 0 && pdevinfo->recas >= 0) snprintf(chans2, sizeof(chans2), "2.0"); if (strcmp(chans1, chans2) == 0) chans2[0] = 0; } if (t1 == -1) t1 = t2; else if (t2 == -1) t2 = t1; if (t1 != t2) t1 = -2; if (pdevinfo->digital) t1 = -2; pdesc = device_get_desc(device_get_parent(dev)); device_set_descf(dev, "%.*s (%s%s%s%s%s%s%s%s%s)", (int)(strlen(pdesc) - 21), pdesc, loc1 >= 0 ? HDA_LOCS[loc1] : "", loc1 >= 0 ? " " : "", (pdevinfo->digital == 0x7)?"HDMI/DP": ((pdevinfo->digital == 0x5)?"DisplayPort": ((pdevinfo->digital == 0x3)?"HDMI": ((pdevinfo->digital)?"Digital":"Analog"))), chans1[0] ? " " : "", chans1, chans2[0] ? "/" : "", chans2, t1 >= 0 ? " " : "", t1 >= 0 ? HDA_DEVS[t1] : ""); return (BUS_PROBE_SPECIFIC); } static int hdaa_pcm_attach(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as; struct snddev_info *d; char status[SND_STATUSLEN]; int i; pdevinfo->chan_size = pcm_getbuffersize(dev, HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX); HDA_BOOTVERBOSE( hdaa_dump_dac(pdevinfo); hdaa_dump_adc(pdevinfo); hdaa_dump_mix(pdevinfo); hdaa_dump_ctls(pdevinfo, "Master Volume", SOUND_MASK_VOLUME); hdaa_dump_ctls(pdevinfo, "PCM Volume", SOUND_MASK_PCM); hdaa_dump_ctls(pdevinfo, "CD Volume", SOUND_MASK_CD); hdaa_dump_ctls(pdevinfo, "Microphone Volume", SOUND_MASK_MIC); hdaa_dump_ctls(pdevinfo, "Microphone2 Volume", SOUND_MASK_MONITOR); hdaa_dump_ctls(pdevinfo, "Line-in Volume", SOUND_MASK_LINE); hdaa_dump_ctls(pdevinfo, "Speaker/Beep Volume", SOUND_MASK_SPEAKER); hdaa_dump_ctls(pdevinfo, "Recording Level", SOUND_MASK_RECLEV); hdaa_dump_ctls(pdevinfo, "Input Mix Level", SOUND_MASK_IMIX); hdaa_dump_ctls(pdevinfo, "Input Monitoring Level", SOUND_MASK_IGAIN); hdaa_dump_ctls(pdevinfo, NULL, 0); ); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= HDA_BLK_ALIGN; if (i < HDA_BLK_MIN) i = HDA_BLK_MIN; pdevinfo->chan_blkcnt = pdevinfo->chan_size / i; i = 0; while (pdevinfo->chan_blkcnt >> i) i++; pdevinfo->chan_blkcnt = 1 << (i - 1); if (pdevinfo->chan_blkcnt < HDA_BDL_MIN) pdevinfo->chan_blkcnt = HDA_BDL_MIN; else if (pdevinfo->chan_blkcnt > HDA_BDL_MAX) pdevinfo->chan_blkcnt = HDA_BDL_MAX; } else pdevinfo->chan_blkcnt = HDA_BDL_DEFAULT; /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); HDA_BOOTHVERBOSE( device_printf(dev, "OSS mixer initialization...\n"); ); if (mixer_init(dev, &hdaa_audio_ctl_ossmixer_class, pdevinfo) != 0) device_printf(dev, "Can't register mixer\n"); HDA_BOOTHVERBOSE( device_printf(dev, "Registering PCM channels...\n"); ); pcm_init(dev, pdevinfo); pdevinfo->registered++; d = device_get_softc(dev); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < as->num_chans; i++) pcm_addchan(dev, PCMDIR_PLAY, &hdaa_channel_class, &devinfo->chans[as->chans[i]]); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, as, sizeof(as), hdaa_sysctl_32bit, "I", "Resolution of 32bit samples (20/24/32bit)"); } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < as->num_chans; i++) pcm_addchan(dev, PCMDIR_REC, &hdaa_channel_class, &devinfo->chans[as->chans[i]]); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, as, sizeof(as), hdaa_sysctl_32bit, "I", "Resolution of 32bit samples (20/24/32bit)"); pdevinfo->autorecsrc = 2; resource_int_value(device_get_name(dev), device_get_unit(dev), "rec.autosrc", &pdevinfo->autorecsrc); SYSCTL_ADD_INT(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "autosrc", CTLFLAG_RW, &pdevinfo->autorecsrc, 0, "Automatic recording source selection"); } if (pdevinfo->mixer != NULL) { hdaa_audio_ctl_set_defaults(pdevinfo); hdaa_lock(devinfo); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; hdaa_channels_handler(as); } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; hdaa_autorecsrc_handler(as, NULL); hdaa_channels_handler(as); } hdaa_unlock(devinfo); } snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); return (pcm_register(dev, status)); } static int hdaa_pcm_detach(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); int err; if (pdevinfo->registered > 0) { err = pcm_unregister(dev); if (err != 0) return (err); } return (0); } static device_method_t hdaa_pcm_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdaa_pcm_probe), DEVMETHOD(device_attach, hdaa_pcm_attach), DEVMETHOD(device_detach, hdaa_pcm_detach), DEVMETHOD_END }; static driver_t hdaa_pcm_driver = { "pcm", hdaa_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hda_pcm, hdaa, hdaa_pcm_driver, NULL, NULL); MODULE_DEPEND(snd_hda, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hda, 1); diff --git a/sys/dev/sound/pci/hdsp-pcm.c b/sys/dev/sound/pci/hdsp-pcm.c index 5ac571e64fde..bc11199f15e8 100644 --- a/sys/dev/sound/pci/hdsp-pcm.c +++ b/sys/dev/sound/pci/hdsp-pcm.c @@ -1,1136 +1,1136 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2021 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * RME HDSP driver for FreeBSD (pcm-part). * Supported cards: HDSP 9632, HDSP 9652. */ #include #include #include #include #include #include #define HDSP_MATRIX_MAX 8 struct hdsp_latency { uint32_t n; uint32_t period; float ms; }; static struct hdsp_latency latency_map[] = { { 7, 32, 0.7 }, { 0, 64, 1.5 }, { 1, 128, 3 }, { 2, 256, 6 }, { 3, 512, 12 }, { 4, 1024, 23 }, { 5, 2048, 46 }, { 6, 4096, 93 }, { 0, 0, 0 }, }; struct hdsp_rate { uint32_t speed; uint32_t reg; }; static struct hdsp_rate rate_map[] = { { 32000, (HDSP_FREQ_32000) }, { 44100, (HDSP_FREQ_44100) }, { 48000, (HDSP_FREQ_48000) }, { 64000, (HDSP_FREQ_32000 | HDSP_FREQ_DOUBLE) }, { 88200, (HDSP_FREQ_44100 | HDSP_FREQ_DOUBLE) }, { 96000, (HDSP_FREQ_48000 | HDSP_FREQ_DOUBLE) }, { 128000, (HDSP_FREQ_32000 | HDSP_FREQ_QUAD) }, { 176400, (HDSP_FREQ_44100 | HDSP_FREQ_QUAD) }, { 192000, (HDSP_FREQ_48000 | HDSP_FREQ_QUAD) }, { 0, 0 }, }; static uint32_t hdsp_adat_slot_map(uint32_t speed) { /* ADAT slot bitmap depends on sample rate. */ if (speed <= 48000) return (0x000000ff); /* 8 channels single speed. */ else if (speed <= 96000) return (0x000000aa); /* 4 channels (1,3,5,7) double speed. */ else return (0x00000000); /* ADAT disabled at quad speed. */ } static uint32_t hdsp_port_slot_map(uint32_t ports, uint32_t speed) { uint32_t slot_map = 0; if (ports & HDSP_CHAN_9632_ALL) { /* Map HDSP 9632 ports to slot bitmap. */ if (ports & HDSP_CHAN_9632_ADAT) slot_map |= (hdsp_adat_slot_map(speed) << 0); if (ports & HDSP_CHAN_9632_SPDIF) slot_map |= (0x03 << 8); /* 2 channels SPDIF. */ if (ports & HDSP_CHAN_9632_LINE) slot_map |= (0x03 << 10); /* 2 channels line. */ if (ports & HDSP_CHAN_9632_EXT) slot_map |= (0x0f << 12); /* 4 channels extension. */ } else if ((ports & HDSP_CHAN_9652_ALL) && (speed <= 96000)) { /* Map HDSP 9652 ports to slot bitmap, no quad speed. */ if (ports & HDSP_CHAN_9652_ADAT1) slot_map |= (hdsp_adat_slot_map(speed) << 0); if (ports & HDSP_CHAN_9652_ADAT2) slot_map |= (hdsp_adat_slot_map(speed) << 8); if (ports & HDSP_CHAN_9652_ADAT3) slot_map |= (hdsp_adat_slot_map(speed) << 16); if (ports & HDSP_CHAN_9652_SPDIF) slot_map |= (0x03 << 24); /* 2 channels SPDIF. */ } return (slot_map); } static uint32_t hdsp_slot_first(uint32_t slots) { return (slots & (~(slots - 1))); /* Extract first bit set. */ } static uint32_t hdsp_slot_first_row(uint32_t slots) { uint32_t ends; /* Ends of slot rows are followed by a slot which is not in the set. */ ends = slots & (~(slots >> 1)); /* First row of contiguous slots ends in the first row end. */ return (slots & (ends ^ (ends - 1))); } static uint32_t hdsp_slot_first_n(uint32_t slots, unsigned int n) { /* Clear all but the first n slots. */ for (uint32_t slot = 1; slot != 0; slot <<= 1) { if ((slots & slot) && n > 0) --n; else slots &= ~slot; } return (slots); } static unsigned int hdsp_slot_count(uint32_t slots) { return (bitcount32(slots)); } static unsigned int hdsp_slot_offset(uint32_t slots) { return (hdsp_slot_count(hdsp_slot_first(slots) - 1)); } static unsigned int hdsp_slot_channel_offset(uint32_t subset, uint32_t slots) { uint32_t preceding; /* Make sure we have a subset of slots. */ subset &= slots; /* Include all slots preceding the first one of the subset. */ preceding = slots & (hdsp_slot_first(subset) - 1); return (hdsp_slot_count(preceding)); } static uint32_t hdsp_port_first(uint32_t ports) { return (ports & (~(ports - 1))); /* Extract first bit set. */ } static unsigned int hdsp_port_slot_count(uint32_t ports, uint32_t speed) { return (hdsp_slot_count(hdsp_port_slot_map(ports, speed))); } static unsigned int hdsp_port_slot_count_max(uint32_t ports) { return (hdsp_slot_count(hdsp_port_slot_map(ports, 48000))); } static uint32_t hdsp_channel_play_ports(struct hdsp_channel *hc) { return (hc->ports & (HDSP_CHAN_9632_ALL | HDSP_CHAN_9652_ALL)); } static uint32_t hdsp_channel_rec_ports(struct hdsp_channel *hc) { return (hc->ports & (HDSP_CHAN_9632_ALL | HDSP_CHAN_9652_ALL)); } static int hdsp_hw_mixer(struct sc_chinfo *ch, unsigned int dst, unsigned int src, unsigned short data) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t value; int offset; scp = ch->parent; sc = scp->sc; offset = 0; value = (HDSP_MIN_GAIN << 16) | (uint16_t) data; if (ch->dir != PCMDIR_PLAY) return (0); switch (sc->type) { case HDSP_9632: /* Mixer is 2 rows of sources (inputs, playback) per output. */ offset = dst * (2 * HDSP_MIX_SLOTS_9632); /* Source index in the second row (playback). */ offset += HDSP_MIX_SLOTS_9632 + src; break; case HDSP_9652: /* Mixer is 2 rows of sources (inputs, playback) per output. */ offset = dst * (2 * HDSP_MIX_SLOTS_9652); /* Source index in the second row (playback). */ offset += HDSP_MIX_SLOTS_9652 + src; break; default: return (0); } /* * We have to write mixer matrix values in pairs, with the second * (odd) value in the upper 16 bits of the 32 bit value. * Make value offset even and shift value accordingly. * Assume the paired value to be silenced, since we only set gain * on the diagonal where src and dst are the same. */ if (offset % 2) { offset -= 1; value = (value << 16) | HDSP_MIN_GAIN; } hdsp_write_4(sc, HDSP_MIXER_BASE + offset * sizeof(uint16_t), value); return (0); }; static int hdspchan_setgain(struct sc_chinfo *ch) { uint32_t port, ports; uint32_t slot, slots; unsigned int offset; unsigned short volume; /* Iterate through all physical ports of the channel. */ ports = ch->ports; port = hdsp_port_first(ports); while (port != 0) { /* * Get slot map from physical port. * Unlike DMA buffers, the hardware mixer's channel mapping * does not change with double or quad speed sample rates. */ slots = hdsp_port_slot_map(port, 48000); slot = hdsp_slot_first(slots); /* Treat first slot as left channel. */ volume = ch->lvol * HDSP_MAX_GAIN / 100; while (slot != 0) { offset = hdsp_slot_offset(slot); hdsp_hw_mixer(ch, offset, offset, volume); slots &= ~slot; slot = hdsp_slot_first(slots); /* Subsequent slots all get the right channel volume. */ volume = ch->rvol * HDSP_MAX_GAIN / 100; } ports &= ~port; port = hdsp_port_first(ports); } return (0); } static int hdspmixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return (-1); mask = SOUND_MASK_PCM; if (hdsp_channel_play_ports(scp->hc)) mask |= SOUND_MASK_VOLUME; if (hdsp_channel_rec_ports(scp->hc)) mask |= SOUND_MASK_RECLEV; snd_mtxlock(sc->lock); pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, mask); snd_mtxunlock(sc->lock); return (0); } static int hdspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_pcminfo *scp; struct sc_chinfo *ch; int i; scp = mix_getdevinfo(m); #if 0 device_printf(scp->dev, "hdspmixer_set() %d %d\n", left, right); #endif for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; if ((dev == SOUND_MIXER_VOLUME && ch->dir == PCMDIR_PLAY) || (dev == SOUND_MIXER_RECLEV && ch->dir == PCMDIR_REC)) { ch->lvol = left; ch->rvol = right; if (ch->run) hdspchan_setgain(ch); } } return (0); } static kobj_method_t hdspmixer_methods[] = { KOBJMETHOD(mixer_init, hdspmixer_init), KOBJMETHOD(mixer_set, hdspmixer_set), KOBJMETHOD_END }; MIXER_DECLARE(hdspmixer); static void hdspchan_enable(struct sc_chinfo *ch, int value) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t slot, slots; unsigned int offset; int reg; scp = ch->parent; sc = scp->sc; if (ch->dir == PCMDIR_PLAY) reg = HDSP_OUT_ENABLE_BASE; else reg = HDSP_IN_ENABLE_BASE; ch->run = value; /* Iterate through all slots of the channel's physical ports. */ slots = hdsp_port_slot_map(ch->ports, sc->speed); slot = hdsp_slot_first(slots); while (slot != 0) { /* Set register to enable or disable slot. */ offset = hdsp_slot_offset(slot); hdsp_write_1(sc, reg + (4 * offset), value); slots &= ~slot; slot = hdsp_slot_first(slots); } } static int hdsp_running(struct sc_info *sc) { struct sc_pcminfo *scp; struct sc_chinfo *ch; device_t *devlist; int devcount; int i, j; int running; running = 0; devlist = NULL; devcount = 0; if (device_get_children(sc->dev, &devlist, &devcount) != 0) running = 1; /* On error, avoid channel config changes. */ for (i = 0; running == 0 && i < devcount; i++) { scp = device_get_ivars(devlist[i]); for (j = 0; j < scp->chnum; j++) { ch = &scp->chan[j]; if (ch->run) { running = 1; break; } } } #if 0 if (running == 1) device_printf(sc->dev, "hdsp is running\n"); #endif free(devlist, M_TEMP); return (running); } static void hdsp_start_audio(struct sc_info *sc) { sc->ctrl_register |= (HDSP_AUDIO_INT_ENABLE | HDSP_ENABLE); hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); } static void hdsp_stop_audio(struct sc_info *sc) { if (hdsp_running(sc) == 1) return; sc->ctrl_register &= ~(HDSP_AUDIO_INT_ENABLE | HDSP_ENABLE); hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); } static void buffer_mux_write(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int pos_end, unsigned int width, unsigned int channels) { unsigned int slot; for (; pos < pos_end; ++pos) { for (slot = 0; slot < width; slot++) { dma[slot * HDSP_CHANBUF_SAMPLES + pos] = pcm[pos * channels + slot]; } } } static void buffer_mux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t slots, unsigned int pos, unsigned int samples, unsigned int channels) { unsigned int slot_offset, width; unsigned int chan_pos; /* Translate DMA slot offset to DMA buffer offset. */ slot_offset = hdsp_slot_offset(subset); dma += slot_offset * HDSP_CHANBUF_SAMPLES; /* Channel position of the slot subset. */ chan_pos = hdsp_slot_channel_offset(subset, slots); pcm += chan_pos; /* Only copy channels supported by both hardware and pcm format. */ width = hdsp_slot_count(subset); /* Let the compiler inline and loop unroll common cases. */ if (width == 1) buffer_mux_write(dma, pcm, pos, pos + samples, 1, channels); else if (width == 2) buffer_mux_write(dma, pcm, pos, pos + samples, 2, channels); else if (width == 4) buffer_mux_write(dma, pcm, pos, pos + samples, 4, channels); else if (width == 8) buffer_mux_write(dma, pcm, pos, pos + samples, 8, channels); else buffer_mux_write(dma, pcm, pos, pos + samples, width, channels); } static void buffer_demux_read(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int pos_end, unsigned int width, unsigned int channels) { unsigned int slot; for (; pos < pos_end; ++pos) { for (slot = 0; slot < width; slot++) { pcm[pos * channels + slot] = dma[slot * HDSP_CHANBUF_SAMPLES + pos]; } } } static void buffer_demux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t slots, unsigned int pos, unsigned int samples, unsigned int channels) { unsigned int slot_offset, width; unsigned int chan_pos; /* Translate DMA slot offset to DMA buffer offset. */ slot_offset = hdsp_slot_offset(subset); dma += slot_offset * HDSP_CHANBUF_SAMPLES; /* Channel position of the slot subset. */ chan_pos = hdsp_slot_channel_offset(subset, slots); pcm += chan_pos; /* Only copy channels supported by both hardware and pcm format. */ width = hdsp_slot_count(subset); /* Let the compiler inline and loop unroll common cases. */ if (width == 1) buffer_demux_read(dma, pcm, pos, pos + samples, 1, channels); else if (width == 2) buffer_demux_read(dma, pcm, pos, pos + samples, 2, channels); else if (width == 4) buffer_demux_read(dma, pcm, pos, pos + samples, 4, channels); else if (width == 8) buffer_demux_read(dma, pcm, pos, pos + samples, 8, channels); else buffer_demux_read(dma, pcm, pos, pos + samples, width, channels); } /* Copy data between DMA and PCM buffers. */ static void buffer_copy(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, slots; uint32_t dma_pos; unsigned int pos, length, remainder, offset, buffer_size; unsigned int channels; scp = ch->parent; sc = scp->sc; channels = AFMT_CHANNEL(ch->format); /* Number of PCM channels. */ /* HDSP cards read / write a double buffer, twice the latency period. */ buffer_size = 2 * sc->period * sizeof(uint32_t); /* Derive buffer position and length to be copied. */ if (ch->dir == PCMDIR_PLAY) { /* Buffer position scaled down to a single channel. */ pos = sndbuf_getreadyptr(ch->buffer) / channels; length = sndbuf_getready(ch->buffer) / channels; /* Copy no more than 2 periods in advance. */ if (length > buffer_size) length = buffer_size; /* Skip what was already copied last time. */ offset = (ch->position + buffer_size) - pos; offset %= buffer_size; if (offset <= length) { pos = (pos + offset) % buffer_size; length -= offset; } } else { /* Buffer position scaled down to a single channel. */ pos = sndbuf_getfreeptr(ch->buffer) / channels; /* Get DMA buffer write position. */ dma_pos = hdsp_read_2(sc, HDSP_STATUS_REG); dma_pos &= HDSP_BUF_POSITION_MASK; dma_pos %= buffer_size; /* Copy what is newly available. */ length = (dma_pos + buffer_size) - pos; length %= buffer_size; } /* Position and length in samples (4 bytes). */ pos /= 4; length /= 4; buffer_size /= sizeof(uint32_t); /* Split copy length to wrap around at buffer end. */ remainder = 0; if (pos + length > buffer_size) remainder = (pos + length) - buffer_size; /* Iterate through rows of contiguous slots. */ slots = hdsp_port_slot_map(ch->ports, sc->speed); slots = hdsp_slot_first_n(slots, channels); row = hdsp_slot_first_row(slots); while (row != 0) { if (ch->dir == PCMDIR_PLAY) { buffer_mux_port(sc->pbuf, ch->data, row, slots, pos, length - remainder, channels); buffer_mux_port(sc->pbuf, ch->data, row, slots, 0, remainder, channels); } else { buffer_demux_port(sc->rbuf, ch->data, row, slots, pos, length - remainder, channels); buffer_demux_port(sc->rbuf, ch->data, row, slots, 0, remainder, channels); } slots &= ~row; row = hdsp_slot_first_row(slots); } ch->position = ((pos + length) * 4) % buffer_size; } static int clean(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t *buf; uint32_t slot, slots; unsigned int offset; scp = ch->parent; sc = scp->sc; buf = sc->rbuf; if (ch->dir == PCMDIR_PLAY) buf = sc->pbuf; /* Iterate through all of the channel's slots. */ slots = hdsp_port_slot_map(ch->ports, sc->speed); slot = hdsp_slot_first(slots); while (slot != 0) { /* Clear the slot's buffer. */ offset = hdsp_slot_offset(slot); bzero(buf + offset * HDSP_CHANBUF_SAMPLES, HDSP_CHANBUF_SIZE); slots &= ~slot; slot = hdsp_slot_first(slots); } ch->position = 0; return (0); } /* Channel interface. */ static int hdspchan_free(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; #if 0 device_printf(scp->dev, "hdspchan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_HDSP); ch->data = NULL; } if (ch->caps != NULL) { free(ch->caps, M_HDSP); ch->caps = NULL; } snd_mtxunlock(sc->lock); return (0); } static void * hdspchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int num; scp = devinfo; sc = scp->sc; snd_mtxlock(sc->lock); num = scp->chnum; ch = &scp->chan[num]; if (dir == PCMDIR_PLAY) ch->ports = hdsp_channel_play_ports(scp->hc); else ch->ports = hdsp_channel_rec_ports(scp->hc); ch->run = 0; ch->lvol = 0; ch->rvol = 0; /* Support all possible ADAT widths as channel formats. */ ch->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, hdsp_port_slot_count(ch->ports, 48000), 0); ch->cap_fmts[1] = SND_FORMAT(AFMT_S32_LE, hdsp_port_slot_count(ch->ports, 96000), 0); ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, hdsp_port_slot_count(ch->ports, 192000), 0); ch->cap_fmts[3] = 0; ch->caps = malloc(sizeof(struct pcmchan_caps), M_HDSP, M_NOWAIT); *(ch->caps) = (struct pcmchan_caps) {32000, 192000, ch->cap_fmts, 0}; /* HDSP 9652 does not support quad speed sample rates. */ if (sc->type == HDSP_9652) { ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, 2, 0); ch->caps->maxspeed = 96000; } /* Allocate maximum buffer size. */ ch->size = HDSP_CHANBUF_SIZE * hdsp_port_slot_count_max(ch->ports); ch->data = malloc(ch->size, M_HDSP, M_NOWAIT); ch->position = 0; ch->buffer = b; ch->channel = c; ch->parent = scp; ch->dir = dir; snd_mtxunlock(sc->lock); if (sndbuf_setup(ch->buffer, ch->data, ch->size) != 0) { device_printf(scp->dev, "Can't setup sndbuf.\n"); hdspchan_free(obj, ch); return (NULL); } return (ch); } static int hdspchan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "hdspchan_trigger(): start\n"); #endif hdspchan_enable(ch, 1); hdspchan_setgain(ch); hdsp_start_audio(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "hdspchan_trigger(): stop or abort\n"); #endif clean(ch); hdspchan_enable(ch, 0); hdsp_stop_audio(sc); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: if(ch->run) buffer_copy(ch); break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t hdspchan_getptr(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; uint32_t ret, pos; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); ret = hdsp_read_2(sc, HDSP_STATUS_REG); snd_mtxunlock(sc->lock); pos = ret & HDSP_BUF_POSITION_MASK; pos %= (2 * sc->period * sizeof(uint32_t)); /* Double buffer. */ pos *= AFMT_CHANNEL(ch->format); /* Hardbuf with multiple channels. */ return (pos); } static int hdspchan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scp = ch->parent; device_printf(scp->dev, "hdspchan_setformat(%d)\n", format); #endif ch->format = format; return (0); } static uint32_t hdspchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct hdsp_rate *hr; struct sc_chinfo *ch; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hr = NULL; #if 0 device_printf(scp->dev, "hdspchan_setspeed(%d)\n", speed); #endif if (hdsp_running(sc) == 1) goto end; /* HDSP 9652 only supports sample rates up to 96kHz. */ if (sc->type == HDSP_9652 && speed > 96000) speed = 96000; if (sc->force_speed > 0) speed = sc->force_speed; /* First look for equal frequency. */ for (i = 0; rate_map[i].speed != 0; i++) { if (rate_map[i].speed == speed) hr = &rate_map[i]; } /* If no match, just find nearest. */ if (hr == NULL) { for (i = 0; rate_map[i].speed != 0; i++) { hr = &rate_map[i]; threshold = hr->speed + ((rate_map[i + 1].speed != 0) ? ((rate_map[i + 1].speed - hr->speed) >> 1) : 0); if (speed < threshold) break; } } /* Write frequency on the device. */ sc->ctrl_register &= ~HDSP_FREQ_MASK; sc->ctrl_register |= hr->reg; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); if (sc->type == HDSP_9632) { /* Set DDS value. */ hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(hr->speed)); } sc->speed = hr->speed; end: return (sc->speed); } static uint32_t hdspchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct hdsp_latency *hl; struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hl = NULL; #if 0 device_printf(scp->dev, "hdspchan_setblocksize(%d)\n", blocksize); #endif if (hdsp_running(sc) == 1) goto end; if (blocksize > HDSP_LAT_BYTES_MAX) blocksize = HDSP_LAT_BYTES_MAX; else if (blocksize < HDSP_LAT_BYTES_MIN) blocksize = HDSP_LAT_BYTES_MIN; blocksize /= 4 /* samples */; if (sc->force_period > 0) blocksize = sc->force_period; /* First look for equal latency. */ for (i = 0; latency_map[i].period != 0; i++) { if (latency_map[i].period == blocksize) hl = &latency_map[i]; } /* If no match, just find nearest. */ if (hl == NULL) { for (i = 0; latency_map[i].period != 0; i++) { hl = &latency_map[i]; threshold = hl->period + ((latency_map[i + 1].period != 0) ? ((latency_map[i + 1].period - hl->period) >> 1) : 0); if (blocksize < threshold) break; } } snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_LAT_MASK; sc->ctrl_register |= hdsp_encode_latency(hl->n); hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); sc->period = hl->period; snd_mtxunlock(sc->lock); #if 0 device_printf(scp->dev, "New period=%d\n", sc->period); #endif sndbuf_resize(ch->buffer, 2, (sc->period * AFMT_CHANNEL(ch->format) * sizeof(uint32_t))); /* Reset pointer, rewrite frequency (same register) for 9632. */ hdsp_write_4(sc, HDSP_RESET_POINTER, 0); if (sc->type == HDSP_9632) hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(sc->speed)); end: - return (sndbuf_getblksz(ch->buffer)); + return (ch->buffer->blksz); } static uint32_t hdsp_bkp_fmt[] = { SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; /* Capabilities fallback, no quad speed for HDSP 9652 compatibility. */ static struct pcmchan_caps hdsp_bkp_caps = {32000, 96000, hdsp_bkp_fmt, 0}; static struct pcmchan_caps * hdspchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch; ch = data; #if 0 device_printf(ch->parent->dev, "hdspchan_getcaps()\n"); #endif if (ch->caps != NULL) return (ch->caps); return (&hdsp_bkp_caps); } static kobj_method_t hdspchan_methods[] = { KOBJMETHOD(channel_init, hdspchan_init), KOBJMETHOD(channel_free, hdspchan_free), KOBJMETHOD(channel_setformat, hdspchan_setformat), KOBJMETHOD(channel_setspeed, hdspchan_setspeed), KOBJMETHOD(channel_setblocksize, hdspchan_setblocksize), KOBJMETHOD(channel_trigger, hdspchan_trigger), KOBJMETHOD(channel_getptr, hdspchan_getptr), KOBJMETHOD(channel_getcaps, hdspchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdspchan); static int hdsp_pcm_probe(device_t dev) { #if 0 device_printf(dev,"hdsp_pcm_probe()\n"); #endif return (0); } static uint32_t hdsp_pcm_intr(struct sc_pcminfo *scp) { struct sc_chinfo *ch; struct sc_info *sc; int i; sc = scp->sc; for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } return (0); } static int hdsp_pcm_attach(device_t dev) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; const char *buf; uint32_t pcm_flags; int err; int play, rec; scp = device_get_ivars(dev); scp->ih = &hdsp_pcm_intr; if (scp->hc->ports & HDSP_CHAN_9632_ALL) buf = "9632"; else if (scp->hc->ports & HDSP_CHAN_9652_ALL) buf = "9652"; else buf = "?"; device_set_descf(dev, "HDSP %s [%s]", buf, scp->hc->descr); /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_flags = pcm_getflags(dev) | SD_F_MPSAFE; if (hdsp_port_slot_count_max(scp->hc->ports) > HDSP_MATRIX_MAX) /* Disable vchan conversion, too many channels. */ pcm_flags |= SD_F_BITPERFECT; pcm_setflags(dev, pcm_flags); pcm_init(dev, scp); play = (hdsp_channel_play_ports(scp->hc)) ? 1 : 0; rec = (hdsp_channel_rec_ports(scp->hc)) ? 1 : 0; scp->chnum = 0; if (play) { pcm_addchan(dev, PCMDIR_PLAY, &hdspchan_class, scp); scp->chnum++; } if (rec) { pcm_addchan(dev, PCMDIR_REC, &hdspchan_class, scp); scp->chnum++; } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(scp->sc->cs), rman_get_start(scp->sc->irq), device_get_nameunit(device_get_parent(dev))); err = pcm_register(dev, status); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } mixer_init(dev, &hdspmixer_class, scp); return (0); } static int hdsp_pcm_detach(device_t dev) { int err; err = pcm_unregister(dev); if (err) { device_printf(dev, "Can't unregister device.\n"); return (err); } return (0); } static device_method_t hdsp_pcm_methods[] = { DEVMETHOD(device_probe, hdsp_pcm_probe), DEVMETHOD(device_attach, hdsp_pcm_attach), DEVMETHOD(device_detach, hdsp_pcm_detach), { 0, 0 } }; static driver_t hdsp_pcm_driver = { "pcm", hdsp_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdsp_pcm, hdsp, hdsp_pcm_driver, 0, 0); MODULE_DEPEND(snd_hdsp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hdsp, 1); diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c index 09bbbe22dacf..1c0a92e45b7a 100644 --- a/sys/dev/sound/pci/hdspe-pcm.c +++ b/sys/dev/sound/pci/hdspe-pcm.c @@ -1,1127 +1,1127 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2021 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * RME HDSPe driver for FreeBSD (pcm-part). * Supported cards: AIO, RayDAT. */ #include #include #include #include #include #define HDSPE_MATRIX_MAX 8 struct hdspe_latency { uint32_t n; uint32_t period; float ms; }; static struct hdspe_latency latency_map[] = { { 7, 32, 0.7 }, { 0, 64, 1.5 }, { 1, 128, 3 }, { 2, 256, 6 }, { 3, 512, 12 }, { 4, 1024, 23 }, { 5, 2048, 46 }, { 6, 4096, 93 }, { 0, 0, 0 }, }; struct hdspe_rate { uint32_t speed; uint32_t reg; }; static struct hdspe_rate rate_map[] = { { 32000, (HDSPE_FREQ_32000) }, { 44100, (HDSPE_FREQ_44100) }, { 48000, (HDSPE_FREQ_48000) }, { 64000, (HDSPE_FREQ_32000 | HDSPE_FREQ_DOUBLE) }, { 88200, (HDSPE_FREQ_44100 | HDSPE_FREQ_DOUBLE) }, { 96000, (HDSPE_FREQ_48000 | HDSPE_FREQ_DOUBLE) }, { 128000, (HDSPE_FREQ_32000 | HDSPE_FREQ_QUAD) }, { 176400, (HDSPE_FREQ_44100 | HDSPE_FREQ_QUAD) }, { 192000, (HDSPE_FREQ_48000 | HDSPE_FREQ_QUAD) }, { 0, 0 }, }; static uint32_t hdspe_channel_play_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL | HDSPE_CHAN_RAY_ALL)); } static uint32_t hdspe_channel_rec_ports(struct hdspe_channel *hc) { return (hc->ports & (HDSPE_CHAN_AIO_ALL_REC | HDSPE_CHAN_RAY_ALL)); } static unsigned int hdspe_adat_width(uint32_t speed) { if (speed > 96000) return (2); if (speed > 48000) return (4); return (8); } static uint32_t hdspe_port_first(uint32_t ports) { return (ports & (~(ports - 1))); /* Extract first bit set. */ } static uint32_t hdspe_port_first_row(uint32_t ports) { uint32_t ends; /* Restrict ports to one set with contiguous slots. */ if (ports & HDSPE_CHAN_AIO_ALL) ports &= HDSPE_CHAN_AIO_ALL; /* All AIO slots. */ else if (ports & HDSPE_CHAN_RAY_ALL) ports &= HDSPE_CHAN_RAY_ALL; /* All RayDAT slots. */ /* Ends of port rows are followed by a port which is not in the set. */ ends = ports & (~(ports >> 1)); /* First row of contiguous ports ends in the first row end. */ return (ports & (ends ^ (ends - 1))); } static unsigned int hdspe_channel_count(uint32_t ports, uint32_t adat_width) { unsigned int count = 0; if (ports & HDSPE_CHAN_AIO_ALL) { /* AIO ports. */ if (ports & HDSPE_CHAN_AIO_LINE) count += 2; if (ports & HDSPE_CHAN_AIO_EXT) count += 4; if (ports & HDSPE_CHAN_AIO_PHONE) count += 2; if (ports & HDSPE_CHAN_AIO_AES) count += 2; if (ports & HDSPE_CHAN_AIO_SPDIF) count += 2; if (ports & HDSPE_CHAN_AIO_ADAT) count += adat_width; } else if (ports & HDSPE_CHAN_RAY_ALL) { /* RayDAT ports. */ if (ports & HDSPE_CHAN_RAY_AES) count += 2; if (ports & HDSPE_CHAN_RAY_SPDIF) count += 2; if (ports & HDSPE_CHAN_RAY_ADAT1) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT2) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT3) count += adat_width; if (ports & HDSPE_CHAN_RAY_ADAT4) count += adat_width; } return (count); } static unsigned int hdspe_channel_offset(uint32_t subset, uint32_t ports, unsigned int adat_width) { uint32_t preceding; /* Make sure we have a subset of ports. */ subset &= ports; /* Include all ports preceding the first one of the subset. */ preceding = ports & (~subset & (subset - 1)); if (preceding & HDSPE_CHAN_AIO_ALL) preceding &= HDSPE_CHAN_AIO_ALL; /* Contiguous AIO slots. */ else if (preceding & HDSPE_CHAN_RAY_ALL) preceding &= HDSPE_CHAN_RAY_ALL; /* Contiguous RayDAT slots. */ return (hdspe_channel_count(preceding, adat_width)); } static unsigned int hdspe_port_slot_offset(uint32_t port, unsigned int adat_width) { /* Exctract the first port (lowest bit) if set of ports. */ switch (hdspe_port_first(port)) { /* AIO ports */ case HDSPE_CHAN_AIO_LINE: return (0); case HDSPE_CHAN_AIO_EXT: return (2); case HDSPE_CHAN_AIO_PHONE: return (6); case HDSPE_CHAN_AIO_AES: return (8); case HDSPE_CHAN_AIO_SPDIF: return (10); case HDSPE_CHAN_AIO_ADAT: return (12); /* RayDAT ports */ case HDSPE_CHAN_RAY_AES: return (0); case HDSPE_CHAN_RAY_SPDIF: return (2); case HDSPE_CHAN_RAY_ADAT1: return (4); case HDSPE_CHAN_RAY_ADAT2: return (4 + adat_width); case HDSPE_CHAN_RAY_ADAT3: return (4 + 2 * adat_width); case HDSPE_CHAN_RAY_ADAT4: return (4 + 3 * adat_width); default: return (0); } } static unsigned int hdspe_port_slot_width(uint32_t ports, unsigned int adat_width) { uint32_t row; /* Count number of contiguous slots from the first physical port. */ row = hdspe_port_first_row(ports); return (hdspe_channel_count(row, adat_width)); } static int hdspe_hw_mixer(struct sc_chinfo *ch, unsigned int dst, unsigned int src, unsigned short data) { struct sc_pcminfo *scp; struct sc_info *sc; int offs; scp = ch->parent; sc = scp->sc; offs = 0; if (ch->dir == PCMDIR_PLAY) offs = 64; hdspe_write_4(sc, HDSPE_MIXER_BASE + ((offs + src + 128 * dst) * sizeof(uint32_t)), data & 0xFFFF); return (0); }; static int hdspechan_setgain(struct sc_chinfo *ch) { struct sc_info *sc; uint32_t port, ports; unsigned int slot, end_slot; unsigned short volume; sc = ch->parent->sc; /* Iterate through all physical ports of the channel. */ ports = ch->ports; port = hdspe_port_first(ports); while (port != 0) { /* Get slot range of the physical port. */ slot = hdspe_port_slot_offset(port, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(port, hdspe_adat_width(sc->speed)); /* Treat first slot as left channel. */ volume = ch->lvol * HDSPE_MAX_GAIN / 100; for (; slot < end_slot; slot++) { hdspe_hw_mixer(ch, slot, slot, volume); /* Subsequent slots all get the right channel volume. */ volume = ch->rvol * HDSPE_MAX_GAIN / 100; } ports &= ~port; port = hdspe_port_first(ports); } return (0); } static int hdspemixer_init(struct snd_mixer *m) { struct sc_pcminfo *scp; struct sc_info *sc; int mask; scp = mix_getdevinfo(m); sc = scp->sc; if (sc == NULL) return (-1); mask = SOUND_MASK_PCM; if (hdspe_channel_play_ports(scp->hc)) mask |= SOUND_MASK_VOLUME; if (hdspe_channel_rec_ports(scp->hc)) mask |= SOUND_MASK_RECLEV; snd_mtxlock(sc->lock); pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, mask); snd_mtxunlock(sc->lock); return (0); } static int hdspemixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_pcminfo *scp; struct sc_chinfo *ch; int i; scp = mix_getdevinfo(m); #if 0 device_printf(scp->dev, "hdspemixer_set() %d %d\n", left, right); #endif for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; if ((dev == SOUND_MIXER_VOLUME && ch->dir == PCMDIR_PLAY) || (dev == SOUND_MIXER_RECLEV && ch->dir == PCMDIR_REC)) { ch->lvol = left; ch->rvol = right; if (ch->run) hdspechan_setgain(ch); } } return (0); } static kobj_method_t hdspemixer_methods[] = { KOBJMETHOD(mixer_init, hdspemixer_init), KOBJMETHOD(mixer_set, hdspemixer_set), KOBJMETHOD_END }; MIXER_DECLARE(hdspemixer); static void hdspechan_enable(struct sc_chinfo *ch, int value) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; int reg; unsigned int slot, end_slot; scp = ch->parent; sc = scp->sc; if (ch->dir == PCMDIR_PLAY) reg = HDSPE_OUT_ENABLE_BASE; else reg = HDSPE_IN_ENABLE_BASE; ch->run = value; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { slot = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); end_slot = slot + hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); for (; slot < end_slot; slot++) { hdspe_write_1(sc, reg + (4 * slot), value); } ports &= ~row; row = hdspe_port_first_row(ports); } } static int hdspe_running(struct sc_info *sc) { struct sc_pcminfo *scp; struct sc_chinfo *ch; device_t *devlist; int devcount; int i, j; int err; if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) goto bad; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); for (j = 0; j < scp->chnum; j++) { ch = &scp->chan[j]; if (ch->run) goto bad; } } free(devlist, M_TEMP); return (0); bad: #if 0 device_printf(sc->dev, "hdspe is running\n"); #endif free(devlist, M_TEMP); return (1); } static void hdspe_start_audio(struct sc_info *sc) { sc->ctrl_register |= (HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); } static void hdspe_stop_audio(struct sc_info *sc) { if (hdspe_running(sc) == 1) return; sc->ctrl_register &= ~(HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); } static void buffer_mux_write(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { dma[slot * HDSPE_CHANBUF_SAMPLES + pos] = pcm[pos * channels + slot]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_mux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate DMA slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_mux_write(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_mux_write(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_mux_write(dma, pcm, pos, samples, 8, channels); else buffer_mux_write(dma, pcm, pos, samples, slots, channels); } static void buffer_demux_read(uint32_t *dma, uint32_t *pcm, unsigned int pos, unsigned int samples, unsigned int slots, unsigned int channels) { int slot; for (; samples > 0; samples--) { for (slot = 0; slot < slots; slot++) { pcm[pos * channels + slot] = dma[slot * HDSPE_CHANBUF_SAMPLES + pos]; } pos = (pos + 1) % HDSPE_CHANBUF_SAMPLES; } } static void buffer_demux_port(uint32_t *dma, uint32_t *pcm, uint32_t subset, uint32_t ports, unsigned int pos, unsigned int samples, unsigned int adat_width, unsigned int pcm_width) { unsigned int slot_offset, slots; unsigned int channels, chan_pos; /* Translate port slot offset to DMA buffer offset. */ slot_offset = hdspe_port_slot_offset(subset, adat_width); dma += slot_offset * HDSPE_CHANBUF_SAMPLES; /* Channel position of the port subset and total number of channels. */ chan_pos = hdspe_channel_offset(subset, ports, pcm_width); pcm += chan_pos; channels = hdspe_channel_count(ports, pcm_width); /* Only copy as much as supported by both hardware and pcm channel. */ slots = hdspe_port_slot_width(subset, MIN(adat_width, pcm_width)); /* Let the compiler inline and loop unroll common cases. */ if (slots == 2) buffer_demux_read(dma, pcm, pos, samples, 2, channels); else if (slots == 4) buffer_demux_read(dma, pcm, pos, samples, 4, channels); else if (slots == 8) buffer_demux_read(dma, pcm, pos, samples, 8, channels); else buffer_demux_read(dma, pcm, pos, samples, slots, channels); } /* Copy data between DMA and PCM buffers. */ static void buffer_copy(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t row, ports; uint32_t dma_pos; unsigned int pos, length, offset; unsigned int n; unsigned int adat_width, pcm_width; scp = ch->parent; sc = scp->sc; n = AFMT_CHANNEL(ch->format); /* n channels */ /* Let pcm formats differ from current hardware ADAT width. */ adat_width = hdspe_adat_width(sc->speed); if (n == hdspe_channel_count(ch->ports, 2)) pcm_width = 2; else if (n == hdspe_channel_count(ch->ports, 4)) pcm_width = 4; else pcm_width = 8; /* Derive buffer position and length to be copied. */ if (ch->dir == PCMDIR_PLAY) { /* Position per channel is n times smaller than PCM. */ pos = sndbuf_getreadyptr(ch->buffer) / n; length = sndbuf_getready(ch->buffer) / n; /* Copy no more than 2 periods in advance. */ if (length > (sc->period * 4 * 2)) length = (sc->period * 4 * 2); /* Skip what was already copied last time. */ offset = (ch->position + HDSPE_CHANBUF_SIZE) - pos; offset %= HDSPE_CHANBUF_SIZE; if (offset <= length) { pos = (pos + offset) % HDSPE_CHANBUF_SIZE; length -= offset; } } else { /* Position per channel is n times smaller than PCM. */ pos = sndbuf_getfreeptr(ch->buffer) / n; /* Get DMA buffer write position. */ dma_pos = hdspe_read_2(sc, HDSPE_STATUS_REG); dma_pos &= HDSPE_BUF_POSITION_MASK; /* Copy what is newly available. */ length = (dma_pos + HDSPE_CHANBUF_SIZE) - pos; length %= HDSPE_CHANBUF_SIZE; } /* Position and length in samples (4 bytes). */ pos /= 4; length /= 4; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); while (row != 0) { if (ch->dir == PCMDIR_PLAY) buffer_mux_port(sc->pbuf, ch->data, row, ch->ports, pos, length, adat_width, pcm_width); else buffer_demux_port(sc->rbuf, ch->data, row, ch->ports, pos, length, adat_width, pcm_width); ports &= ~row; if (pcm_width == adat_width) row = hdspe_port_first_row(ports); else row = hdspe_port_first(ports); } ch->position = ((pos + length) * 4) % HDSPE_CHANBUF_SIZE; } static int clean(struct sc_chinfo *ch) { struct sc_pcminfo *scp; struct sc_info *sc; uint32_t *buf; uint32_t row, ports; unsigned int offset, slots; scp = ch->parent; sc = scp->sc; buf = sc->rbuf; if (ch->dir == PCMDIR_PLAY) buf = sc->pbuf; /* Iterate through rows of ports with contiguous slots. */ ports = ch->ports; row = hdspe_port_first_row(ports); while (row != 0) { offset = hdspe_port_slot_offset(row, hdspe_adat_width(sc->speed)); slots = hdspe_port_slot_width(row, hdspe_adat_width(sc->speed)); bzero(buf + offset * HDSPE_CHANBUF_SAMPLES, slots * HDSPE_CHANBUF_SIZE); ports &= ~row; row = hdspe_port_first_row(ports); } ch->position = 0; return (0); } /* Channel interface. */ static int hdspechan_free(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; #if 0 device_printf(scp->dev, "hdspechan_free()\n"); #endif snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_HDSPE); ch->data = NULL; } if (ch->caps != NULL) { free(ch->caps, M_HDSPE); ch->caps = NULL; } snd_mtxunlock(sc->lock); return (0); } static void * hdspechan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int num; scp = devinfo; sc = scp->sc; snd_mtxlock(sc->lock); num = scp->chnum; ch = &scp->chan[num]; if (dir == PCMDIR_PLAY) ch->ports = hdspe_channel_play_ports(scp->hc); else ch->ports = hdspe_channel_rec_ports(scp->hc); ch->run = 0; ch->lvol = 0; ch->rvol = 0; /* Support all possible ADAT widths as channel formats. */ ch->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 2), 0); ch->cap_fmts[1] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 4), 0); ch->cap_fmts[2] = SND_FORMAT(AFMT_S32_LE, hdspe_channel_count(ch->ports, 8), 0); ch->cap_fmts[3] = 0; ch->caps = malloc(sizeof(struct pcmchan_caps), M_HDSPE, M_NOWAIT); *(ch->caps) = (struct pcmchan_caps) {32000, 192000, ch->cap_fmts, 0}; /* Allocate maximum buffer size. */ ch->size = HDSPE_CHANBUF_SIZE * hdspe_channel_count(ch->ports, 8); ch->data = malloc(ch->size, M_HDSPE, M_NOWAIT); ch->position = 0; ch->buffer = b; ch->channel = c; ch->parent = scp; ch->dir = dir; snd_mtxunlock(sc->lock); if (sndbuf_setup(ch->buffer, ch->data, ch->size) != 0) { device_printf(scp->dev, "Can't setup sndbuf.\n"); hdspechan_free(obj, ch); return (NULL); } return (ch); } static int hdspechan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "hdspechan_trigger(): start\n"); #endif hdspechan_enable(ch, 1); hdspechan_setgain(ch); hdspe_start_audio(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "hdspechan_trigger(): stop or abort\n"); #endif clean(ch); hdspechan_enable(ch, 0); hdspe_stop_audio(sc); break; case PCMTRIG_EMLDMAWR: case PCMTRIG_EMLDMARD: if(ch->run) buffer_copy(ch); break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t hdspechan_getptr(kobj_t obj, void *data) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; uint32_t ret, pos; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); ret = hdspe_read_2(sc, HDSPE_STATUS_REG); snd_mtxunlock(sc->lock); pos = ret & HDSPE_BUF_POSITION_MASK; pos *= AFMT_CHANNEL(ch->format); /* Hardbuf with multiple channels. */ return (pos); } static int hdspechan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scp = ch->parent; device_printf(scp->dev, "hdspechan_setformat(%d)\n", format); #endif ch->format = format; return (0); } static uint32_t hdspechan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct hdspe_rate *hr; struct sc_chinfo *ch; struct sc_info *sc; long long period; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hr = NULL; #if 0 device_printf(scp->dev, "hdspechan_setspeed(%d)\n", speed); #endif if (hdspe_running(sc) == 1) goto end; if (sc->force_speed > 0) speed = sc->force_speed; /* First look for equal frequency. */ for (i = 0; rate_map[i].speed != 0; i++) { if (rate_map[i].speed == speed) hr = &rate_map[i]; } /* If no match, just find nearest. */ if (hr == NULL) { for (i = 0; rate_map[i].speed != 0; i++) { hr = &rate_map[i]; threshold = hr->speed + ((rate_map[i + 1].speed != 0) ? ((rate_map[i + 1].speed - hr->speed) >> 1) : 0); if (speed < threshold) break; } } switch (sc->type) { case HDSPE_RAYDAT: case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: /* Unsupported card. */ goto end; } /* Write frequency on the device. */ sc->ctrl_register &= ~HDSPE_FREQ_MASK; sc->ctrl_register |= hr->reg; hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); speed = hr->speed; if (speed > 96000) speed /= 4; else if (speed > 48000) speed /= 2; /* Set DDS value. */ period /= speed; hdspe_write_4(sc, HDSPE_FREQ_REG, period); sc->speed = hr->speed; end: return (sc->speed); } static uint32_t hdspechan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct hdspe_latency *hl; struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; int threshold; int i; ch = data; scp = ch->parent; sc = scp->sc; hl = NULL; #if 0 device_printf(scp->dev, "hdspechan_setblocksize(%d)\n", blocksize); #endif if (hdspe_running(sc) == 1) goto end; if (blocksize > HDSPE_LAT_BYTES_MAX) blocksize = HDSPE_LAT_BYTES_MAX; else if (blocksize < HDSPE_LAT_BYTES_MIN) blocksize = HDSPE_LAT_BYTES_MIN; blocksize /= 4 /* samples */; if (sc->force_period > 0) blocksize = sc->force_period; /* First look for equal latency. */ for (i = 0; latency_map[i].period != 0; i++) { if (latency_map[i].period == blocksize) hl = &latency_map[i]; } /* If no match, just find nearest. */ if (hl == NULL) { for (i = 0; latency_map[i].period != 0; i++) { hl = &latency_map[i]; threshold = hl->period + ((latency_map[i + 1].period != 0) ? ((latency_map[i + 1].period - hl->period) >> 1) : 0); if (blocksize < threshold) break; } } snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSPE_LAT_MASK; sc->ctrl_register |= hdspe_encode_latency(hl->n); hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); sc->period = hl->period; snd_mtxunlock(sc->lock); #if 0 device_printf(scp->dev, "New period=%d\n", sc->period); #endif sndbuf_resize(ch->buffer, (HDSPE_CHANBUF_SIZE * AFMT_CHANNEL(ch->format)) / (sc->period * 4), (sc->period * 4)); end: - return (sndbuf_getblksz(ch->buffer)); + return (ch->buffer->blksz); } static uint32_t hdspe_bkp_fmt[] = { SND_FORMAT(AFMT_S32_LE, 2, 0), 0 }; static struct pcmchan_caps hdspe_bkp_caps = {32000, 192000, hdspe_bkp_fmt, 0}; static struct pcmchan_caps * hdspechan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch; ch = data; #if 0 struct sc_pcminfo *scl = ch->parent; device_printf(scp->dev, "hdspechan_getcaps()\n"); #endif if (ch->caps != NULL) return (ch->caps); return (&hdspe_bkp_caps); } static kobj_method_t hdspechan_methods[] = { KOBJMETHOD(channel_init, hdspechan_init), KOBJMETHOD(channel_free, hdspechan_free), KOBJMETHOD(channel_setformat, hdspechan_setformat), KOBJMETHOD(channel_setspeed, hdspechan_setspeed), KOBJMETHOD(channel_setblocksize, hdspechan_setblocksize), KOBJMETHOD(channel_trigger, hdspechan_trigger), KOBJMETHOD(channel_getptr, hdspechan_getptr), KOBJMETHOD(channel_getcaps, hdspechan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdspechan); static int hdspe_pcm_probe(device_t dev) { #if 0 device_printf(dev,"hdspe_pcm_probe()\n"); #endif return (0); } static uint32_t hdspe_pcm_intr(struct sc_pcminfo *scp) { struct sc_chinfo *ch; struct sc_info *sc; int i; sc = scp->sc; for (i = 0; i < scp->chnum; i++) { ch = &scp->chan[i]; snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } return (0); } static int hdspe_pcm_attach(device_t dev) { char status[SND_STATUSLEN]; struct sc_pcminfo *scp; const char *buf; uint32_t pcm_flags; int err; int play, rec; scp = device_get_ivars(dev); scp->ih = &hdspe_pcm_intr; if (scp->hc->ports & HDSPE_CHAN_AIO_ALL) buf = "AIO"; else if (scp->hc->ports & HDSPE_CHAN_RAY_ALL) buf = "RayDAT"; else buf = "?"; device_set_descf(dev, "HDSPe %s [%s]", buf, scp->hc->descr); /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_flags = pcm_getflags(dev) | SD_F_MPSAFE; if (hdspe_channel_count(scp->hc->ports, 8) > HDSPE_MATRIX_MAX) /* Disable vchan conversion, too many channels. */ pcm_flags |= SD_F_BITPERFECT; pcm_setflags(dev, pcm_flags); pcm_init(dev, scp); play = (hdspe_channel_play_ports(scp->hc)) ? 1 : 0; rec = (hdspe_channel_rec_ports(scp->hc)) ? 1 : 0; scp->chnum = 0; if (play) { pcm_addchan(dev, PCMDIR_PLAY, &hdspechan_class, scp); scp->chnum++; } if (rec) { pcm_addchan(dev, PCMDIR_REC, &hdspechan_class, scp); scp->chnum++; } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(scp->sc->cs), rman_get_start(scp->sc->irq), device_get_nameunit(device_get_parent(dev))); err = pcm_register(dev, status); if (err) { device_printf(dev, "Can't register pcm.\n"); return (ENXIO); } mixer_init(dev, &hdspemixer_class, scp); return (0); } static int hdspe_pcm_detach(device_t dev) { int err; err = pcm_unregister(dev); if (err) { device_printf(dev, "Can't unregister device.\n"); return (err); } return (0); } static device_method_t hdspe_pcm_methods[] = { DEVMETHOD(device_probe, hdspe_pcm_probe), DEVMETHOD(device_attach, hdspe_pcm_attach), DEVMETHOD(device_detach, hdspe_pcm_detach), { 0, 0 } }; static driver_t hdspe_pcm_driver = { "pcm", hdspe_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdspe_pcm, hdspe, hdspe_pcm_driver, 0, 0); MODULE_DEPEND(snd_hdspe, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hdspe, 1); diff --git a/sys/dev/sound/pci/ich.c b/sys/dev/sound/pci/ich.c index 500d6d95daac..53f4db3332a8 100644 --- a/sys/dev/sound/pci/ich.c +++ b/sys/dev/sound/pci/ich.c @@ -1,1240 +1,1240 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Katsurajima Naoto * Copyright (c) 2001 Cameron Grant * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include /* -------------------------------------------------------------------- */ #define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ #define ICH_DTBL_LENGTH 32 #define ICH_DEFAULT_BUFSZ 16384 #define ICH_MAX_BUFSZ 65536 #define ICH_MIN_BUFSZ 4096 #define ICH_DEFAULT_BLKCNT 2 #define ICH_MAX_BLKCNT 32 #define ICH_MIN_BLKCNT 2 #define ICH_MIN_BLKSZ 64 #define INTEL_VENDORID 0x8086 #define SIS_VENDORID 0x1039 #define NVIDIA_VENDORID 0x10de #define AMD_VENDORID 0x1022 #define INTEL_82440MX 0x7195 #define INTEL_82801AA 0x2415 #define INTEL_82801AB 0x2425 #define INTEL_82801BA 0x2445 #define INTEL_82801CA 0x2485 #define INTEL_82801DB 0x24c5 /* ICH4 needs special handling */ #define INTEL_82801EB 0x24d5 /* ICH5 needs to be treated as ICH4 */ #define INTEL_6300ESB 0x25a6 /* 6300ESB needs to be treated as ICH4 */ #define INTEL_82801FB 0x266e /* ICH6 needs to be treated as ICH4 */ #define INTEL_82801GB 0x27de /* ICH7 needs to be treated as ICH4 */ #define SIS_7012 0x7012 /* SiS 7012 needs special handling */ #define NVIDIA_NFORCE 0x01b1 #define NVIDIA_NFORCE2 0x006a #define NVIDIA_NFORCE2_400 0x008a #define NVIDIA_NFORCE3 0x00da #define NVIDIA_NFORCE3_250 0x00ea #define NVIDIA_NFORCE4 0x0059 #define NVIDIA_NFORCE_410_MCP 0x026b #define NVIDIA_NFORCE4_MCP 0x003a #define AMD_768 0x7445 #define AMD_8111 0x746d #define ICH_LOCK(sc) snd_mtxlock((sc)->ich_lock) #define ICH_UNLOCK(sc) snd_mtxunlock((sc)->ich_lock) #define ICH_LOCK_ASSERT(sc) snd_mtxassert((sc)->ich_lock) #if 0 #define ICH_DEBUG(stmt) do { \ stmt \ } while (0) #else #define ICH_DEBUG(...) #endif #define ICH_CALIBRATE_DONE (1 << 0) #define ICH_IGNORE_PCR (1 << 1) #define ICH_IGNORE_RESET (1 << 2) #define ICH_FIXED_RATE (1 << 3) #define ICH_DMA_NOCACHE (1 << 4) #define ICH_HIGH_LATENCY (1 << 5) static const struct ich_type { uint16_t vendor; uint16_t devid; uint32_t options; #define PROBE_LOW 0x01 char *name; } ich_devs[] = { { INTEL_VENDORID, INTEL_82440MX, 0, "Intel 440MX" }, { INTEL_VENDORID, INTEL_82801AA, 0, "Intel ICH (82801AA)" }, { INTEL_VENDORID, INTEL_82801AB, 0, "Intel ICH (82801AB)" }, { INTEL_VENDORID, INTEL_82801BA, 0, "Intel ICH2 (82801BA)" }, { INTEL_VENDORID, INTEL_82801CA, 0, "Intel ICH3 (82801CA)" }, { INTEL_VENDORID, INTEL_82801DB, PROBE_LOW, "Intel ICH4 (82801DB)" }, { INTEL_VENDORID, INTEL_82801EB, PROBE_LOW, "Intel ICH5 (82801EB)" }, { INTEL_VENDORID, INTEL_6300ESB, PROBE_LOW, "Intel 6300ESB" }, { INTEL_VENDORID, INTEL_82801FB, PROBE_LOW, "Intel ICH6 (82801FB)" }, { INTEL_VENDORID, INTEL_82801GB, PROBE_LOW, "Intel ICH7 (82801GB)" }, { SIS_VENDORID, SIS_7012, 0, "SiS 7012" }, { NVIDIA_VENDORID, NVIDIA_NFORCE, 0, "nVidia nForce" }, { NVIDIA_VENDORID, NVIDIA_NFORCE2, 0, "nVidia nForce2" }, { NVIDIA_VENDORID, NVIDIA_NFORCE2_400, 0, "nVidia nForce2 400" }, { NVIDIA_VENDORID, NVIDIA_NFORCE3, 0, "nVidia nForce3" }, { NVIDIA_VENDORID, NVIDIA_NFORCE3_250, 0, "nVidia nForce3 250" }, { NVIDIA_VENDORID, NVIDIA_NFORCE4, 0, "nVidia nForce4" }, { NVIDIA_VENDORID, NVIDIA_NFORCE_410_MCP, 0, "nVidia nForce 410 MCP" }, { NVIDIA_VENDORID, NVIDIA_NFORCE4_MCP, 0, "nVidia nForce 4 MCP" }, { AMD_VENDORID, AMD_768, 0, "AMD-768" }, { AMD_VENDORID, AMD_8111, 0, "AMD-8111" } }; /* buffer descriptor */ struct ich_desc { volatile uint32_t buffer; volatile uint32_t length; }; struct sc_info; /* channel registers */ struct sc_chinfo { uint32_t num:8, run:1, run_save:1; uint32_t blksz, blkcnt, spd; uint32_t regbase, spdreg; uint32_t imask; uint32_t civ; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; struct ich_desc *dtbl; bus_addr_t desc_addr; }; /* device private data */ struct sc_info { device_t dev; int hasvra, hasvrm, hasmic; unsigned int chnum, bufsz, blkcnt; int sample_size, swap_reg; struct resource *nambar, *nabmbar, *irq; int regtype, nambarid, nabmbarid, irqid; bus_space_tag_t nambart, nabmbart; bus_space_handle_t nambarh, nabmbarh; bus_dma_tag_t dmat, chan_dmat; bus_dmamap_t dtmap; void *ih; struct ac97_info *codec; struct sc_chinfo ch[3]; int ac97rate; struct ich_desc *dtbl; unsigned int dtbl_size; bus_addr_t desc_addr; struct intr_config_hook intrhook; uint16_t vendor; uint16_t devid; uint32_t flags; struct mtx *ich_lock; }; /* -------------------------------------------------------------------- */ static uint32_t ich_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps ich_vrcaps = {8000, 48000, ich_fmt, 0}; static struct pcmchan_caps ich_caps = {48000, 48000, ich_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static __inline uint32_t ich_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno)); case 2: return (bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno)); case 4: return (bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno)); default: return (0xffffffff); } } static __inline void ich_wr(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(sc->nabmbart, sc->nabmbarh, regno, data); break; case 2: bus_space_write_2(sc->nabmbart, sc->nabmbarh, regno, data); break; case 4: bus_space_write_4(sc->nabmbart, sc->nabmbarh, regno, data); break; } } /* ac97 codec */ static int ich_waitcd(void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; uint32_t data; int i; for (i = 0; i < ICH_TIMEOUT; i++) { data = ich_rd(sc, ICH_REG_ACC_SEMA, 1); if ((data & 0x01) == 0) return (0); DELAY(1); } if ((sc->flags & ICH_IGNORE_PCR) != 0) return (0); device_printf(sc->dev, "CODEC semaphore timeout\n"); return (ETIMEDOUT); } static int ich_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; ich_waitcd(sc); return (bus_space_read_2(sc->nambart, sc->nambarh, regno)); } static int ich_wrcd(kobj_t obj, void *devinfo, int regno, uint32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; regno &= 0xff; ich_waitcd(sc); bus_space_write_2(sc->nambart, sc->nambarh, regno, data); return (0); } static kobj_method_t ich_ac97_methods[] = { KOBJMETHOD(ac97_read, ich_rdcd), KOBJMETHOD(ac97_write, ich_wrcd), KOBJMETHOD_END }; AC97_DECLARE(ich_ac97); /* -------------------------------------------------------------------- */ /* common routines */ static void ich_filldtbl(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; uint32_t base; int i; - base = sndbuf_getbufaddr(ch->buffer); - if ((ch->blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) - ch->blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; - if ((sndbuf_getblksz(ch->buffer) != ch->blksz || - sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && + base = ch->buffer->buf_addr; + if ((ch->blksz * ch->blkcnt) > ch->buffer->maxsize) + ch->blksz = ch->buffer->maxsize / ch->blkcnt; + if ((ch->buffer->blksz != ch->blksz || + ch->buffer->blkcnt != ch->blkcnt) && sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz) != 0) device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, ch->blksz, ch->blkcnt); - ch->blksz = sndbuf_getblksz(ch->buffer); + ch->blksz = ch->buffer->blksz; for (i = 0; i < ICH_DTBL_LENGTH; i++) { ch->dtbl[i].buffer = base + (ch->blksz * (i % ch->blkcnt)); ch->dtbl[i].length = ICH_BDC_IOC | (ch->blksz / ch->parent->sample_size); } } static int ich_resetchan(struct sc_info *sc, int num) { int i, cr, regbase; if (num == 0) regbase = ICH_REG_PO_BASE; else if (num == 1) regbase = ICH_REG_PI_BASE; else if (num == 2) regbase = ICH_REG_MC_BASE; else return (ENXIO); ich_wr(sc, regbase + ICH_REG_X_CR, 0, 1); #if 1 /* This may result in no sound output on NForce 2 MBs, see PR 73987 */ DELAY(100); #else (void)ich_rd(sc, regbase + ICH_REG_X_CR, 1); #endif ich_wr(sc, regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); for (i = 0; i < ICH_TIMEOUT; i++) { cr = ich_rd(sc, regbase + ICH_REG_X_CR, 1); if (cr == 0) return (0); DELAY(1); } if (sc->flags & ICH_IGNORE_RESET) return (0); #if 0 else if (sc->vendor == NVIDIA_VENDORID) { sc->flags |= ICH_IGNORE_RESET; device_printf(sc->dev, "ignoring reset failure!\n"); return (0); } #endif device_printf(sc->dev, "cannot reset channel %d\n", num); return (ENXIO); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * ichchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; unsigned int num; ICH_LOCK(sc); num = sc->chnum++; ch = &sc->ch[num]; ch->num = num; ch->buffer = b; ch->channel = c; ch->parent = sc; ch->run = 0; ch->dtbl = sc->dtbl + (ch->num * ICH_DTBL_LENGTH); ch->desc_addr = sc->desc_addr + (ch->num * ICH_DTBL_LENGTH * sizeof(struct ich_desc)); ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; switch(ch->num) { case 0: /* play */ KASSERT(dir == PCMDIR_PLAY, ("wrong direction")); ch->regbase = ICH_REG_PO_BASE; ch->spdreg = (sc->hasvra) ? AC97_REGEXT_FDACRATE : 0; ch->imask = ICH_GLOB_STA_POINT; break; case 1: /* record */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_PI_BASE; ch->spdreg = (sc->hasvra) ? AC97_REGEXT_LADCRATE : 0; ch->imask = ICH_GLOB_STA_PIINT; break; case 2: /* mic */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_MC_BASE; ch->spdreg = (sc->hasvrm) ? AC97_REGEXT_MADCRATE : 0; ch->imask = ICH_GLOB_STA_MINT; break; default: return (NULL); } if (sc->flags & ICH_FIXED_RATE) ch->spdreg = 0; ICH_UNLOCK(sc); if (sndbuf_alloc(ch->buffer, sc->chan_dmat, ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), sc->bufsz) != 0) return (NULL); ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ICH_UNLOCK(sc); return (ch); } static int ichchan_setformat(kobj_t obj, void *data, uint32_t format) { ICH_DEBUG( struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); return (0); } static uint32_t ichchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); if (ch->spdreg) { int r, ac97rate; ICH_LOCK(sc); if (sc->ac97rate <= 32000 || sc->ac97rate >= 64000) sc->ac97rate = 48000; ac97rate = sc->ac97rate; ICH_UNLOCK(sc); r = (speed * 48000) / ac97rate; /* * Cast the return value of ac97_setrate() to uint64 so that * the math don't overflow into the negative range. */ ch->spd = ((uint64_t)ac97_setrate(sc->codec, ch->spdreg, r) * ac97rate) / 48000; } else { ch->spd = 48000; } return (ch->spd); } static uint32_t ichchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); if (sc->flags & ICH_HIGH_LATENCY) - blocksize = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; + blocksize = ch->buffer->maxsize / ch->blkcnt; if (blocksize < ICH_MIN_BLKSZ) blocksize = ICH_MIN_BLKSZ; blocksize &= ~(ICH_MIN_BLKSZ - 1); ch->blksz = blocksize; ich_filldtbl(ch); ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_LVI, ch->blkcnt - 1, 1); ICH_UNLOCK(sc); return (ch->blksz); } static int ichchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); switch (go) { case PCMTRIG_START: ch->run = 1; ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE, 1); ICH_UNLOCK(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ICH_LOCK(sc); ich_resetchan(sc, ch->num); ICH_UNLOCK(sc); ch->run = 0; break; default: break; } return (0); } static uint32_t ichchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; uint32_t pos; ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); ICH_LOCK(sc); ch->civ = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1) % ch->blkcnt; ICH_UNLOCK(sc); pos = ch->civ * ch->blksz; return (pos); } static struct pcmchan_caps * ichchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; ICH_DEBUG( struct sc_info *sc = ch->parent; if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(ch->parent->dev, "WARNING: %s() called before calibration!\n", __func__); ); return ((ch->spdreg) ? &ich_vrcaps : &ich_caps); } static kobj_method_t ichchan_methods[] = { KOBJMETHOD(channel_init, ichchan_init), KOBJMETHOD(channel_setformat, ichchan_setformat), KOBJMETHOD(channel_setspeed, ichchan_setspeed), KOBJMETHOD(channel_setblocksize, ichchan_setblocksize), KOBJMETHOD(channel_trigger, ichchan_trigger), KOBJMETHOD(channel_getptr, ichchan_getptr), KOBJMETHOD(channel_getcaps, ichchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(ichchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void ich_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; uint32_t cbi, lbi, lvi, st, gs; int i; ICH_LOCK(sc); ICH_DEBUG( if (!(sc->flags & ICH_CALIBRATE_DONE)) device_printf(sc->dev, "WARNING: %s() called before calibration!\n", __func__); ); gs = ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_IMASK; if (gs & (ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES)) { /* Clear resume interrupt(s) - nothing doing with them */ ich_wr(sc, ICH_REG_GLOB_STA, gs, 4); } gs &= ~(ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES); for (i = 0; i < 3; i++) { ch = &sc->ch[i]; if ((ch->imask & gs) == 0) continue; gs &= ~ch->imask; st = ich_rd(sc, ch->regbase + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), 2); st &= ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI; if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) { /* block complete - update buffer */ if (ch->run) { ICH_UNLOCK(sc); chn_intr(ch->channel); ICH_LOCK(sc); } lvi = ich_rd(sc, ch->regbase + ICH_REG_X_LVI, 1); cbi = ch->civ % ch->blkcnt; if (cbi == 0) cbi = ch->blkcnt - 1; else cbi--; lbi = lvi % ch->blkcnt; if (cbi >= lbi) lvi += cbi - lbi; else lvi += cbi + ch->blkcnt - lbi; lvi %= ICH_DTBL_LENGTH; ich_wr(sc, ch->regbase + ICH_REG_X_LVI, lvi, 1); } /* clear status bit */ ich_wr(sc, ch->regbase + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), st, 2); } ICH_UNLOCK(sc); if (gs != 0) { device_printf(sc->dev, "Unhandled interrupt, gs_intr = %x\n", gs); } } /* ------------------------------------------------------------------------- */ /* Sysctl to control ac97 speed (some boards appear to end up using * XTAL_IN rather than BIT_CLK for link timing). */ static int ich_initsys(struct sc_info* sc) { /* XXX: this should move to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "ac97rate", CTLFLAG_RW, &sc->ac97rate, 48000, "AC97 link rate (default = 48000)"); return (0); } static void ich_setstatus(struct sc_info *sc) { char status[SND_STATUSLEN]; snprintf(status, SND_STATUSLEN, "port 0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(sc->dev))); if (bootverbose && (sc->flags & ICH_DMA_NOCACHE)) device_printf(sc->dev, "PCI Master abort workaround enabled\n"); pcm_register(sc->dev, status); } /* -------------------------------------------------------------------- */ /* Calibrate card to determine the clock source. The source maybe a * function of the ac97 codec initialization code (to be investigated). */ static void ich_calibrate(void *arg) { struct sc_info *sc; struct sc_chinfo *ch; struct timeval t1, t2; uint8_t ociv, nciv; uint32_t wait_us, actual_48k_rate, oblkcnt; sc = (struct sc_info *)arg; ICH_LOCK(sc); ch = &sc->ch[1]; if (sc->intrhook.ich_func != NULL) { config_intrhook_disestablish(&sc->intrhook); sc->intrhook.ich_func = NULL; } /* * Grab audio from input for fixed interval and compare how * much we actually get with what we expect. Interval needs * to be sufficiently short that no interrupts are * generated. */ KASSERT(ch->regbase == ICH_REG_PI_BASE, ("wrong direction")); oblkcnt = ch->blkcnt; ch->blkcnt = 2; sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); - ichchan_setblocksize(0, ch, sndbuf_getmaxsize(ch->buffer) >> 1); + ichchan_setblocksize(0, ch, ch->buffer->maxsize >> 1); ICH_LOCK(sc); sc->flags &= ~ICH_CALIBRATE_DONE; /* * our data format is stereo, 16 bit so each sample is 4 bytes. * assuming we get 48000 samples per second, we get 192000 bytes/sec. * we're going to start recording with interrupts disabled and measure * the time taken for one block to complete. we know the block size, * we know the time in microseconds, we calculate the sample rate: * * actual_rate [bps] = bytes / (time [s] * 4) * actual_rate [bps] = (bytes * 1000000) / (time [us] * 4) * actual_rate [Hz] = (bytes * 250000) / time [us] */ /* prepare */ ociv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); nciv = ociv; ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); /* start */ microtime(&t1); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM, 1); /* wait */ do { microtime(&t2); if (t2.tv_sec - t1.tv_sec > 1) break; nciv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); } while (nciv == ociv); /* stop */ ich_wr(sc, ch->regbase + ICH_REG_X_CR, 0, 1); /* reset */ DELAY(100); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); ch->blkcnt = oblkcnt; /* turn time delta into us */ wait_us = ((t2.tv_sec - t1.tv_sec) * 1000000) + t2.tv_usec - t1.tv_usec; if (nciv == ociv) { device_printf(sc->dev, "ac97 link rate calibration timed out after %d us\n", wait_us); sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ich_setstatus(sc); return; } /* Just in case the timecounter screwed. It is possible, really. */ if (wait_us > 0) actual_48k_rate = ((uint64_t)ch->blksz * 250000) / wait_us; else actual_48k_rate = 48000; if (actual_48k_rate < 47500 || actual_48k_rate > 48500) { sc->ac97rate = actual_48k_rate; } else { sc->ac97rate = 48000; } if (bootverbose || sc->ac97rate != 48000) { device_printf(sc->dev, "measured ac97 link rate at %d Hz", actual_48k_rate); if (sc->ac97rate != actual_48k_rate) printf(", will use %d Hz", sc->ac97rate); printf("\n"); } sc->flags |= ICH_CALIBRATE_DONE; ICH_UNLOCK(sc); ich_setstatus(sc); return; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static void ich_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct sc_info *sc = (struct sc_info *)arg; sc->desc_addr = segs->ds_addr; return; } static int ich_init(struct sc_info *sc) { uint32_t stat; ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); DELAY(600000); stat = ich_rd(sc, ICH_REG_GLOB_STA, 4); if ((stat & ICH_GLOB_STA_PCR) == 0) { /* ICH4/ICH5 may fail when busmastering is enabled. Continue */ if (sc->vendor == INTEL_VENDORID && ( sc->devid == INTEL_82801DB || sc->devid == INTEL_82801EB || sc->devid == INTEL_6300ESB || sc->devid == INTEL_82801FB || sc->devid == INTEL_82801GB)) { sc->flags |= ICH_IGNORE_PCR; device_printf(sc->dev, "primary codec not ready!\n"); } } #if 0 ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD | ICH_GLOB_CTL_PRES, 4); #else ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); #endif if (ich_resetchan(sc, 0) || ich_resetchan(sc, 1)) return (ENXIO); if (sc->hasmic && ich_resetchan(sc, 2)) return (ENXIO); return (0); } static int ich_pci_probe(device_t dev) { size_t i; uint16_t devid, vendor; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < nitems(ich_devs); i++) { if (vendor == ich_devs[i].vendor && devid == ich_devs[i].devid) { device_set_desc(dev, ich_devs[i].name); /* allow a better driver to override us */ if ((ich_devs[i].options & PROBE_LOW) != 0) return (BUS_PROBE_LOW_PRIORITY); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int ich_pci_attach(device_t dev) { uint32_t subdev; uint16_t extcaps; uint16_t devid, vendor; struct sc_info *sc; int i; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->ich_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ich softc"); sc->dev = dev; vendor = sc->vendor = pci_get_vendor(dev); devid = sc->devid = pci_get_device(dev); subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); /* * The SiS 7012 register set isn't quite like the standard ich. * There really should be a general "quirks" mechanism. */ if (vendor == SIS_VENDORID && devid == SIS_7012) { sc->swap_reg = 1; sc->sample_size = 1; } else { sc->swap_reg = 0; sc->sample_size = 2; } /* * Intel 440MX Errata #36 * - AC97 Soft Audio and Soft Modem Master Abort Errata * * http://www.intel.com/design/chipsets/specupdt/245051.htm */ if (vendor == INTEL_VENDORID && devid == INTEL_82440MX) sc->flags |= ICH_DMA_NOCACHE; /* * Enable bus master. On ich4/5 this may prevent the detection of * the primary codec becoming ready in ich_init(). */ pci_enable_busmaster(dev); /* * By default, ich4 has NAMBAR and NABMBAR i/o spaces as * read-only. Need to enable "legacy support", by poking into * pci config space. The driver should use MMBAR and MBBAR, * but doing so will mess things up here. ich4 has enough new * features it warrants it's own driver. */ if (vendor == INTEL_VENDORID && (devid == INTEL_82801DB || devid == INTEL_82801EB || devid == INTEL_6300ESB || devid == INTEL_82801FB || devid == INTEL_82801GB)) { sc->nambarid = PCIR_MMBAR; sc->nabmbarid = PCIR_MBBAR; sc->regtype = SYS_RES_MEMORY; pci_write_config(dev, PCIR_ICH_LEGACY, ICH_LEGACY_ENABLE, 1); } else { sc->nambarid = PCIR_NAMBAR; sc->nabmbarid = PCIR_NABMBAR; sc->regtype = SYS_RES_IOPORT; } sc->nambar = bus_alloc_resource_any(dev, sc->regtype, &sc->nambarid, RF_ACTIVE); sc->nabmbar = bus_alloc_resource_any(dev, sc->regtype, &sc->nabmbarid, RF_ACTIVE); if (!sc->nambar || !sc->nabmbar) { device_printf(dev, "unable to map IO port space\n"); goto bad; } sc->nambart = rman_get_bustag(sc->nambar); sc->nambarh = rman_get_bushandle(sc->nambar); sc->nabmbart = rman_get_bustag(sc->nabmbar); sc->nabmbarh = rman_get_bushandle(sc->nabmbar); sc->bufsz = pcm_getbuffersize(dev, ICH_MIN_BUFSZ, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { sc->blkcnt = sc->bufsz / i; i = 0; while (sc->blkcnt >> i) i++; sc->blkcnt = 1 << (i - 1); if (sc->blkcnt < ICH_MIN_BLKCNT) sc->blkcnt = ICH_MIN_BLKCNT; else if (sc->blkcnt > ICH_MAX_BLKCNT) sc->blkcnt = ICH_MAX_BLKCNT; } else sc->blkcnt = ICH_DEFAULT_BLKCNT; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "highlatency", &i) == 0 && i != 0) { sc->flags |= ICH_HIGH_LATENCY; sc->blkcnt = ICH_MIN_BLKCNT; } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "fixedrate", &i) == 0 && i != 0) sc->flags |= ICH_FIXED_RATE; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "micchannel_enabled", &i) == 0 && i != 0) sc->hasmic = 1; sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ich_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (ich_init(sc)) { device_printf(dev, "unable to initialize the card\n"); goto bad; } sc->codec = AC97_CREATE(dev, sc, ich_ac97); if (sc->codec == NULL) goto bad; /* * Turn on inverted external amplifier sense flags for few * 'special' boards. */ switch (subdev) { case 0x202f161f: /* Gateway 7326GZ */ case 0x203a161f: /* Gateway 4028GZ */ case 0x203e161f: /* Gateway 3520GZ/M210 */ case 0x204c161f: /* Kvazar-Micro Senator 3592XT */ case 0x8144104d: /* Sony VAIO PCG-TR* */ case 0x8197104d: /* Sony S1XP */ case 0x81c0104d: /* Sony VAIO type T */ case 0x81c5104d: /* Sony VAIO VGN B1VP/B1XP */ case 0x3089103c: /* Compaq Presario B3800 */ case 0x309a103c: /* HP Compaq nx4300 */ case 0x82131033: /* NEC VersaPro VJ10F/BH */ case 0x82be1033: /* NEC VersaPro VJ12F/CH */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: break; } mixer_init(dev, ac97_getmixerclass(), sc->codec); /* check and set VRA function */ extcaps = ac97_getextcaps(sc->codec); sc->hasvra = extcaps & AC97_EXTCAP_VRA; sc->hasvrm = extcaps & AC97_EXTCAP_VRM; sc->hasmic = (sc->hasmic != 0 && (ac97_getcaps(sc->codec) & AC97_CAP_MICCHANNEL)) ? 1 : 0; ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); sc->dtbl_size = sizeof(struct ich_desc) * ICH_DTBL_LENGTH * ((sc->hasmic) ? 3 : 2); /* BDL tag */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->dtbl_size, 1, 0x3ffff, 0, NULL, NULL, &sc->dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* PCM channel tag */ if (bus_dma_tag_create(bus_get_dma_tag(dev), ICH_MIN_BLKSZ, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->bufsz, 1, 0x3ffff, 0, NULL, NULL, &sc->chan_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT | ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), &sc->dtmap)) goto bad; if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sc->dtbl_size, ich_setmap, sc, 0)) goto bad; pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &ichchan_class, sc); /* play */ pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record */ if (sc->hasmic) pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record mic */ if (sc->flags & ICH_FIXED_RATE) { sc->flags |= ICH_CALIBRATE_DONE; ich_setstatus(sc); } else { ich_initsys(sc); sc->intrhook.ich_func = ich_calibrate; sc->intrhook.ich_arg = sc; if (cold == 0 || config_intrhook_establish(&sc->intrhook) != 0) { sc->intrhook.ich_func = NULL; ich_calibrate(sc); } } return (0); bad: if (sc->codec) ac97_destroy(sc->codec); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->nambar) bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); if (sc->nabmbar) bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); if (sc->dtmap) bus_dmamap_unload(sc->dmat, sc->dtmap); if (sc->dtbl) bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); if (sc->chan_dmat) bus_dma_tag_destroy(sc->chan_dmat); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->ich_lock) snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return (ENXIO); } static int ich_pci_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return (r); sc = pcm_getdevinfo(dev); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); bus_dmamap_unload(sc->dmat, sc->dtmap); bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); bus_dma_tag_destroy(sc->chan_dmat); bus_dma_tag_destroy(sc->dmat); snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return (0); } static void ich_pci_codec_reset(struct sc_info *sc) { int i; uint32_t control; control = ich_rd(sc, ICH_REG_GLOB_CNT, 4); control &= ~(ICH_GLOB_CTL_SHUT); control |= (control & ICH_GLOB_CTL_COLD) ? ICH_GLOB_CTL_WARM : ICH_GLOB_CTL_COLD; ich_wr(sc, ICH_REG_GLOB_CNT, control, 4); for (i = 500000; i; i--) { if (ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_PCR) break; /* or ICH_SCR? */ DELAY(1); } if (i <= 0) printf("%s: time out\n", __func__); } static int ich_pci_suspend(device_t dev) { struct sc_info *sc; int i; sc = pcm_getdevinfo(dev); ICH_LOCK(sc); for (i = 0 ; i < 3; i++) { sc->ch[i].run_save = sc->ch[i].run; if (sc->ch[i].run) { ICH_UNLOCK(sc); ichchan_trigger(0, &sc->ch[i], PCMTRIG_ABORT); ICH_LOCK(sc); } } ICH_UNLOCK(sc); return (0); } static int ich_pci_resume(device_t dev) { struct sc_info *sc; int err, i; sc = pcm_getdevinfo(dev); ICH_LOCK(sc); /* Reinit audio device */ err = ich_init(sc); if (err != 0) { device_printf(dev, "unable to reinitialize the card\n"); ICH_UNLOCK(sc); return (err); } /* Reinit mixer */ ich_pci_codec_reset(sc); ICH_UNLOCK(sc); ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } /* Re-start DMA engines */ for (i = 0 ; i < 3; i++) { struct sc_chinfo *ch = &sc->ch[i]; if (sc->ch[i].run_save) { ichchan_setblocksize(0, ch, ch->blksz); ichchan_setspeed(0, ch, ch->spd); ichchan_trigger(0, ch, PCMTRIG_START); } } return (0); } static device_method_t ich_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ich_pci_probe), DEVMETHOD(device_attach, ich_pci_attach), DEVMETHOD(device_detach, ich_pci_detach), DEVMETHOD(device_suspend, ich_pci_suspend), DEVMETHOD(device_resume, ich_pci_resume), { 0, 0 } }; static driver_t ich_driver = { "pcm", ich_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_ich, pci, ich_driver, 0, 0); MODULE_DEPEND(snd_ich, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_ich, 1); diff --git a/sys/dev/sound/pci/maestro3.c b/sys/dev/sound/pci/maestro3.c index 2d102fcd6dbe..ee39f5f3c90d 100644 --- a/sys/dev/sound/pci/maestro3.c +++ b/sys/dev/sound/pci/maestro3.c @@ -1,1794 +1,1794 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Scott Long * Copyright (c) 2001 Darrell Anderson * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * Maestro-3/Allegro FreeBSD pcm sound driver * * executive status summary: * (+) /dev/dsp multiple concurrent play channels. * (+) /dev/dsp config (speed, mono/stereo, 8/16 bit). * (+) /dev/mixer sets left/right volumes. * (+) /dev/dsp recording works. Tested successfully with the cdrom channel * (+) apm suspend/resume works, and works properly!. * (-) hardware volme controls don't work =-( * (-) setblocksize() does nothing. * * The real credit goes to: * * Zach Brown for his Linux driver core and helpful technical comments. * , http://www.zabbo.net/maestro3 * * Cameron Grant created the pcm framework used here nearly verbatim. * , https://people.freebsd.org/~cg/template.c * * Taku YAMAMOTO for his Maestro-1/2 FreeBSD driver and sanity reference. * * * ESS docs explained a few magic registers and numbers. * http://virgo.caltech.edu/~dmoore/maestro3.pdf.gz */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #define M3_MODEL 1 #include #include /* -------------------------------------------------------------------- */ enum {CHANGE=0, CALL=1, INTR=2, BORING=3, NONE=-1}; #ifndef M3_DEBUG_LEVEL #define M3_DEBUG_LEVEL NONE #endif #define M3_DEBUG(level, _msg) {if ((level) <= M3_DEBUG_LEVEL) {printf _msg;}} /* -------------------------------------------------------------------- */ enum { ESS_ALLEGRO_1, ESS_MAESTRO3 }; static struct m3_card_type { u_int32_t pci_id; int which; int delay1; int delay2; char *name; } m3_card_types[] = { { 0x1988125d, ESS_ALLEGRO_1, 50, 800, "ESS Technology Allegro-1" }, { 0x1998125d, ESS_MAESTRO3, 20, 500, "ESS Technology Maestro3" }, { 0x199a125d, ESS_MAESTRO3, 20, 500, "ESS Technology Maestro3" }, { 0, 0, 0, 0, NULL } }; #define M3_BUFSIZE_MIN 4096 #define M3_BUFSIZE_MAX 65536 #define M3_BUFSIZE_DEFAULT 4096 #define M3_PCHANS 4 /* create /dev/dsp0.[0-N] to use more than one */ #define M3_RCHANS 1 #define M3_MAXADDR ((1 << 27) - 1) #define M3_DEFAULT_VOL 0x6800 struct sc_info; struct sc_pchinfo { u_int32_t spd; u_int32_t fmt; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; u_int32_t bufsize; u_int32_t dac_data; u_int32_t dac_idx; u_int32_t active; u_int32_t ptr; u_int32_t prevptr; }; struct sc_rchinfo { u_int32_t spd; u_int32_t fmt; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; u_int32_t bufsize; u_int32_t adc_data; u_int32_t adc_idx; u_int32_t active; u_int32_t ptr; u_int32_t prevptr; }; struct sc_info { device_t dev; u_int32_t type; int which; int delay1; int delay2; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg; struct resource *irq; int regtype; int regid; int irqid; void *ih; struct sc_pchinfo pch[M3_PCHANS]; struct sc_rchinfo rch[M3_RCHANS]; int pch_cnt; int rch_cnt; int pch_active_cnt; unsigned int bufsz; u_int16_t *savemem; struct mtx *sc_lock; }; #define M3_LOCK(_sc) snd_mtxlock((_sc)->sc_lock) #define M3_UNLOCK(_sc) snd_mtxunlock((_sc)->sc_lock) #define M3_LOCK_ASSERT(_sc) snd_mtxassert((_sc)->sc_lock) /* -------------------------------------------------------------------- */ /* play channel interface */ static void *m3_pchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int m3_pchan_free(kobj_t, void *); static int m3_pchan_setformat(kobj_t, void *, u_int32_t); static u_int32_t m3_pchan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t m3_pchan_setblocksize(kobj_t, void *, u_int32_t); static int m3_pchan_trigger(kobj_t, void *, int); static int m3_pchan_trigger_locked(kobj_t, void *, int); static u_int32_t m3_pchan_getptr_internal(struct sc_pchinfo *); static u_int32_t m3_pchan_getptr(kobj_t, void *); static struct pcmchan_caps *m3_pchan_getcaps(kobj_t, void *); /* record channel interface */ static void *m3_rchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int m3_rchan_free(kobj_t, void *); static int m3_rchan_setformat(kobj_t, void *, u_int32_t); static u_int32_t m3_rchan_setspeed(kobj_t, void *, u_int32_t); static u_int32_t m3_rchan_setblocksize(kobj_t, void *, u_int32_t); static int m3_rchan_trigger(kobj_t, void *, int); static int m3_rchan_trigger_locked(kobj_t, void *, int); static u_int32_t m3_rchan_getptr_internal(struct sc_rchinfo *); static u_int32_t m3_rchan_getptr(kobj_t, void *); static struct pcmchan_caps *m3_rchan_getcaps(kobj_t, void *); static int m3_chan_active(struct sc_info *); /* talk to the codec - called from ac97.c */ static u_int32_t m3_initcd(kobj_t, void *); static int m3_rdcd(kobj_t, void *, int); static int m3_wrcd(kobj_t, void *, int, u_int32_t); /* stuff */ static void m3_intr(void *); static int m3_power(struct sc_info *, int); static int m3_init(struct sc_info *); static int m3_uninit(struct sc_info *); static u_int8_t m3_assp_halt(struct sc_info *); static void m3_config(struct sc_info *); static void m3_amp_enable(struct sc_info *); static void m3_enable_ints(struct sc_info *); static void m3_codec_reset(struct sc_info *); /* -------------------------------------------------------------------- */ /* Codec descriptor */ static kobj_method_t m3_codec_methods[] = { KOBJMETHOD(ac97_init, m3_initcd), KOBJMETHOD(ac97_read, m3_rdcd), KOBJMETHOD(ac97_write, m3_wrcd), KOBJMETHOD_END }; AC97_DECLARE(m3_codec); /* -------------------------------------------------------------------- */ /* channel descriptors */ static u_int32_t m3_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps m3_playcaps = {8000, 48000, m3_playfmt, 0}; static kobj_method_t m3_pch_methods[] = { KOBJMETHOD(channel_init, m3_pchan_init), KOBJMETHOD(channel_setformat, m3_pchan_setformat), KOBJMETHOD(channel_setspeed, m3_pchan_setspeed), KOBJMETHOD(channel_setblocksize, m3_pchan_setblocksize), KOBJMETHOD(channel_trigger, m3_pchan_trigger), KOBJMETHOD(channel_getptr, m3_pchan_getptr), KOBJMETHOD(channel_getcaps, m3_pchan_getcaps), KOBJMETHOD(channel_free, m3_pchan_free), KOBJMETHOD_END }; CHANNEL_DECLARE(m3_pch); static u_int32_t m3_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps m3_reccaps = {8000, 48000, m3_recfmt, 0}; static kobj_method_t m3_rch_methods[] = { KOBJMETHOD(channel_init, m3_rchan_init), KOBJMETHOD(channel_setformat, m3_rchan_setformat), KOBJMETHOD(channel_setspeed, m3_rchan_setspeed), KOBJMETHOD(channel_setblocksize, m3_rchan_setblocksize), KOBJMETHOD(channel_trigger, m3_rchan_trigger), KOBJMETHOD(channel_getptr, m3_rchan_getptr), KOBJMETHOD(channel_getcaps, m3_rchan_getcaps), KOBJMETHOD(channel_free, m3_rchan_free), KOBJMETHOD_END }; CHANNEL_DECLARE(m3_rch); /* -------------------------------------------------------------------- */ /* some i/o convenience functions */ #define m3_rd_1(sc, regno) bus_space_read_1(sc->st, sc->sh, regno) #define m3_rd_2(sc, regno) bus_space_read_2(sc->st, sc->sh, regno) #define m3_rd_4(sc, regno) bus_space_read_4(sc->st, sc->sh, regno) #define m3_wr_1(sc, regno, data) bus_space_write_1(sc->st, sc->sh, regno, data) #define m3_wr_2(sc, regno, data) bus_space_write_2(sc->st, sc->sh, regno, data) #define m3_wr_4(sc, regno, data) bus_space_write_4(sc->st, sc->sh, regno, data) #define m3_rd_assp_code(sc, index) \ m3_rd_assp(sc, MEMTYPE_INTERNAL_CODE, index) #define m3_wr_assp_code(sc, index, data) \ m3_wr_assp(sc, MEMTYPE_INTERNAL_CODE, index, data) #define m3_rd_assp_data(sc, index) \ m3_rd_assp(sc, MEMTYPE_INTERNAL_DATA, index) #define m3_wr_assp_data(sc, index, data) \ m3_wr_assp(sc, MEMTYPE_INTERNAL_DATA, index, data) static __inline u_int16_t m3_rd_assp(struct sc_info *sc, u_int16_t region, u_int16_t index) { m3_wr_2(sc, DSP_PORT_MEMORY_TYPE, region & MEMTYPE_MASK); m3_wr_2(sc, DSP_PORT_MEMORY_INDEX, index); return m3_rd_2(sc, DSP_PORT_MEMORY_DATA); } static __inline void m3_wr_assp(struct sc_info *sc, u_int16_t region, u_int16_t index, u_int16_t data) { m3_wr_2(sc, DSP_PORT_MEMORY_TYPE, region & MEMTYPE_MASK); m3_wr_2(sc, DSP_PORT_MEMORY_INDEX, index); m3_wr_2(sc, DSP_PORT_MEMORY_DATA, data); } static __inline int m3_wait(struct sc_info *sc) { int i; for (i=0 ; i<20 ; i++) { if ((m3_rd_1(sc, CODEC_STATUS) & 1) == 0) { return 0; } DELAY(2); } return -1; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static u_int32_t m3_initcd(kobj_t kobj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; M3_DEBUG(CALL, ("m3_initcd\n")); /* init ac-link */ data = m3_rd_1(sc, CODEC_COMMAND); return ((data & 0x1) ? 0 : 1); } static int m3_rdcd(kobj_t kobj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data; if (m3_wait(sc)) { device_printf(sc->dev, "m3_rdcd timed out.\n"); return -1; } m3_wr_1(sc, CODEC_COMMAND, (regno & 0x7f) | 0x80); DELAY(50); /* ac97 cycle = 20.8 usec */ if (m3_wait(sc)) { device_printf(sc->dev, "m3_rdcd timed out.\n"); return -1; } data = m3_rd_2(sc, CODEC_DATA); return data; } static int m3_wrcd(kobj_t kobj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; if (m3_wait(sc)) { device_printf(sc->dev, "m3_wrcd timed out.\n"); return -1; } m3_wr_2(sc, CODEC_DATA, data); m3_wr_1(sc, CODEC_COMMAND, regno & 0x7f); DELAY(50); /* ac97 cycle = 20.8 usec */ return 0; } /* -------------------------------------------------------------------- */ /* play channel interface */ #define LO(x) (((x) & 0x0000ffff) ) #define HI(x) (((x) & 0xffff0000) >> 16) static void * m3_pchan_init(kobj_t kobj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_pchinfo *ch; u_int32_t bus_addr, i; int idx, data_bytes, dac_data; int dsp_in_size, dsp_out_size, dsp_in_buf, dsp_out_buf; struct data_word { u_int16_t addr, val; } pv[] = { {CDATA_LEFT_VOLUME, M3_DEFAULT_VOL}, {CDATA_RIGHT_VOLUME, M3_DEFAULT_VOL}, {SRC3_DIRECTION_OFFSET, 0} , {SRC3_DIRECTION_OFFSET + 3, 0x0000}, {SRC3_DIRECTION_OFFSET + 4, 0}, {SRC3_DIRECTION_OFFSET + 5, 0}, {SRC3_DIRECTION_OFFSET + 6, 0}, {SRC3_DIRECTION_OFFSET + 7, 0}, {SRC3_DIRECTION_OFFSET + 8, 0}, {SRC3_DIRECTION_OFFSET + 9, 0}, {SRC3_DIRECTION_OFFSET + 10, 0x8000}, {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, {SRC3_DIRECTION_OFFSET + 13, 0}, {SRC3_DIRECTION_OFFSET + 14, 0}, {SRC3_DIRECTION_OFFSET + 15, 0}, {SRC3_DIRECTION_OFFSET + 16, 8}, {SRC3_DIRECTION_OFFSET + 17, 50*2}, {SRC3_DIRECTION_OFFSET + 18, MINISRC_BIQUAD_STAGE - 1}, {SRC3_DIRECTION_OFFSET + 20, 0}, {SRC3_DIRECTION_OFFSET + 21, 0} }; M3_LOCK(sc); idx = sc->pch_cnt; /* dac instance number, no active reuse! */ M3_DEBUG(CHANGE, ("m3_pchan_init(dac=%d)\n", idx)); if (dir != PCMDIR_PLAY) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_pchan_init not PCMDIR_PLAY\n"); return (NULL); } data_bytes = (((MINISRC_TMP_BUFFER_SIZE & ~1) + (MINISRC_IN_BUFFER_SIZE & ~1) + (MINISRC_OUT_BUFFER_SIZE & ~1) + 4) + 255) &~ 255; dac_data = 0x1100 + (data_bytes * idx); dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x20 * 2); dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x20 * 2); dsp_in_buf = dac_data + (MINISRC_TMP_BUFFER_SIZE/2); dsp_out_buf = dsp_in_buf + (dsp_in_size/2) + 1; ch = &sc->pch[idx]; ch->dac_idx = idx; ch->dac_data = dac_data; if (ch->dac_data + data_bytes/2 >= 0x1c00) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_pchan_init: revb mem exhausted\n"); return (NULL); } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_pchan_init chn_allocbuf failed\n"); return (NULL); } M3_LOCK(sc); - ch->bufsize = sndbuf_getsize(ch->buffer); + ch->bufsize = ch->buffer->bufsize; /* host dma buffer pointers */ - bus_addr = sndbuf_getbufaddr(ch->buffer); + bus_addr = ch->buffer->buf_addr; if (bus_addr & 3) { device_printf(sc->dev, "m3_pchan_init unaligned bus_addr\n"); bus_addr = (bus_addr + 4) & ~3; } m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_ADDRL, LO(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_ADDRH, HI(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_END_PLUS_1L, LO(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_END_PLUS_1H, HI(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL, LO(bus_addr)); m3_wr_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH, HI(bus_addr)); /* dsp buffers */ m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_BEGIN, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_END_PLUS_1, dsp_in_buf + dsp_in_size/2); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_HEAD, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_IN_BUF_TAIL, dsp_in_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_BEGIN, dsp_out_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_END_PLUS_1, dsp_out_buf + dsp_out_size/2); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_HEAD, dsp_out_buf); m3_wr_assp_data(sc, ch->dac_data + CDATA_OUT_BUF_TAIL, dsp_out_buf); /* some per client initializers */ m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 12, ch->dac_data + 40 + 8); m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 19, 0x400 + MINISRC_COEF_LOC); /* enable or disable low pass filter? (0xff if rate> 45000) */ m3_wr_assp_data(sc, ch->dac_data + SRC3_DIRECTION_OFFSET + 22, 0); /* tell it which way dma is going? */ m3_wr_assp_data(sc, ch->dac_data + CDATA_DMA_CONTROL, DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); /* set an armload of static initializers */ for(i = 0 ; i < nitems(pv); i++) { m3_wr_assp_data(sc, ch->dac_data + pv[i].addr, pv[i].val); } /* put us in the packed task lists */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt + sc->rch_cnt), ch->dac_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt + sc->rch_cnt), ch->dac_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_MIXER_XFER0 + sc->pch_cnt, ch->dac_data >> DP_SHIFT_COUNT); /* gotta start before stop */ m3_pchan_trigger_locked(NULL, ch, PCMTRIG_START); /* silence noise on load */ m3_pchan_trigger_locked(NULL, ch, PCMTRIG_STOP); sc->pch_cnt++; M3_UNLOCK(sc); return (ch); } static int m3_pchan_free(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_free(dac=%d)\n", ch->dac_idx)); /* * should remove this exact instance from the packed lists, but all * are released at once (and in a stopped state) so this is ok. */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt - 1) + sc->rch_cnt, 0); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt - 1) + sc->rch_cnt, 0); m3_wr_assp_data(sc, KDATA_MIXER_XFER0 + (sc->pch_cnt-1), 0); sc->pch_cnt--; M3_UNLOCK(sc); return (0); } static int m3_pchan_setformat(kobj_t kobj, void *chdata, u_int32_t format) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_setformat(dac=%d, format=0x%x{%s-%s})\n", ch->dac_idx, format, format & (AFMT_U8|AFMT_S8) ? "8bit":"16bit", (AFMT_CHANNEL(format) > 1) ? "STEREO":"MONO")); /* mono word */ data = (AFMT_CHANNEL(format) > 1)? 0 : 1; m3_wr_assp_data(sc, ch->dac_data + SRC3_MODE_OFFSET, data); /* 8bit word */ data = ((format & AFMT_U8) || (format & AFMT_S8)) ? 1 : 0; m3_wr_assp_data(sc, ch->dac_data + SRC3_WORD_LENGTH_OFFSET, data); ch->fmt = format; M3_UNLOCK(sc); return (0); } static u_int32_t m3_pchan_setspeed(kobj_t kobj, void *chdata, u_int32_t speed) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t freq; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_pchan_setspeed(dac=%d, speed=%d)\n", ch->dac_idx, speed)); if ((freq = ((speed << 15) + 24000) / 48000) != 0) { freq--; } m3_wr_assp_data(sc, ch->dac_data + CDATA_FREQUENCY, freq); ch->spd = speed; M3_UNLOCK(sc); /* return closest possible speed */ return (speed); } static u_int32_t m3_pchan_setblocksize(kobj_t kobj, void *chdata, u_int32_t blocksize) { struct sc_pchinfo *ch = chdata; M3_DEBUG(CHANGE, ("m3_pchan_setblocksize(dac=%d, blocksize=%d)\n", ch->dac_idx, blocksize)); - return (sndbuf_getblksz(ch->buffer)); + return (ch->buffer->blksz); } static int m3_pchan_trigger(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; int ret; if (!PCMTRIG_COMMON(go)) return (0); M3_LOCK(sc); ret = m3_pchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); return (ret); } static int m3_chan_active(struct sc_info *sc) { int i, ret; ret = 0; for (i = 0; i < sc->pch_cnt; i++) ret += sc->pch[i].active; for (i = 0; i < sc->rch_cnt; i++) ret += sc->rch[i].active; return (ret); } static int m3_pchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK_ASSERT(sc); M3_DEBUG(go == PCMTRIG_START ? CHANGE : go == PCMTRIG_STOP ? CHANGE : go == PCMTRIG_ABORT ? CHANGE : CALL, ("m3_pchan_trigger(dac=%d, go=0x%x{%s})\n", ch->dac_idx, go, go == PCMTRIG_START ? "PCMTRIG_START" : go == PCMTRIG_STOP ? "PCMTRIG_STOP" : go == PCMTRIG_ABORT ? "PCMTRIG_ABORT" : "ignore")); switch(go) { case PCMTRIG_START: if (ch->active) { return 0; } ch->active = 1; ch->ptr = 0; ch->prevptr = 0; sc->pch_active_cnt++; /*[[inc_timer_users]]*/ if (m3_chan_active(sc) == 1) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 1); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, sc->pch_active_cnt); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (ch->active == 0) { return 0; } ch->active = 0; sc->pch_active_cnt--; /* XXX should the channel be drained? */ /*[[dec_timer_users]]*/ if (m3_chan_active(sc) == 0) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, sc->pch_active_cnt); break; case PCMTRIG_EMLDMAWR: /* got play irq, transfer next buffer - ignore if using dma */ case PCMTRIG_EMLDMARD: /* got rec irq, transfer next buffer - ignore if using dma */ default: break; } return 0; } static u_int32_t m3_pchan_getptr_internal(struct sc_pchinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; - bus_base = sndbuf_getbufaddr(ch->buffer); + bus_base = ch->buffer->buf_addr; hi = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL); bus_crnt = lo | (hi << 16); M3_DEBUG(CALL, ("m3_pchan_getptr(dac=%d) result=%d\n", ch->dac_idx, bus_crnt - bus_base)); return (bus_crnt - bus_base); /* current byte offset of channel */ } static u_int32_t m3_pchan_getptr(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t ptr; M3_LOCK(sc); ptr = ch->ptr; M3_UNLOCK(sc); return (ptr); } static struct pcmchan_caps * m3_pchan_getcaps(kobj_t kobj, void *chdata) { struct sc_pchinfo *ch = chdata; M3_DEBUG(CALL, ("m3_pchan_getcaps(dac=%d)\n", ch->dac_idx)); return &m3_playcaps; } /* -------------------------------------------------------------------- */ /* rec channel interface */ static void * m3_rchan_init(kobj_t kobj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_rchinfo *ch; u_int32_t bus_addr, i; int idx, data_bytes, adc_data; int dsp_in_size, dsp_out_size, dsp_in_buf, dsp_out_buf; struct data_word { u_int16_t addr, val; } rv[] = { {CDATA_LEFT_VOLUME, M3_DEFAULT_VOL}, {CDATA_RIGHT_VOLUME, M3_DEFAULT_VOL}, {SRC3_DIRECTION_OFFSET, 1}, {SRC3_DIRECTION_OFFSET + 3, 0x0000}, {SRC3_DIRECTION_OFFSET + 4, 0}, {SRC3_DIRECTION_OFFSET + 5, 0}, {SRC3_DIRECTION_OFFSET + 6, 0}, {SRC3_DIRECTION_OFFSET + 7, 0}, {SRC3_DIRECTION_OFFSET + 8, 0}, {SRC3_DIRECTION_OFFSET + 9, 0}, {SRC3_DIRECTION_OFFSET + 10, 0x8000}, {SRC3_DIRECTION_OFFSET + 11, 0xFF00}, {SRC3_DIRECTION_OFFSET + 13, 0}, {SRC3_DIRECTION_OFFSET + 14, 0}, {SRC3_DIRECTION_OFFSET + 15, 0}, {SRC3_DIRECTION_OFFSET + 16, 50}, {SRC3_DIRECTION_OFFSET + 17, 8}, {SRC3_DIRECTION_OFFSET + 18, 0}, {SRC3_DIRECTION_OFFSET + 19, 0}, {SRC3_DIRECTION_OFFSET + 20, 0}, {SRC3_DIRECTION_OFFSET + 21, 0}, {SRC3_DIRECTION_OFFSET + 22, 0xff} }; M3_LOCK(sc); idx = sc->rch_cnt; /* adc instance number, no active reuse! */ M3_DEBUG(CHANGE, ("m3_rchan_init(adc=%d)\n", idx)); if (dir != PCMDIR_REC) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_pchan_init not PCMDIR_REC\n"); return (NULL); } data_bytes = (((MINISRC_TMP_BUFFER_SIZE & ~1) + (MINISRC_IN_BUFFER_SIZE & ~1) + (MINISRC_OUT_BUFFER_SIZE & ~1) + 4) + 255) &~ 255; adc_data = 0x1100 + (data_bytes * idx) + data_bytes/2; dsp_in_size = MINISRC_IN_BUFFER_SIZE + (0x10 * 2); dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x10 * 2); dsp_in_buf = adc_data + (MINISRC_TMP_BUFFER_SIZE / 2); dsp_out_buf = dsp_in_buf + (dsp_in_size / 2) + 1; ch = &sc->rch[idx]; ch->adc_idx = idx; ch->adc_data = adc_data; if (ch->adc_data + data_bytes/2 >= 0x1c00) { M3_UNLOCK(sc); device_printf(sc->dev, "m3_rchan_init: revb mem exhausted\n"); return (NULL); } ch->buffer = b; ch->parent = sc; ch->channel = c; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_rchan_init chn_allocbuf failed\n"); return (NULL); } M3_LOCK(sc); - ch->bufsize = sndbuf_getsize(ch->buffer); + ch->bufsize = ch->buffer->bufsize; /* host dma buffer pointers */ - bus_addr = sndbuf_getbufaddr(ch->buffer); + bus_addr = ch->buffer->buf_addr; if (bus_addr & 3) { device_printf(sc->dev, "m3_rchan_init unaligned bus_addr\n"); bus_addr = (bus_addr + 4) & ~3; } m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_ADDRL, LO(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_ADDRH, HI(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_END_PLUS_1L, LO(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_END_PLUS_1H, HI(bus_addr + ch->bufsize)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL, LO(bus_addr)); m3_wr_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH, HI(bus_addr)); /* dsp buffers */ m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_BEGIN, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_END_PLUS_1, dsp_in_buf + dsp_in_size/2); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_HEAD, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_IN_BUF_TAIL, dsp_in_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_BEGIN, dsp_out_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_END_PLUS_1, dsp_out_buf + dsp_out_size/2); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_HEAD, dsp_out_buf); m3_wr_assp_data(sc, ch->adc_data + CDATA_OUT_BUF_TAIL, dsp_out_buf); /* some per client initializers */ m3_wr_assp_data(sc, ch->adc_data + SRC3_DIRECTION_OFFSET + 12, ch->adc_data + 40 + 8); m3_wr_assp_data(sc, ch->adc_data + CDATA_DMA_CONTROL, DMACONTROL_DIRECTION + DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR); /* set an armload of static initializers */ for(i = 0 ; i < nitems(rv); i++) { m3_wr_assp_data(sc, ch->adc_data + rv[i].addr, rv[i].val); } /* put us in the packed task lists */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->pch_cnt + sc->rch_cnt), ch->adc_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->pch_cnt + sc->rch_cnt), ch->adc_data >> DP_SHIFT_COUNT); m3_wr_assp_data(sc, KDATA_ADC1_XFER0 + sc->rch_cnt, ch->adc_data >> DP_SHIFT_COUNT); /* gotta start before stop */ m3_rchan_trigger_locked(NULL, ch, PCMTRIG_START); /* stop on init */ m3_rchan_trigger_locked(NULL, ch, PCMTRIG_STOP); sc->rch_cnt++; M3_UNLOCK(sc); return (ch); } static int m3_rchan_free(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_free(adc=%d)\n", ch->adc_idx)); /* * should remove this exact instance from the packed lists, but all * are released at once (and in a stopped state) so this is ok. */ m3_wr_assp_data(sc, KDATA_INSTANCE0_MINISRC + (sc->rch_cnt - 1) + sc->pch_cnt, 0); m3_wr_assp_data(sc, KDATA_DMA_XFER0 + (sc->rch_cnt - 1) + sc->pch_cnt, 0); m3_wr_assp_data(sc, KDATA_ADC1_XFER0 + (sc->rch_cnt - 1), 0); sc->rch_cnt--; M3_UNLOCK(sc); return (0); } static int m3_rchan_setformat(kobj_t kobj, void *chdata, u_int32_t format) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_setformat(dac=%d, format=0x%x{%s-%s})\n", ch->adc_idx, format, format & (AFMT_U8|AFMT_S8) ? "8bit":"16bit", (AFMT_CHANNEL(format) > 1) ? "STEREO":"MONO")); /* mono word */ data = (AFMT_CHANNEL(format) > 1) ? 0 : 1; m3_wr_assp_data(sc, ch->adc_data + SRC3_MODE_OFFSET, data); /* 8bit word */ data = ((format & AFMT_U8) || (format & AFMT_S8)) ? 1 : 0; m3_wr_assp_data(sc, ch->adc_data + SRC3_WORD_LENGTH_OFFSET, data); ch->fmt = format; M3_UNLOCK(sc); return (0); } static u_int32_t m3_rchan_setspeed(kobj_t kobj, void *chdata, u_int32_t speed) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t freq; M3_LOCK(sc); M3_DEBUG(CHANGE, ("m3_rchan_setspeed(adc=%d, speed=%d)\n", ch->adc_idx, speed)); if ((freq = ((speed << 15) + 24000) / 48000) != 0) { freq--; } m3_wr_assp_data(sc, ch->adc_data + CDATA_FREQUENCY, freq); ch->spd = speed; M3_UNLOCK(sc); /* return closest possible speed */ return (speed); } static u_int32_t m3_rchan_setblocksize(kobj_t kobj, void *chdata, u_int32_t blocksize) { struct sc_rchinfo *ch = chdata; M3_DEBUG(CHANGE, ("m3_rchan_setblocksize(adc=%d, blocksize=%d)\n", ch->adc_idx, blocksize)); - return (sndbuf_getblksz(ch->buffer)); + return (ch->buffer->blksz); } static int m3_rchan_trigger(kobj_t kobj, void *chdata, int go) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; int ret; if (!PCMTRIG_COMMON(go)) return (0); M3_LOCK(sc); ret = m3_rchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); return (ret); } static int m3_rchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t data; M3_LOCK_ASSERT(sc); M3_DEBUG(go == PCMTRIG_START ? CHANGE : go == PCMTRIG_STOP ? CHANGE : go == PCMTRIG_ABORT ? CHANGE : CALL, ("m3_rchan_trigger(adc=%d, go=0x%x{%s})\n", ch->adc_idx, go, go == PCMTRIG_START ? "PCMTRIG_START" : go == PCMTRIG_STOP ? "PCMTRIG_STOP" : go == PCMTRIG_ABORT ? "PCMTRIG_ABORT" : "ignore")); switch(go) { case PCMTRIG_START: if (ch->active) { return 0; } ch->active = 1; ch->ptr = 0; ch->prevptr = 0; /*[[inc_timer_users]]*/ if (m3_chan_active(sc) == 1) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 1); m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 1); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (ch->active == 0) { return 0; } ch->active = 0; /*[[dec_timer_users]]*/ if (m3_chan_active(sc) == 0) { m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); data = m3_rd_2(sc, HOST_INT_CTRL); m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); } m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 0); break; case PCMTRIG_EMLDMAWR: /* got play irq, transfer next buffer - ignore if using dma */ case PCMTRIG_EMLDMARD: /* got rec irq, transfer next buffer - ignore if using dma */ default: break; } return 0; } static u_int32_t m3_rchan_getptr_internal(struct sc_rchinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; - bus_base = sndbuf_getbufaddr(ch->buffer); + bus_base = ch->buffer->buf_addr; hi = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL); bus_crnt = lo | (hi << 16); M3_DEBUG(CALL, ("m3_rchan_getptr(adc=%d) result=%d\n", ch->adc_idx, bus_crnt - bus_base)); return (bus_crnt - bus_base); /* current byte offset of channel */ } static u_int32_t m3_rchan_getptr(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t ptr; M3_LOCK(sc); ptr = ch->ptr; M3_UNLOCK(sc); return (ptr); } static struct pcmchan_caps * m3_rchan_getcaps(kobj_t kobj, void *chdata) { struct sc_rchinfo *ch = chdata; M3_DEBUG(CALL, ("m3_rchan_getcaps(adc=%d)\n", ch->adc_idx)); return &m3_reccaps; } /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void m3_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_pchinfo *pch; struct sc_rchinfo *rch; u_int32_t status, ctl, i, delta; M3_DEBUG(INTR, ("m3_intr\n")); M3_LOCK(sc); status = m3_rd_1(sc, HOST_INT_STATUS); if (!status) { M3_UNLOCK(sc); return; } m3_wr_1(sc, HOST_INT_STATUS, 0xff); /* ack the int? */ if (status & HV_INT_PENDING) { u_int8_t event; event = m3_rd_1(sc, HW_VOL_COUNTER_MASTER); switch (event) { case 0x99: mixer_hwvol_mute(sc->dev); break; case 0xaa: mixer_hwvol_step(sc->dev, 1, 1); break; case 0x66: mixer_hwvol_step(sc->dev, -1, -1); break; case 0x88: break; default: device_printf(sc->dev, "Unknown HWVOL event\n"); } m3_wr_1(sc, HW_VOL_COUNTER_MASTER, 0x88); } if (status & ASSP_INT_PENDING) { ctl = m3_rd_1(sc, ASSP_CONTROL_B); if (!(ctl & STOP_ASSP_CLOCK)) { ctl = m3_rd_1(sc, ASSP_HOST_INT_STATUS); if (ctl & DSP2HOST_REQ_TIMER) { m3_wr_1(sc, ASSP_HOST_INT_STATUS, DSP2HOST_REQ_TIMER); /*[[ess_update_ptr]]*/ goto m3_handle_channel_intr; } } } goto m3_handle_channel_intr_out; m3_handle_channel_intr: for (i=0 ; ipch_cnt ; i++) { pch = &sc->pch[i]; if (pch->active) { pch->ptr = m3_pchan_getptr_internal(pch); delta = pch->bufsize + pch->ptr - pch->prevptr; delta %= pch->bufsize; - if (delta < sndbuf_getblksz(pch->buffer)) + if (delta < pch->buffer->blksz) continue; pch->prevptr = pch->ptr; M3_UNLOCK(sc); chn_intr(pch->channel); M3_LOCK(sc); } } for (i=0 ; irch_cnt ; i++) { rch = &sc->rch[i]; if (rch->active) { rch->ptr = m3_rchan_getptr_internal(rch); delta = rch->bufsize + rch->ptr - rch->prevptr; delta %= rch->bufsize; - if (delta < sndbuf_getblksz(rch->buffer)) + if (delta < rch->buffer->blksz) continue; rch->prevptr = rch->ptr; M3_UNLOCK(sc); chn_intr(rch->channel); M3_LOCK(sc); } } m3_handle_channel_intr_out: M3_UNLOCK(sc); } /* -------------------------------------------------------------------- */ /* stuff */ static int m3_power(struct sc_info *sc, int state) { u_int32_t data; M3_DEBUG(CHANGE, ("m3_power(%d)\n", state)); M3_LOCK_ASSERT(sc); data = pci_read_config(sc->dev, 0x34, 1); if (pci_read_config(sc->dev, data, 1) == 1) { pci_write_config(sc->dev, data + 4, state, 1); } return 0; } static int m3_init(struct sc_info *sc) { u_int32_t data, i, size; u_int8_t reset_state; M3_LOCK_ASSERT(sc); M3_DEBUG(CHANGE, ("m3_init\n")); /* diable legacy emulations. */ data = pci_read_config(sc->dev, PCI_LEGACY_AUDIO_CTRL, 2); data |= DISABLE_LEGACY; pci_write_config(sc->dev, PCI_LEGACY_AUDIO_CTRL, data, 2); m3_config(sc); reset_state = m3_assp_halt(sc); m3_codec_reset(sc); /* [m3_assp_init] */ /* zero kernel data */ size = REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA; for(i = 0 ; i < size / 2 ; i++) { m3_wr_assp_data(sc, KDATA_BASE_ADDR + i, 0); } /* zero mixer data? */ size = REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA; for(i = 0 ; i < size / 2 ; i++) { m3_wr_assp_data(sc, KDATA_BASE_ADDR2 + i, 0); } /* init dma pointer */ m3_wr_assp_data(sc, KDATA_CURRENT_DMA, KDATA_DMA_XFER0); /* write kernel into code memory */ size = sizeof(gaw_kernel_vect_code); for(i = 0 ; i < size / 2; i++) { m3_wr_assp_code(sc, REV_B_CODE_MEMORY_BEGIN + i, gaw_kernel_vect_code[i]); } /* * We only have this one client and we know that 0x400 is free in * our kernel's mem map, so lets just drop it there. It seems that * the minisrc doesn't need vectors, so we won't bother with them.. */ size = sizeof(gaw_minisrc_code_0400); for(i = 0 ; i < size / 2; i++) { m3_wr_assp_code(sc, 0x400 + i, gaw_minisrc_code_0400[i]); } /* write the coefficients for the low pass filter? */ size = sizeof(minisrc_lpf); for(i = 0; i < size / 2 ; i++) { m3_wr_assp_code(sc,0x400 + MINISRC_COEF_LOC + i, minisrc_lpf[i]); } m3_wr_assp_code(sc, 0x400 + MINISRC_COEF_LOC + size, 0x8000); /* the minisrc is the only thing on our task list */ m3_wr_assp_data(sc, KDATA_TASK0, 0x400); /* init the mixer number */ m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, 0); /* extreme kernel master volume */ m3_wr_assp_data(sc, KDATA_DAC_LEFT_VOLUME, M3_DEFAULT_VOL); m3_wr_assp_data(sc, KDATA_DAC_RIGHT_VOLUME, M3_DEFAULT_VOL); m3_amp_enable(sc); /* [m3_assp_client_init] (only one client at index 0) */ for (i=0x1100 ; i<0x1c00 ; i++) { m3_wr_assp_data(sc, i, 0); /* zero entire dac/adc area */ } /* [m3_assp_continue] */ m3_wr_1(sc, DSP_PORT_CONTROL_REG_B, reset_state | REGB_ENABLE_RESET); return 0; } static int m3_uninit(struct sc_info *sc) { M3_DEBUG(CHANGE, ("m3_uninit\n")); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int m3_pci_probe(device_t dev) { struct m3_card_type *card; M3_DEBUG(CALL, ("m3_pci_probe(0x%x)\n", pci_get_devid(dev))); for (card = m3_card_types ; card->pci_id ; card++) { if (pci_get_devid(dev) == card->pci_id) { device_set_desc(dev, card->name); return BUS_PROBE_DEFAULT; } } return ENXIO; } static int m3_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; struct m3_card_type *card; int i, len, dacn, adcn; M3_DEBUG(CALL, ("m3_pci_attach\n")); sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); sc->sc_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_maestro3 softc"); for (card = m3_card_types ; card->pci_id ; card++) { if (sc->type == card->pci_id) { sc->which = card->which; sc->delay1 = card->delay1; sc->delay2 = card->delay2; break; } } if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &i) == 0) { if (i < 1) dacn = 1; else if (i > M3_PCHANS) dacn = M3_PCHANS; else dacn = i; } else dacn = M3_PCHANS; adcn = M3_RCHANS; pci_enable_busmaster(dev); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); } if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, m3_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->bufsz = pcm_getbuffersize(dev, M3_BUFSIZE_MIN, M3_BUFSIZE_DEFAULT, M3_BUFSIZE_MAX); if (bus_dma_tag_create( bus_get_dma_tag(dev), /* parent */ 2, 0, /* alignment, boundary */ M3_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ sc->bufsz, /* maxsize */ 1, /* nsegments */ 0x3ffff, /* maxsegz */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } M3_LOCK(sc); m3_power(sc, 0); /* power up */ /* init chip */ i = m3_init(sc); M3_UNLOCK(sc); if (i == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ codec = AC97_CREATE(dev, sc, m3_codec); if (codec == NULL) { device_printf(dev, "ac97_create error\n"); goto bad; } if (mixer_init(dev, ac97_getmixerclass(), codec)) { device_printf(dev, "mixer_init error\n"); goto bad; } m3_enable_ints(sc); pcm_init(dev, sc); for (i=0 ; iregtype == SYS_RES_IOPORT)? "port" : "mem", rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) { device_printf(dev, "pcm_register error\n"); goto bad; } mixer_hwvol_init(dev); /* Create the buffer for saving the card state during suspend */ len = sizeof(u_int16_t) * (REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH); sc->savemem = malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); return 0; bad: if (codec) ac97_destroy(codec); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->sc_lock) snd_mtxfree(sc->sc_lock); free(sc, M_DEVBUF); return ENXIO; } static int m3_pci_detach(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int r; M3_DEBUG(CALL, ("m3_pci_detach\n")); if ((r = pcm_unregister(dev)) != 0) { return r; } M3_LOCK(sc); m3_uninit(sc); /* shutdown chip */ m3_power(sc, 3); /* power off */ M3_UNLOCK(sc); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_dma_tag_destroy(sc->parent_dmat); free(sc->savemem, M_DEVBUF); snd_mtxfree(sc->sc_lock); free(sc, M_DEVBUF); return 0; } static int m3_pci_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int i, index = 0; M3_DEBUG(CHANGE, ("m3_pci_suspend\n")); M3_LOCK(sc); for (i=0 ; ipch_cnt ; i++) { if (sc->pch[i].active) { m3_pchan_trigger_locked(NULL, &sc->pch[i], PCMTRIG_STOP); } } for (i=0 ; irch_cnt ; i++) { if (sc->rch[i].active) { m3_rchan_trigger_locked(NULL, &sc->rch[i], PCMTRIG_STOP); } } DELAY(10 * 1000); /* give things a chance to stop */ /* Disable interrupts */ m3_wr_2(sc, HOST_INT_CTRL, 0); m3_wr_1(sc, ASSP_CONTROL_C, 0); m3_assp_halt(sc); /* Save the state of the ASSP */ for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++) sc->savemem[index++] = m3_rd_assp_code(sc, i); for (i = REV_B_DATA_MEMORY_BEGIN; i <= REV_B_DATA_MEMORY_END; i++) sc->savemem[index++] = m3_rd_assp_data(sc, i); /* Power down the card to D3 state */ m3_power(sc, 3); M3_UNLOCK(sc); return 0; } static int m3_pci_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int i, index = 0; u_int8_t reset_state; M3_DEBUG(CHANGE, ("m3_pci_resume\n")); M3_LOCK(sc); /* Power the card back to D0 */ m3_power(sc, 0); m3_config(sc); reset_state = m3_assp_halt(sc); m3_codec_reset(sc); /* Restore the ASSP state */ for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++) m3_wr_assp_code(sc, i, sc->savemem[index++]); for (i = REV_B_DATA_MEMORY_BEGIN; i <= REV_B_DATA_MEMORY_END; i++) m3_wr_assp_data(sc, i, sc->savemem[index++]); /* Restart the DMA engine */ m3_wr_assp_data(sc, KDATA_DMA_ACTIVE, 0); /* [m3_assp_continue] */ m3_wr_1(sc, DSP_PORT_CONTROL_REG_B, reset_state | REGB_ENABLE_RESET); m3_amp_enable(sc); m3_enable_ints(sc); M3_UNLOCK(sc); /* XXX */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return (ENXIO); } M3_LOCK(sc); /* Turn the channels back on */ for (i=0 ; ipch_cnt ; i++) { if (sc->pch[i].active) { m3_pchan_trigger_locked(NULL, &sc->pch[i], PCMTRIG_START); } } for (i=0 ; irch_cnt ; i++) { if (sc->rch[i].active) { m3_rchan_trigger_locked(NULL, &sc->rch[i], PCMTRIG_START); } } M3_UNLOCK(sc); return 0; } static int m3_pci_shutdown(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); M3_DEBUG(CALL, ("m3_pci_shutdown\n")); M3_LOCK(sc); m3_power(sc, 3); /* power off */ M3_UNLOCK(sc); return 0; } static u_int8_t m3_assp_halt(struct sc_info *sc) { u_int8_t data, reset_state; M3_LOCK_ASSERT(sc); data = m3_rd_1(sc, DSP_PORT_CONTROL_REG_B); reset_state = data & ~REGB_STOP_CLOCK; /* remember for continue */ DELAY(10 * 1000); m3_wr_1(sc, DSP_PORT_CONTROL_REG_B, reset_state & ~REGB_ENABLE_RESET); DELAY(10 * 1000); /* necessary? */ return reset_state; } static void m3_config(struct sc_info *sc) { u_int32_t data, hv_cfg; int hint; M3_LOCK_ASSERT(sc); M3_UNLOCK(sc); /* * The volume buttons can be wired up via two different sets of pins. * This presents a problem since we can't tell which way it's * configured. Allow the user to set a hint in order to twiddle * the proper bits. */ if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "hwvol_config", &hint) == 0) hv_cfg = (hint > 0) ? HV_BUTTON_FROM_GD : 0; else hv_cfg = HV_BUTTON_FROM_GD; M3_LOCK(sc); data = pci_read_config(sc->dev, PCI_ALLEGRO_CONFIG, 4); data &= ~HV_BUTTON_FROM_GD; data |= REDUCED_DEBOUNCE | HV_CTRL_ENABLE | hv_cfg; data |= PM_CTRL_ENABLE | CLK_DIV_BY_49 | USE_PCI_TIMING; pci_write_config(sc->dev, PCI_ALLEGRO_CONFIG, data, 4); m3_wr_1(sc, ASSP_CONTROL_B, RESET_ASSP); data = pci_read_config(sc->dev, PCI_ALLEGRO_CONFIG, 4); data &= ~INT_CLK_SELECT; if (sc->which == ESS_MAESTRO3) { data &= ~INT_CLK_MULT_ENABLE; data |= INT_CLK_SRC_NOT_PCI; } data &= ~(CLK_MULT_MODE_SELECT | CLK_MULT_MODE_SELECT_2); pci_write_config(sc->dev, PCI_ALLEGRO_CONFIG, data, 4); if (sc->which == ESS_ALLEGRO_1) { data = pci_read_config(sc->dev, PCI_USER_CONFIG, 4); data |= IN_CLK_12MHZ_SELECT; pci_write_config(sc->dev, PCI_USER_CONFIG, data, 4); } data = m3_rd_1(sc, ASSP_CONTROL_A); data &= ~(DSP_CLK_36MHZ_SELECT | ASSP_CLK_49MHZ_SELECT); data |= ASSP_CLK_49MHZ_SELECT; /*XXX assumes 49MHZ dsp XXX*/ data |= ASSP_0_WS_ENABLE; m3_wr_1(sc, ASSP_CONTROL_A, data); m3_wr_1(sc, ASSP_CONTROL_B, RUN_ASSP); } static void m3_enable_ints(struct sc_info *sc) { u_int8_t data; m3_wr_2(sc, HOST_INT_CTRL, ASSP_INT_ENABLE | HV_INT_ENABLE); data = m3_rd_1(sc, ASSP_CONTROL_C); m3_wr_1(sc, ASSP_CONTROL_C, data | ASSP_HOST_INT_ENABLE); } static void m3_amp_enable(struct sc_info *sc) { u_int32_t gpo, polarity_port, polarity; u_int16_t data; M3_LOCK_ASSERT(sc); switch (sc->which) { case ESS_ALLEGRO_1: polarity_port = 0x1800; break; case ESS_MAESTRO3: polarity_port = 0x1100; break; default: panic("bad sc->which"); } gpo = (polarity_port >> 8) & 0x0f; polarity = polarity_port >> 12; polarity = !polarity; /* enable */ polarity = polarity << gpo; gpo = 1 << gpo; m3_wr_2(sc, GPIO_MASK, ~gpo); data = m3_rd_2(sc, GPIO_DIRECTION); m3_wr_2(sc, GPIO_DIRECTION, data | gpo); data = GPO_SECONDARY_AC97 | GPO_PRIMARY_AC97 | polarity; m3_wr_2(sc, GPIO_DATA, data); m3_wr_2(sc, GPIO_MASK, ~0); } static void m3_codec_reset(struct sc_info *sc) { u_int16_t data, dir; int retry = 0; M3_LOCK_ASSERT(sc); do { data = m3_rd_2(sc, GPIO_DIRECTION); dir = data | 0x10; /* assuming pci bus master? */ /* [[remote_codec_config]] */ data = m3_rd_2(sc, RING_BUS_CTRL_B); m3_wr_2(sc, RING_BUS_CTRL_B, data & ~SECOND_CODEC_ID_MASK); data = m3_rd_2(sc, SDO_OUT_DEST_CTRL); m3_wr_2(sc, SDO_OUT_DEST_CTRL, data & ~COMMAND_ADDR_OUT); data = m3_rd_2(sc, SDO_IN_DEST_CTRL); m3_wr_2(sc, SDO_IN_DEST_CTRL, data & ~STATUS_ADDR_IN); m3_wr_2(sc, RING_BUS_CTRL_A, IO_SRAM_ENABLE); DELAY(20); m3_wr_2(sc, GPIO_DIRECTION, dir & ~GPO_PRIMARY_AC97); m3_wr_2(sc, GPIO_MASK, ~GPO_PRIMARY_AC97); m3_wr_2(sc, GPIO_DATA, 0); m3_wr_2(sc, GPIO_DIRECTION, dir | GPO_PRIMARY_AC97); DELAY(sc->delay1 * 1000); /*delay1 (ALLEGRO:50, MAESTRO3:20)*/ m3_wr_2(sc, GPIO_DATA, GPO_PRIMARY_AC97); DELAY(5); m3_wr_2(sc, RING_BUS_CTRL_A, IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE); m3_wr_2(sc, GPIO_MASK, ~0); DELAY(sc->delay2 * 1000); /*delay2 (ALLEGRO:800, MAESTRO3:500)*/ /* [[try read vendor]] */ data = m3_rdcd(NULL, sc, 0x7c); if ((data == 0) || (data == 0xffff)) { retry++; if (retry > 3) { device_printf(sc->dev, "Codec reset failed\n"); break; } device_printf(sc->dev, "Codec reset retry\n"); } else retry = 0; } while (retry); } static device_method_t m3_methods[] = { DEVMETHOD(device_probe, m3_pci_probe), DEVMETHOD(device_attach, m3_pci_attach), DEVMETHOD(device_detach, m3_pci_detach), DEVMETHOD(device_suspend, m3_pci_suspend), DEVMETHOD(device_resume, m3_pci_resume), DEVMETHOD(device_shutdown, m3_pci_shutdown), { 0, 0 } }; static driver_t m3_driver = { "pcm", m3_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_maestro3, pci, m3_driver, 0, 0); MODULE_DEPEND(snd_maestro3, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_maestro3, 1); diff --git a/sys/dev/sound/pci/neomagic.c b/sys/dev/sound/pci/neomagic.c index d7824c990a52..1fee943d9364 100644 --- a/sys/dev/sound/pci/neomagic.c +++ b/sys/dev/sound/pci/neomagic.c @@ -1,822 +1,822 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Derived from the public domain Linux driver * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include /* -------------------------------------------------------------------- */ #define NM_BUFFSIZE 16384 #define NM256AV_PCI_ID 0x800510c8 #define NM256ZX_PCI_ID 0x800610c8 struct sc_info; /* channel registers */ struct sc_chinfo { int active, spd, dir, fmt; u_int32_t blksize, wmark; struct snd_dbuf *buffer; struct pcm_channel *channel; struct sc_info *parent; }; /* device private data */ struct sc_info { device_t dev; u_int32_t type; struct resource *reg, *irq, *buf; int regid, irqid, bufid; void *ih; u_int32_t ac97_base, ac97_status, ac97_busy; u_int32_t buftop, pbuf, rbuf, cbuf, acbuf; u_int32_t playint, recint, misc1int, misc2int; u_int32_t irsz, badintr; struct sc_chinfo pch, rch; }; /* -------------------------------------------------------------------- */ /* * prototypes */ /* stuff */ static int nm_loadcoeff(struct sc_info *sc, int dir, int num); static int nm_setch(struct sc_chinfo *ch); static int nm_init(struct sc_info *); static void nm_intr(void *); /* talk to the card */ static u_int32_t nm_rd(struct sc_info *, int, int); static void nm_wr(struct sc_info *, int, u_int32_t, int); static u_int32_t nm_rdbuf(struct sc_info *, int, int); static void nm_wrbuf(struct sc_info *, int, u_int32_t, int); static u_int32_t badcards[] = { 0x0007103c, 0x008f1028, 0x00dd1014, 0x8005110a, }; #define NUM_BADCARDS (sizeof(badcards) / sizeof(u_int32_t)) /* The actual rates supported by the card. */ static int samplerates[9] = { 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 99999999 }; /* -------------------------------------------------------------------- */ static u_int32_t nm_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps nm_caps = {4000, 48000, nm_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t nm_rd(struct sc_info *sc, int regno, int size) { bus_space_tag_t st = rman_get_bustag(sc->reg); bus_space_handle_t sh = rman_get_bushandle(sc->reg); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void nm_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(sc->reg); bus_space_handle_t sh = rman_get_bushandle(sc->reg); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static u_int32_t nm_rdbuf(struct sc_info *sc, int regno, int size) { bus_space_tag_t st = rman_get_bustag(sc->buf); bus_space_handle_t sh = rman_get_bushandle(sc->buf); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void nm_wrbuf(struct sc_info *sc, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(sc->buf); bus_space_handle_t sh = rman_get_bushandle(sc->buf); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int nm_waitcd(struct sc_info *sc) { int cnt = 10; int fail = 1; while (cnt-- > 0) { if (nm_rd(sc, sc->ac97_status, 2) & sc->ac97_busy) { DELAY(100); } else { fail = 0; break; } } return (fail); } static u_int32_t nm_initcd(kobj_t obj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; nm_wr(sc, 0x6c0, 0x01, 1); #if 0 /* * The following code-line may cause a hang for some chipsets, see * PR 56617. * In case of a bugreport without this line have a look at the PR and * conditionize the code-line based upon the specific version of * the chip. */ nm_wr(sc, 0x6cc, 0x87, 1); #endif nm_wr(sc, 0x6cc, 0x80, 1); nm_wr(sc, 0x6cc, 0x00, 1); return 1; } static int nm_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t x; if (!nm_waitcd(sc)) { x = nm_rd(sc, sc->ac97_base + regno, 2); DELAY(1000); return x; } else { device_printf(sc->dev, "ac97 codec not ready\n"); return -1; } } static int nm_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; int cnt = 3; if (!nm_waitcd(sc)) { while (cnt-- > 0) { nm_wr(sc, sc->ac97_base + regno, data, 2); if (!nm_waitcd(sc)) { DELAY(1000); return 0; } } } device_printf(sc->dev, "ac97 codec not ready\n"); return -1; } static kobj_method_t nm_ac97_methods[] = { KOBJMETHOD(ac97_init, nm_initcd), KOBJMETHOD(ac97_read, nm_rdcd), KOBJMETHOD(ac97_write, nm_wrcd), KOBJMETHOD_END }; AC97_DECLARE(nm_ac97); /* -------------------------------------------------------------------- */ static void nm_ackint(struct sc_info *sc, u_int32_t num) { if (sc->type == NM256AV_PCI_ID) { nm_wr(sc, NM_INT_REG, num << 1, 2); } else if (sc->type == NM256ZX_PCI_ID) { nm_wr(sc, NM_INT_REG, num, 4); } } static int nm_loadcoeff(struct sc_info *sc, int dir, int num) { int ofs, sz, i; u_int32_t addr; addr = (dir == PCMDIR_PLAY)? 0x01c : 0x21c; if (dir == PCMDIR_REC) num += 8; sz = coefficientSizes[num]; ofs = 0; while (num-- > 0) ofs+= coefficientSizes[num]; for (i = 0; i < sz; i++) nm_wrbuf(sc, sc->cbuf + i, coefficients[ofs + i], 1); nm_wr(sc, addr, sc->cbuf, 4); if (dir == PCMDIR_PLAY) sz--; nm_wr(sc, addr + 4, sc->cbuf + sz, 4); return 0; } static int nm_setch(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; u_int32_t base; u_int8_t x; for (x = 0; x < 8; x++) if (ch->spd < (samplerates[x] + samplerates[x + 1]) / 2) break; if (x == 8) return 1; ch->spd = samplerates[x]; nm_loadcoeff(sc, ch->dir, x); x <<= 4; x &= NM_RATE_MASK; if (ch->fmt & AFMT_16BIT) x |= NM_RATE_BITS_16; if (AFMT_CHANNEL(ch->fmt) > 1) x |= NM_RATE_STEREO; base = (ch->dir == PCMDIR_PLAY)? NM_PLAYBACK_REG_OFFSET : NM_RECORD_REG_OFFSET; nm_wr(sc, base + NM_RATE_REG_OFFSET, x, 1); return 0; } /* channel interface */ static void * nmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; u_int32_t chnbuf; chnbuf = (dir == PCMDIR_PLAY)? sc->pbuf : sc->rbuf; ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; ch->active = 0; ch->blksize = 0; ch->wmark = 0; ch->buffer = b; sndbuf_setup(ch->buffer, (u_int8_t *)rman_get_virtual(sc->buf) + chnbuf, NM_BUFFSIZE); if (bootverbose) device_printf(sc->dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? - "play" : "rec", sndbuf_getbuf(ch->buffer)); + "play" : "rec", ch->buffer->buf); ch->parent = sc; ch->channel = c; ch->dir = dir; return ch; } static int nmchan_free(kobj_t obj, void *data) { return 0; } static int nmchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; ch->fmt = format; return nm_setch(ch); } static u_int32_t nmchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; ch->spd = speed; return nm_setch(ch)? 0 : ch->spd; } static u_int32_t nmchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; ch->blksize = blocksize; return blocksize; } static int nmchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; int ssz; if (!PCMTRIG_COMMON(go)) return 0; ssz = (ch->fmt & AFMT_16BIT)? 2 : 1; if (AFMT_CHANNEL(ch->fmt) > 1) ssz <<= 1; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { ch->active = 1; ch->wmark = ch->blksize; nm_wr(sc, NM_PBUFFER_START, sc->pbuf, 4); nm_wr(sc, NM_PBUFFER_END, sc->pbuf + NM_BUFFSIZE - ssz, 4); nm_wr(sc, NM_PBUFFER_CURRP, sc->pbuf, 4); nm_wr(sc, NM_PBUFFER_WMARK, sc->pbuf + ch->wmark, 4); nm_wr(sc, NM_PLAYBACK_ENABLE_REG, NM_PLAYBACK_FREERUN | NM_PLAYBACK_ENABLE_FLAG, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, 0, 2); } else { ch->active = 0; nm_wr(sc, NM_PLAYBACK_ENABLE_REG, 0, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, NM_AUDIO_MUTE_BOTH, 2); } } else { if (go == PCMTRIG_START) { ch->active = 1; ch->wmark = ch->blksize; nm_wr(sc, NM_RECORD_ENABLE_REG, NM_RECORD_FREERUN | NM_RECORD_ENABLE_FLAG, 1); nm_wr(sc, NM_RBUFFER_START, sc->rbuf, 4); nm_wr(sc, NM_RBUFFER_END, sc->rbuf + NM_BUFFSIZE, 4); nm_wr(sc, NM_RBUFFER_CURRP, sc->rbuf, 4); nm_wr(sc, NM_RBUFFER_WMARK, sc->rbuf + ch->wmark, 4); } else { ch->active = 0; nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } } return 0; } static u_int32_t nmchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; if (ch->dir == PCMDIR_PLAY) return nm_rd(sc, NM_PBUFFER_CURRP, 4) - sc->pbuf; else return nm_rd(sc, NM_RBUFFER_CURRP, 4) - sc->rbuf; } static struct pcmchan_caps * nmchan_getcaps(kobj_t obj, void *data) { return &nm_caps; } static kobj_method_t nmchan_methods[] = { KOBJMETHOD(channel_init, nmchan_init), KOBJMETHOD(channel_free, nmchan_free), KOBJMETHOD(channel_setformat, nmchan_setformat), KOBJMETHOD(channel_setspeed, nmchan_setspeed), KOBJMETHOD(channel_setblocksize, nmchan_setblocksize), KOBJMETHOD(channel_trigger, nmchan_trigger), KOBJMETHOD(channel_getptr, nmchan_getptr), KOBJMETHOD(channel_getcaps, nmchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(nmchan); /* The interrupt handler */ static void nm_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; int status, x; status = nm_rd(sc, NM_INT_REG, sc->irsz); if (status == 0) return; if (status & sc->playint) { status &= ~sc->playint; sc->pch.wmark += sc->pch.blksize; sc->pch.wmark %= NM_BUFFSIZE; nm_wr(sc, NM_PBUFFER_WMARK, sc->pbuf + sc->pch.wmark, 4); nm_ackint(sc, sc->playint); chn_intr(sc->pch.channel); } if (status & sc->recint) { status &= ~sc->recint; sc->rch.wmark += sc->rch.blksize; sc->rch.wmark %= NM_BUFFSIZE; nm_wr(sc, NM_RBUFFER_WMARK, sc->rbuf + sc->rch.wmark, 4); nm_ackint(sc, sc->recint); chn_intr(sc->rch.channel); } if (status & sc->misc1int) { status &= ~sc->misc1int; nm_ackint(sc, sc->misc1int); x = nm_rd(sc, 0x400, 1); nm_wr(sc, 0x400, x | 2, 1); device_printf(sc->dev, "misc int 1\n"); } if (status & sc->misc2int) { status &= ~sc->misc2int; nm_ackint(sc, sc->misc2int); x = nm_rd(sc, 0x400, 1); nm_wr(sc, 0x400, x & ~2, 1); device_printf(sc->dev, "misc int 2\n"); } if (status) { nm_ackint(sc, status); device_printf(sc->dev, "unknown int\n"); } } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int nm_init(struct sc_info *sc) { u_int32_t ofs, i; if (sc->type == NM256AV_PCI_ID) { sc->ac97_base = NM_MIXER_OFFSET; sc->ac97_status = NM_MIXER_STATUS_OFFSET; sc->ac97_busy = NM_MIXER_READY_MASK; sc->buftop = 2560 * 1024; sc->irsz = 2; sc->playint = NM_PLAYBACK_INT; sc->recint = NM_RECORD_INT; sc->misc1int = NM_MISC_INT_1; sc->misc2int = NM_MISC_INT_2; } else if (sc->type == NM256ZX_PCI_ID) { sc->ac97_base = NM_MIXER_OFFSET; sc->ac97_status = NM2_MIXER_STATUS_OFFSET; sc->ac97_busy = NM2_MIXER_READY_MASK; sc->buftop = (nm_rd(sc, 0xa0b, 2)? 6144 : 4096) * 1024; sc->irsz = 4; sc->playint = NM2_PLAYBACK_INT; sc->recint = NM2_RECORD_INT; sc->misc1int = NM2_MISC_INT_1; sc->misc2int = NM2_MISC_INT_2; } else return -1; sc->badintr = 0; ofs = sc->buftop - 0x0400; sc->buftop -= 0x1400; if (bootverbose) device_printf(sc->dev, "buftop is 0x%08x\n", sc->buftop); if ((nm_rdbuf(sc, ofs, 4) & NM_SIG_MASK) == NM_SIGNATURE) { i = nm_rdbuf(sc, ofs + 4, 4); if (i != 0 && i != 0xffffffff) { if (bootverbose) device_printf(sc->dev, "buftop is changed to 0x%08x\n", i); sc->buftop = i; } } sc->cbuf = sc->buftop - NM_MAX_COEFFICIENT; sc->rbuf = sc->cbuf - NM_BUFFSIZE; sc->pbuf = sc->rbuf - NM_BUFFSIZE; sc->acbuf = sc->pbuf - (NM_TOTAL_COEFF_COUNT * 4); nm_wr(sc, 0, 0x11, 1); nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); nm_wr(sc, 0x214, 0, 2); return 0; } static int nm_pci_probe(device_t dev) { struct sc_info *sc = NULL; char *s = NULL; u_int32_t subdev, i; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case NM256AV_PCI_ID: i = 0; while ((i < NUM_BADCARDS) && (badcards[i] != subdev)) i++; /* Try to catch other non-ac97 cards */ if (i == NUM_BADCARDS) { if (!(sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO))) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } sc->regid = PCIR_BAR(1); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to map register space\n"); free(sc, M_DEVBUF); return ENXIO; } /* * My Panasonic CF-M2EV needs resetting device * before checking mixer is present or not. * t.ichinoseki@nifty.com. */ nm_wr(sc, 0, 0x11, 1); /* reset device */ if ((nm_rd(sc, NM_MIXER_PRESENCE, 2) & NM_PRESENCE_MASK) != NM_PRESENCE_VALUE) { i = 0; /* non-ac97 card, but not listed */ DEB(device_printf(dev, "subdev = 0x%x - badcard?\n", subdev)); } bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); free(sc, M_DEVBUF); } if (i == NUM_BADCARDS) s = "NeoMagic 256AV"; DEB(else) DEB(device_printf(dev, "this is a non-ac97 NM256AV, not attaching\n")); break; case NM256ZX_PCI_ID: s = "NeoMagic 256ZX"; break; } if (s) device_set_desc(dev, s); return s? 0 : ENXIO; } static int nm_pci_attach(device_t dev) { struct sc_info *sc; struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); pci_enable_busmaster(dev); sc->bufid = PCIR_BAR(0); sc->buf = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->bufid, RF_ACTIVE); sc->regid = PCIR_BAR(1); sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regid, RF_ACTIVE); if (!sc->buf || !sc->reg) { device_printf(dev, "unable to map register space\n"); goto bad; } if (nm_init(sc) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } codec = AC97_CREATE(dev, sc, nm_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, nm_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } snprintf(status, SND_STATUSLEN, "mem 0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->buf), rman_get_start(sc->reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_REC, &nmchan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &nmchan_class, sc); if (pcm_register(dev, status)) goto bad; return 0; bad: if (codec) ac97_destroy(codec); if (sc->buf) bus_release_resource(dev, SYS_RES_MEMORY, sc->bufid, sc->buf); if (sc->reg) bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); free(sc, M_DEVBUF); return ENXIO; } static int nm_pci_detach(device_t dev) { int r; struct sc_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_MEMORY, sc->bufid, sc->buf); bus_release_resource(dev, SYS_RES_MEMORY, sc->regid, sc->reg); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); free(sc, M_DEVBUF); return 0; } static int nm_pci_suspend(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* stop playing */ if (sc->pch.active) { nm_wr(sc, NM_PLAYBACK_ENABLE_REG, 0, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, NM_AUDIO_MUTE_BOTH, 2); } /* stop recording */ if (sc->rch.active) { nm_wr(sc, NM_RECORD_ENABLE_REG, 0, 1); } return 0; } static int nm_pci_resume(device_t dev) { struct sc_info *sc; sc = pcm_getdevinfo(dev); /* * Reinit audio device. * Don't call nm_init(). It would change buftop if X ran or * is running. This makes playing and recording buffer address * shift but these buffers of channel layer are not changed. * As a result of this inconsistency, periodic noise will be * generated while playing. */ nm_wr(sc, 0, 0x11, 1); nm_wr(sc, 0x214, 0, 2); /* Reinit mixer */ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } /* restart playing */ if (sc->pch.active) { nm_wr(sc, NM_PLAYBACK_ENABLE_REG, NM_PLAYBACK_FREERUN | NM_PLAYBACK_ENABLE_FLAG, 1); nm_wr(sc, NM_AUDIO_MUTE_REG, 0, 2); } /* restart recording */ if (sc->rch.active) { nm_wr(sc, NM_RECORD_ENABLE_REG, NM_RECORD_FREERUN | NM_RECORD_ENABLE_FLAG, 1); } return 0; } static device_method_t nm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nm_pci_probe), DEVMETHOD(device_attach, nm_pci_attach), DEVMETHOD(device_detach, nm_pci_detach), DEVMETHOD(device_suspend, nm_pci_suspend), DEVMETHOD(device_resume, nm_pci_resume), { 0, 0 } }; static driver_t nm_driver = { "pcm", nm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_neomagic, pci, nm_driver, 0, 0); MODULE_DEPEND(snd_neomagic, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_neomagic, 1); diff --git a/sys/dev/sound/pci/solo.c b/sys/dev/sound/pci/solo.c index 90dd2e26ad41..8229b0cffdae 100644 --- a/sys/dev/sound/pci/solo.c +++ b/sys/dev/sound/pci/solo.c @@ -1,1075 +1,1076 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "mixer_if.h" #define SOLO_DEFAULT_BUFSZ 16384 #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ #define ESS18XX_DUPLEX 1 /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED static u_int32_t ess_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_playfmt, 0}; /* * Recording output is byte-swapped */ static u_int32_t ess_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_BE, 1, 0), SND_FORMAT(AFMT_S16_BE, 2, 0), SND_FORMAT(AFMT_U16_BE, 1, 0), SND_FORMAT(AFMT_U16_BE, 2, 0), 0 }; static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_recfmt, 0}; struct ess_info; struct ess_chinfo { struct ess_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; int dir, hwch, stopping; u_int32_t fmt, spd, blksz; }; struct ess_info { struct resource *io, *sb, *vc, *mpu, *gp; /* I/O address for the board */ struct resource *irq; void *ih; bus_dma_tag_t parent_dmat; int simplex_dir, type, dmasz[2]; unsigned int duplex:1, newspeed:1; unsigned int bufsz; struct ess_chinfo pch, rch; struct mtx *lock; }; #define ess_lock(_ess) snd_mtxlock((_ess)->lock) #define ess_unlock(_ess) snd_mtxunlock((_ess)->lock) #define ess_lock_assert(_ess) snd_mtxassert((_ess)->lock) static int ess_rd(struct ess_info *sc, int reg); static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); static int ess_dspready(struct ess_info *sc); static int ess_cmd(struct ess_info *sc, u_char val); static int ess_cmd1(struct ess_info *sc, u_char cmd, int val); static int ess_get_byte(struct ess_info *sc); static void ess_setmixer(struct ess_info *sc, u_int port, u_int value); static int ess_getmixer(struct ess_info *sc, u_int port); static int ess_reset_dsp(struct ess_info *sc); static int ess_write(struct ess_info *sc, u_char reg, int val); static int ess_read(struct ess_info *sc, u_char reg); static void ess_intr(void *arg); static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len); static int ess_start(struct ess_chinfo *ch); static int ess_stop(struct ess_chinfo *ch); static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir); static int ess_dmapos(struct ess_info *sc, int ch); static int ess_dmatrigger(struct ess_info *sc, int ch, int go); /* * Common code for the midi and pcm functions * * ess_cmd write a single byte to the CMD port. * ess_cmd1 write a CMD + 1 byte arg * ess_cmd2 write a CMD + 2 byte arg * ess_get_byte returns a single byte from the DSP data port * * ess_write is actually ess_cmd1 * ess_read access ext. regs via ess_cmd(0xc0, reg) followed by ess_get_byte */ static int port_rd(struct resource *port, int regno, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: return bus_space_read_1(st, sh, regno); case 2: return bus_space_read_2(st, sh, regno); case 4: return bus_space_read_4(st, sh, regno); default: return 0xffffffff; } } static void port_wr(struct resource *port, int regno, u_int32_t data, int size) { bus_space_tag_t st = rman_get_bustag(port); bus_space_handle_t sh = rman_get_bushandle(port); switch (size) { case 1: bus_space_write_1(st, sh, regno, data); break; case 2: bus_space_write_2(st, sh, regno, data); break; case 4: bus_space_write_4(st, sh, regno, data); break; } } static int ess_rd(struct ess_info *sc, int reg) { return port_rd(sc->sb, reg, 1); } static void ess_wr(struct ess_info *sc, int reg, u_int8_t val) { port_wr(sc->sb, reg, val, 1); } static int ess_dspready(struct ess_info *sc) { return ((ess_rd(sc, SBDSP_STATUS) & 0x80) == 0); } static int ess_dspwr(struct ess_info *sc, u_char val) { int i; for (i = 0; i < 1000; i++) { if (ess_dspready(sc)) { ess_wr(sc, SBDSP_CMD, val); return 1; } if (i > 10) DELAY((i > 100)? 1000 : 10); } printf("ess_dspwr(0x%02x) timed out.\n", val); return 0; } static int ess_cmd(struct ess_info *sc, u_char val) { DEB(printf("ess_cmd: %x\n", val)); return ess_dspwr(sc, val); } static int ess_cmd1(struct ess_info *sc, u_char cmd, int val) { DEB(printf("ess_cmd1: %x, %x\n", cmd, val)); if (ess_dspwr(sc, cmd)) { return ess_dspwr(sc, val & 0xff); } else return 0; } static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); return val; } static int ess_get_byte(struct ess_info *sc) { int i; for (i = 1000; i > 0; i--) { if (ess_rd(sc, 0xc) & 0x40) return ess_rd(sc, DSP_READ); else DELAY(20); } return -1; } static int ess_write(struct ess_info *sc, u_char reg, int val) { return ess_cmd1(sc, reg, val); } static int ess_read(struct ess_info *sc, u_char reg) { return (ess_cmd(sc, 0xc0) && ess_cmd(sc, reg))? ess_get_byte(sc) : -1; } static int ess_reset_dsp(struct ess_info *sc) { DEB(printf("ess_reset_dsp\n")); ess_wr(sc, SBDSP_RST, 3); DELAY(100); ess_wr(sc, SBDSP_RST, 0); if (ess_get_byte(sc) != 0xAA) { DEB(printf("ess_reset_dsp failed\n")); /* rman_get_start(d->io_base))); */ return ENXIO; /* Sorry */ } ess_cmd(sc, 0xc6); return 0; } static void ess_intr(void *arg) { struct ess_info *sc = (struct ess_info *)arg; int src, pirq = 0, rirq = 0; ess_lock(sc); src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; if (src == 0) { ess_unlock(sc); return; } if (sc->duplex) { pirq = (src & sc->pch.hwch)? 1 : 0; rirq = (src & sc->rch.hwch)? 1 : 0; } else { if (sc->simplex_dir == PCMDIR_PLAY) pirq = 1; if (sc->simplex_dir == PCMDIR_REC) rirq = 1; if (!pirq && !rirq) printf("solo: IRQ neither playback nor rec!\n"); } DEB(printf("ess_intr: pirq:%d rirq:%d\n",pirq,rirq)); if (pirq) { if (sc->pch.stopping) { ess_dmatrigger(sc, sc->pch.hwch, 0); sc->pch.stopping = 0; if (sc->pch.hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } ess_unlock(sc); chn_intr(sc->pch.channel); ess_lock(sc); } if (rirq) { if (sc->rch.stopping) { ess_dmatrigger(sc, sc->rch.hwch, 0); sc->rch.stopping = 0; /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } ess_unlock(sc); chn_intr(sc->rch.channel); ess_lock(sc); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); ess_unlock(sc); } /* utility functions for ESS */ static u_int8_t ess_calcspeed8(int *spd) { int speed = *spd; u_int32_t t; if (speed > 22000) { t = (795500 + speed / 2) / speed; speed = (795500 + t / 2) / t; t = (256 - t) | 0x80; } else { t = (397700 + speed / 2) / speed; speed = (397700 + t / 2) / t; t = 128 - t; } *spd = speed; return t & 0x000000ff; } static u_int8_t ess_calcspeed9(int *spd) { int speed, s0, s1, use0; u_int8_t t0, t1; /* rate = source / (256 - divisor) */ /* divisor = 256 - (source / rate) */ speed = *spd; t0 = 128 - (793800 / speed); s0 = 793800 / (128 - t0); t1 = 128 - (768000 / speed); s1 = 768000 / (128 - t1); t1 |= 0x80; use0 = (ABS(speed - s0) < ABS(speed - s1))? 1 : 0; *spd = use0? s0 : s1; return use0? t0 : t1; } static u_int8_t ess_calcfilter(int spd) { int cutoff; /* cutoff = 7160000 / (256 - divisor) */ /* divisor = 256 - (7160000 / cutoff) */ cutoff = (spd * 9 * 82) / 20; return (256 - (7160000 / cutoff)); } static int ess_setupch(struct ess_info *sc, int ch, int dir, int spd, u_int32_t fmt, int len) { int play = (dir == PCMDIR_PLAY)? 1 : 0; int b16 = (fmt & AFMT_16BIT)? 1 : 0; int stereo = (AFMT_CHANNEL(fmt) > 1)? 1 : 0; int unsign = (!(fmt & AFMT_SIGNED))? 1 : 0; u_int8_t spdval, fmtval; DEB(printf("ess_setupch\n")); spdval = (sc->newspeed)? ess_calcspeed9(&spd) : ess_calcspeed8(&spd); sc->simplex_dir = play ? PCMDIR_PLAY : PCMDIR_REC ; if (ch == 1) { KASSERT((dir == PCMDIR_PLAY) || (dir == PCMDIR_REC), ("ess_setupch: dir1 bad")); len = -len; /* transfer length low */ ess_write(sc, 0xa4, len & 0x00ff); /* transfer length high */ ess_write(sc, 0xa5, (len & 0xff00) >> 8); /* autoinit, dma dir */ ess_write(sc, 0xb8, 0x04 | (play? 0x00 : 0x0a)); /* mono/stereo */ ess_write(sc, 0xa8, (ess_read(sc, 0xa8) & ~0x03) | (stereo? 0x01 : 0x02)); /* demand mode, 4 bytes/xfer */ ess_write(sc, 0xb9, 0x02); /* sample rate */ ess_write(sc, 0xa1, spdval); /* filter cutoff */ ess_write(sc, 0xa2, ess_calcfilter(spd)); /* setup dac/adc */ /* if (play) ess_write(sc, 0xb6, unsign? 0x80 : 0x00); */ /* mono, b16: signed, load signal */ /* ess_write(sc, 0xb7, 0x51 | (unsign? 0x00 : 0x20)); */ /* setup fifo */ ess_write(sc, 0xb7, 0x91 | (unsign? 0x00 : 0x20) | (b16? 0x04 : 0x00) | (stereo? 0x08 : 0x40)); /* irq control */ ess_write(sc, 0xb1, (ess_read(sc, 0xb1) & 0x0f) | 0x50); /* drq control */ ess_write(sc, 0xb2, (ess_read(sc, 0xb2) & 0x0f) | 0x50); } else if (ch == 2) { KASSERT(dir == PCMDIR_PLAY, ("ess_setupch: dir2 bad")); len >>= 1; len = -len; /* transfer length low */ ess_setmixer(sc, 0x74, len & 0x00ff); /* transfer length high */ ess_setmixer(sc, 0x76, (len & 0xff00) >> 8); /* autoinit, 4 bytes/req */ ess_setmixer(sc, 0x78, 0x10); fmtval = b16 | (stereo << 1) | ((!unsign) << 2); /* enable irq, set format */ ess_setmixer(sc, 0x7a, 0x40 | fmtval); if (sc->newspeed) { /* sample rate */ ess_setmixer(sc, 0x70, spdval); /* filter cutoff */ ess_setmixer(sc, 0x72, ess_calcfilter(spd)); } } return 0; } static int ess_start(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_start\n");); ess_setupch(sc, ch->hwch, ch->dir, ch->spd, ch->fmt, ch->blksz); ch->stopping = 0; if (ch->hwch == 1) { ess_write(sc, 0xb8, ess_read(sc, 0xb8) | 0x01); if (ch->dir == PCMDIR_PLAY) { #if 0 DELAY(100000); /* 100 ms */ #endif ess_cmd(sc, 0xd1); } } else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) | 0x03); return 0; } static int ess_stop(struct ess_chinfo *ch) { struct ess_info *sc = ch->parent; DEB(printf("ess_stop\n")); ch->stopping = 1; if (ch->hwch == 1) ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x04); else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x10); DEB(printf("done with stop\n")); return 0; } /* -------------------------------------------------------------------- */ /* channel interface for ESS18xx */ static void * esschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct ess_info *sc = devinfo; struct ess_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; DEB(printf("esschan_init\n")); ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) ch->hwch = 2; return ch; } static int esschan_setformat(kobj_t obj, void *data, u_int32_t format) { struct ess_chinfo *ch = data; ch->fmt = format; return 0; } static u_int32_t esschan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; ch->spd = speed; if (sc->newspeed) ess_calcspeed9(&ch->spd); else ess_calcspeed8(&ch->spd); return ch->spd; } static u_int32_t esschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct ess_chinfo *ch = data; ch->blksz = blocksize; return ch->blksz; } static int esschan_trigger(kobj_t obj, void *data, int go) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return 0; DEB(printf("esschan_trigger: %d\n",go)); ess_lock(sc); switch (go) { case PCMTRIG_START: - ess_dmasetup(sc, ch->hwch, sndbuf_getbufaddr(ch->buffer), sndbuf_getsize(ch->buffer), ch->dir); + ess_dmasetup(sc, ch->hwch, ch->buffer->buf_addr, + ch->buffer->bufsize, ch->dir); ess_dmatrigger(sc, ch->hwch, 1); ess_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: default: ess_stop(ch); break; } ess_unlock(sc); return 0; } static u_int32_t esschan_getptr(kobj_t obj, void *data) { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; u_int32_t ret; ess_lock(sc); ret = ess_dmapos(sc, ch->hwch); ess_unlock(sc); return ret; } static struct pcmchan_caps * esschan_getcaps(kobj_t obj, void *data) { struct ess_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY)? &ess_playcaps : &ess_reccaps; } static kobj_method_t esschan_methods[] = { KOBJMETHOD(channel_init, esschan_init), KOBJMETHOD(channel_setformat, esschan_setformat), KOBJMETHOD(channel_setspeed, esschan_setspeed), KOBJMETHOD(channel_setblocksize, esschan_setblocksize), KOBJMETHOD(channel_trigger, esschan_trigger), KOBJMETHOD(channel_getptr, esschan_getptr), KOBJMETHOD(channel_getcaps, esschan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(esschan); /************************************************************/ static int essmix_init(struct snd_mixer *m) { struct ess_info *sc = mix_getdevinfo(m); mix_setrecdevs(m, SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_IMIX); mix_setdevs(m, SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_VOLUME | SOUND_MASK_LINE1); ess_setmixer(sc, 0, 0); /* reset */ return 0; } static int essmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ess_info *sc = mix_getdevinfo(m); int preg = 0, rreg = 0, l, r; l = (left * 15) / 100; r = (right * 15) / 100; switch (dev) { case SOUND_MIXER_SYNTH: preg = 0x36; rreg = 0x6b; break; case SOUND_MIXER_PCM: preg = 0x14; rreg = 0x7c; break; case SOUND_MIXER_LINE: preg = 0x3e; rreg = 0x6e; break; case SOUND_MIXER_MIC: preg = 0x1a; rreg = 0x68; break; case SOUND_MIXER_LINE1: preg = 0x3a; rreg = 0x6c; break; case SOUND_MIXER_CD: preg = 0x38; rreg = 0x6a; break; case SOUND_MIXER_VOLUME: l = left? (left * 63) / 100 : 64; r = right? (right * 63) / 100 : 64; ess_setmixer(sc, 0x60, l); ess_setmixer(sc, 0x62, r); left = (l == 64)? 0 : (l * 100) / 63; right = (r == 64)? 0 : (r * 100) / 63; return left | (right << 8); } if (preg) ess_setmixer(sc, preg, (l << 4) | r); if (rreg) ess_setmixer(sc, rreg, (l << 4) | r); left = (l * 100) / 15; right = (r * 100) / 15; return left | (right << 8); } static u_int32_t essmix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct ess_info *sc = mix_getdevinfo(m); u_char recdev; switch (src) { case SOUND_MASK_CD: recdev = 0x02; break; case SOUND_MASK_LINE: recdev = 0x06; break; case SOUND_MASK_IMIX: recdev = 0x05; break; case SOUND_MASK_MIC: default: recdev = 0x00; src = SOUND_MASK_MIC; break; } ess_setmixer(sc, 0x1c, recdev); return src; } static kobj_method_t solomixer_methods[] = { KOBJMETHOD(mixer_init, essmix_init), KOBJMETHOD(mixer_set, essmix_set), KOBJMETHOD(mixer_setrecsrc, essmix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(solomixer); /************************************************************/ static int ess_dmasetup(struct ess_info *sc, int ch, u_int32_t base, u_int16_t cnt, int dir) { KASSERT(ch == 1 || ch == 2, ("bad ch")); sc->dmasz[ch - 1] = cnt; if (ch == 1) { port_wr(sc->vc, 0x8, 0xc4, 1); /* command */ port_wr(sc->vc, 0xd, 0xff, 1); /* reset */ port_wr(sc->vc, 0xf, 0x01, 1); /* mask */ port_wr(sc->vc, 0xb, dir == PCMDIR_PLAY? 0x58 : 0x54, 1); /* mode */ port_wr(sc->vc, 0x0, base, 4); port_wr(sc->vc, 0x4, cnt - 1, 2); } else if (ch == 2) { port_wr(sc->io, 0x6, 0x08, 1); /* autoinit */ port_wr(sc->io, 0x0, base, 4); port_wr(sc->io, 0x4, cnt, 2); } return 0; } static int ess_dmapos(struct ess_info *sc, int ch) { int p = 0, i = 0, j = 0; KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) { /* * During recording, this register is known to give back * garbage if it's not quiescent while being read. That's * why we spl, stop the DMA, and try over and over until * adjacent reads are "close", in the right order and not * bigger than is otherwise possible. */ ess_dmatrigger(sc, ch, 0); DELAY(20); do { DELAY(10); if (j > 1) printf("DMA count reg bogus: %04x & %04x\n", i, p); i = port_rd(sc->vc, 0x4, 2) + 1; p = port_rd(sc->vc, 0x4, 2) + 1; } while ((p > sc->dmasz[ch - 1] || i < p || (p - i) > 0x8) && j++ < 1000); ess_dmatrigger(sc, ch, 1); } else if (ch == 2) p = port_rd(sc->io, 0x4, 2); return sc->dmasz[ch - 1] - p; } static int ess_dmatrigger(struct ess_info *sc, int ch, int go) { KASSERT(ch == 1 || ch == 2, ("bad ch")); if (ch == 1) port_wr(sc->vc, 0xf, go? 0x00 : 0x01, 1); /* mask */ else if (ch == 2) port_wr(sc->io, 0x6, 0x08 | (go? 0x02 : 0x00), 1); /* autoinit */ return 0; } static void ess_release_resources(struct ess_info *sc, device_t dev) { if (sc->irq) { if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); sc->irq = NULL; } if (sc->io) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->io); sc->io = NULL; } if (sc->sb) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(1), sc->sb); sc->sb = NULL; } if (sc->vc) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(2), sc->vc); sc->vc = NULL; } if (sc->mpu) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(3), sc->mpu); sc->mpu = NULL; } if (sc->gp) { bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(4), sc->gp); sc->gp = NULL; } if (sc->parent_dmat) { bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } if (sc->lock) { snd_mtxfree(sc->lock); sc->lock = NULL; } free(sc, M_DEVBUF); } static int ess_alloc_resources(struct ess_info *sc, device_t dev) { int rid; rid = PCIR_BAR(0); sc->io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(1); sc->sb = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(2); sc->vc = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(3); sc->mpu = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = PCIR_BAR(4); sc->gp = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); rid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_solo softc"); return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; } static int ess_probe(device_t dev) { char *s = NULL; u_int32_t subdev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); switch (pci_get_devid(dev)) { case 0x1969125d: if (subdev == 0x8888125d) s = "ESS Solo-1E"; else if (subdev == 0x1818125d) s = "ESS Solo-1"; else s = "ESS Solo-1 (unknown vendor)"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } #define ESS_PCI_LEGACYCONTROL 0x40 #define ESS_PCI_CONFIG 0x50 #define ESS_PCI_DDMACONTROL 0x60 static int ess_suspend(device_t dev) { return 0; } static int ess_resume(device_t dev) { uint16_t ddma; struct ess_info *sc = pcm_getdevinfo(dev); ess_lock(sc); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); if (ess_reset_dsp(sc)) { ess_unlock(sc); goto no; } ess_unlock(sc); if (mixer_reinit(dev)) goto no; ess_lock(sc); if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ ess_unlock(sc); return 0; no: return EIO; } static int ess_attach(device_t dev) { struct ess_info *sc; char status[SND_STATUSLEN]; u_int16_t ddma; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); pci_enable_busmaster(dev); if (ess_alloc_resources(sc, dev)) goto no; sc->bufsz = pcm_getbuffersize(dev, 4096, SOLO_DEFAULT_BUFSZ, 65536); ddma = rman_get_start(sc->vc) | 1; pci_write_config(dev, ESS_PCI_LEGACYCONTROL, 0x805f, 2); pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ #ifdef ESS18XX_DUPLEX sc->duplex = 1; #else sc->duplex = 0; #endif #ifdef ESS18XX_NEWSPEED sc->newspeed = 1; #else sc->newspeed = 0; #endif if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ess_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto no; } if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); #if 0 if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/65536, /*boundary*/0, #endif if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } if (ess_reset_dsp(sc)) goto no; if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); if (mixer_init(dev, &solomixer_class, sc)) goto no; snprintf(status, SND_STATUSLEN, "port 0x%jx,0x%jx,0x%jx irq %jd on %s", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_REC, &esschan_class, sc); pcm_addchan(dev, PCMDIR_PLAY, &esschan_class, sc); if (pcm_register(dev, status)) goto no; return 0; no: ess_release_resources(sc, dev); return ENXIO; } static int ess_detach(device_t dev) { int r; struct ess_info *sc; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); ess_release_resources(sc, dev); return 0; } static device_method_t ess_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ess_probe), DEVMETHOD(device_attach, ess_attach), DEVMETHOD(device_detach, ess_detach), DEVMETHOD(device_resume, ess_resume), DEVMETHOD(device_suspend, ess_suspend), { 0, 0 } }; static driver_t ess_driver = { "pcm", ess_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_solo, pci, ess_driver, 0, 0); MODULE_DEPEND(snd_solo, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_solo, 1); diff --git a/sys/dev/sound/pci/t4dwave.c b/sys/dev/sound/pci/t4dwave.c index 07b9e1004573..874d1c7a2e2a 100644 --- a/sys/dev/sound/pci/t4dwave.c +++ b/sys/dev/sound/pci/t4dwave.c @@ -1,1037 +1,1037 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include /* -------------------------------------------------------------------- */ #define TDX_PCI_ID 0x20001023 #define TNX_PCI_ID 0x20011023 #define ALI_PCI_ID 0x545110b9 #define SPA_PCI_ID 0x70181039 #define TR_DEFAULT_BUFSZ 0x1000 /* For ALi M5451 the DMA transfer size appears to be fixed to 64k. */ #define ALI_BUFSZ 0x10000 #define TR_BUFALGN 0x8 #define TR_TIMEOUT_CDC 0xffff #define TR_MAXHWCH 64 #define ALI_MAXHWCH 32 #define TR_MAXPLAYCH 4 #define ALI_MAXPLAYCH 1 /* * Though, it's not clearly documented in the 4DWAVE datasheet, the * DX and NX chips can't handle DMA addresses located above 1GB as the * LBA (loop begin address) register which holds the DMA base address * is 32-bit, but the two MSBs are used for other purposes. */ #define TR_MAXADDR ((1U << 30) - 1) #define ALI_MAXADDR ((1U << 31) - 1) struct tr_info; /* channel registers */ struct tr_chinfo { u_int32_t cso, alpha, fms, fmc, ec; u_int32_t lba; u_int32_t eso, delta; u_int32_t rvol, cvol; u_int32_t gvsel, pan, vol, ctrl; u_int32_t active:1, was_active:1; int index, bufhalf; struct snd_dbuf *buffer; struct pcm_channel *channel; struct tr_info *parent; }; struct tr_rchinfo { u_int32_t delta; u_int32_t active:1, was_active:1; struct snd_dbuf *buffer; struct pcm_channel *channel; struct tr_info *parent; }; /* device private data */ struct tr_info { u_int32_t type; u_int32_t rev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; struct resource *reg, *irq; int regtype, regid, irqid; void *ih; struct mtx *lock; u_int32_t hwchns; u_int32_t playchns; unsigned int bufsz; struct tr_chinfo chinfo[TR_MAXPLAYCH]; struct tr_rchinfo recchinfo; }; /* -------------------------------------------------------------------- */ static u_int32_t tr_recfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps tr_reccaps = {4000, 48000, tr_recfmt, 0}; static u_int32_t tr_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps tr_playcaps = {4000, 48000, tr_playfmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ static u_int32_t tr_rd(struct tr_info *tr, int regno, int size) { switch(size) { case 1: return bus_space_read_1(tr->st, tr->sh, regno); case 2: return bus_space_read_2(tr->st, tr->sh, regno); case 4: return bus_space_read_4(tr->st, tr->sh, regno); default: return 0xffffffff; } } static void tr_wr(struct tr_info *tr, int regno, u_int32_t data, int size) { switch(size) { case 1: bus_space_write_1(tr->st, tr->sh, regno, data); break; case 2: bus_space_write_2(tr->st, tr->sh, regno, data); break; case 4: bus_space_write_4(tr->st, tr->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int tr_rdcd(kobj_t obj, void *devinfo, int regno) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { case SPA_PCI_ID: treg=SPA_REG_CODECRD; trw=SPA_CDC_RWSTAT; break; case ALI_PCI_ID: if (tr->rev > 0x01) treg=TDX_REG_CODECWR; else treg=TDX_REG_CODECRD; trw=TDX_CDC_RWSTAT; break; case TDX_PCI_ID: treg=TDX_REG_CODECRD; trw=TDX_CDC_RWSTAT; break; case TNX_PCI_ID: treg=(regno & 0x100)? TNX_REG_CODEC2RD : TNX_REG_CODEC1RD; trw=TNX_CDC_RWSTAT; break; default: printf("!!! tr_rdcd defaulted !!!\n"); return -1; } i = j = 0; regno &= 0x7f; snd_mtxlock(tr->lock); if (tr->type == ALI_PCI_ID) { u_int32_t chk1, chk2; j = trw; for (i = TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j = tr_rd(tr, treg, 4); if (i > 0) { chk1 = tr_rd(tr, 0xc8, 4); chk2 = tr_rd(tr, 0xc8, 4); for (i = TR_TIMEOUT_CDC; (i > 0) && (chk1 == chk2); i--) chk2 = tr_rd(tr, 0xc8, 4); } } if (tr->type != ALI_PCI_ID || i > 0) { tr_wr(tr, treg, regno | trw, 4); j=trw; for (i=TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j=tr_rd(tr, treg, 4); } snd_mtxunlock(tr->lock); if (i == 0) printf("codec timeout during read of register %x\n", regno); return (j >> TR_CDC_DATA) & 0xffff; } static int tr_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) { struct tr_info *tr = (struct tr_info *)devinfo; int i, j, treg, trw; switch (tr->type) { case SPA_PCI_ID: treg=SPA_REG_CODECWR; trw=SPA_CDC_RWSTAT; break; case ALI_PCI_ID: case TDX_PCI_ID: treg=TDX_REG_CODECWR; trw=TDX_CDC_RWSTAT; break; case TNX_PCI_ID: treg=TNX_REG_CODECWR; trw=TNX_CDC_RWSTAT | ((regno & 0x100)? TNX_CDC_SEC : 0); break; default: printf("!!! tr_wrcd defaulted !!!"); return -1; } i = 0; regno &= 0x7f; #if 0 printf("tr_wrcd: reg %x was %x", regno, tr_rdcd(devinfo, regno)); #endif j=trw; snd_mtxlock(tr->lock); if (tr->type == ALI_PCI_ID) { j = trw; for (i = TR_TIMEOUT_CDC; (i > 0) && (j & trw); i--) j = tr_rd(tr, treg, 4); if (i > 0) { u_int32_t chk1, chk2; chk1 = tr_rd(tr, 0xc8, 4); chk2 = tr_rd(tr, 0xc8, 4); for (i = TR_TIMEOUT_CDC; (i > 0) && (chk1 == chk2); i--) chk2 = tr_rd(tr, 0xc8, 4); } } if (tr->type != ALI_PCI_ID || i > 0) { for (i=TR_TIMEOUT_CDC; (i>0) && (j & trw); i--) j=tr_rd(tr, treg, 4); if (tr->type == ALI_PCI_ID && tr->rev > 0x01) trw |= 0x0100; tr_wr(tr, treg, (data << TR_CDC_DATA) | regno | trw, 4); } #if 0 printf(" - wrote %x, now %x\n", data, tr_rdcd(devinfo, regno)); #endif snd_mtxunlock(tr->lock); if (i==0) printf("codec timeout writing %x, data %x\n", regno, data); return (i > 0)? 0 : -1; } static kobj_method_t tr_ac97_methods[] = { KOBJMETHOD(ac97_read, tr_rdcd), KOBJMETHOD(ac97_write, tr_wrcd), KOBJMETHOD_END }; AC97_DECLARE(tr_ac97); /* -------------------------------------------------------------------- */ /* playback channel interrupts */ #if 0 static u_int32_t tr_testint(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; return tr_rd(tr, bank? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 4) & (1 << chan); } #endif static void tr_clrint(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_ADDRINTB : TR_REG_ADDRINTA, 1 << chan, 4); } static void tr_enaint(struct tr_chinfo *ch, int enable) { struct tr_info *tr = ch->parent; u_int32_t i, reg; int bank, chan; snd_mtxlock(tr->lock); bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; reg = bank? TR_REG_INTENB : TR_REG_INTENA; i = tr_rd(tr, reg, 4); i &= ~(1 << chan); i |= (enable? 1 : 0) << chan; tr_clrint(ch); tr_wr(tr, reg, i, 4); snd_mtxunlock(tr->lock); } /* playback channels */ static void tr_selch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int i; i = tr_rd(tr, TR_REG_CIR, 4); i &= ~TR_CIR_MASK; i |= ch->index & 0x3f; tr_wr(tr, TR_REG_CIR, i, 4); } static void tr_startch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_STARTB : TR_REG_STARTA, 1 << chan, 4); } static void tr_stopch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; int bank, chan; bank = (ch->index & 0x20) ? 1 : 0; chan = ch->index & 0x1f; tr_wr(tr, bank? TR_REG_STOPB : TR_REG_STOPA, 1 << chan, 4); } static void tr_wrch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; u_int32_t cr[TR_CHN_REGS], i; ch->gvsel &= 0x00000001; ch->fmc &= 0x00000003; ch->fms &= 0x0000000f; ch->ctrl &= 0x0000000f; ch->pan &= 0x0000007f; ch->rvol &= 0x0000007f; ch->cvol &= 0x0000007f; ch->vol &= 0x000000ff; ch->ec &= 0x00000fff; ch->alpha &= 0x00000fff; ch->delta &= 0x0000ffff; if (tr->type == ALI_PCI_ID) ch->lba &= ALI_MAXADDR; else ch->lba &= TR_MAXADDR; cr[1]=ch->lba; cr[3]=(ch->fmc<<14) | (ch->rvol<<7) | (ch->cvol); cr[4]=(ch->gvsel<<31) | (ch->pan<<24) | (ch->vol<<16) | (ch->ctrl<<12) | (ch->ec); switch (tr->type) { case SPA_PCI_ID: case ALI_PCI_ID: case TDX_PCI_ID: ch->cso &= 0x0000ffff; ch->eso &= 0x0000ffff; cr[0]=(ch->cso<<16) | (ch->alpha<<4) | (ch->fms); cr[2]=(ch->eso<<16) | (ch->delta); break; case TNX_PCI_ID: ch->cso &= 0x00ffffff; ch->eso &= 0x00ffffff; cr[0]=((ch->delta & 0xff)<<24) | (ch->cso); cr[2]=((ch->delta>>8)<<24) | (ch->eso); cr[3]|=(ch->alpha<<20) | (ch->fms<<16) | (ch->fmc<<14); break; } snd_mtxlock(tr->lock); tr_selch(ch); for (i=0; ilock); } static void tr_rdch(struct tr_chinfo *ch) { struct tr_info *tr = ch->parent; u_int32_t cr[5], i; snd_mtxlock(tr->lock); tr_selch(ch); for (i=0; i<5; i++) cr[i]=tr_rd(tr, TR_REG_CHNBASE+(i<<2), 4); snd_mtxunlock(tr->lock); if (tr->type == ALI_PCI_ID) ch->lba=(cr[1] & ALI_MAXADDR); else ch->lba=(cr[1] & TR_MAXADDR); ch->fmc= (cr[3] & 0x0000c000) >> 14; ch->rvol= (cr[3] & 0x00003f80) >> 7; ch->cvol= (cr[3] & 0x0000007f); ch->gvsel= (cr[4] & 0x80000000) >> 31; ch->pan= (cr[4] & 0x7f000000) >> 24; ch->vol= (cr[4] & 0x00ff0000) >> 16; ch->ctrl= (cr[4] & 0x0000f000) >> 12; ch->ec= (cr[4] & 0x00000fff); switch(tr->type) { case SPA_PCI_ID: case ALI_PCI_ID: case TDX_PCI_ID: ch->cso= (cr[0] & 0xffff0000) >> 16; ch->alpha= (cr[0] & 0x0000fff0) >> 4; ch->fms= (cr[0] & 0x0000000f); ch->eso= (cr[2] & 0xffff0000) >> 16; ch->delta= (cr[2] & 0x0000ffff); break; case TNX_PCI_ID: ch->cso= (cr[0] & 0x00ffffff); ch->eso= (cr[2] & 0x00ffffff); ch->delta= ((cr[2] & 0xff000000) >> 16) | ((cr[0] & 0xff000000) >> 24); ch->alpha= (cr[3] & 0xfff00000) >> 20; ch->fms= (cr[3] & 0x000f0000) >> 16; break; } } static u_int32_t tr_fmttobits(u_int32_t fmt) { u_int32_t bits; bits = 0; bits |= (fmt & AFMT_SIGNED)? 0x2 : 0; bits |= (AFMT_CHANNEL(fmt) > 1)? 0x4 : 0; bits |= (fmt & AFMT_16BIT)? 0x8 : 0; return bits; } /* -------------------------------------------------------------------- */ /* channel interface */ static void * trpchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_chinfo *ch; KASSERT(dir == PCMDIR_PLAY, ("trpchan_init: bad direction")); ch = &tr->chinfo[tr->playchns]; ch->index = tr->playchns++; ch->buffer = b; ch->parent = tr; ch->channel = c; if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; } static int trpchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct tr_chinfo *ch = data; ch->ctrl = tr_fmttobits(format) | 0x01; return 0; } static u_int32_t trpchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct tr_chinfo *ch = data; ch->delta = (speed << 12) / 48000; return (ch->delta * 48000) >> 12; } static u_int32_t trpchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct tr_chinfo *ch = data; sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static int trpchan_trigger(kobj_t obj, void *data, int go) { struct tr_chinfo *ch = data; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { ch->fmc = 3; ch->fms = 0; ch->ec = 0; ch->alpha = 0; - ch->lba = sndbuf_getbufaddr(ch->buffer); + ch->lba = ch->buffer->buf_addr; ch->cso = 0; - ch->eso = (sndbuf_getsize(ch->buffer) / sndbuf_getalign(ch->buffer)) - 1; + ch->eso = (ch->buffer->bufsize / ch->buffer->align) - 1; ch->rvol = ch->cvol = 0x7f; ch->gvsel = 0; ch->pan = 0; ch->vol = 0; ch->bufhalf = 0; tr_wrch(ch); tr_enaint(ch, 1); tr_startch(ch); ch->active = 1; } else { tr_stopch(ch); ch->active = 0; } return 0; } static u_int32_t trpchan_getptr(kobj_t obj, void *data) { struct tr_chinfo *ch = data; tr_rdch(ch); - return ch->cso * sndbuf_getalign(ch->buffer); + return ch->cso * ch->buffer->align; } static struct pcmchan_caps * trpchan_getcaps(kobj_t obj, void *data) { return &tr_playcaps; } static kobj_method_t trpchan_methods[] = { KOBJMETHOD(channel_init, trpchan_init), KOBJMETHOD(channel_setformat, trpchan_setformat), KOBJMETHOD(channel_setspeed, trpchan_setspeed), KOBJMETHOD(channel_setblocksize, trpchan_setblocksize), KOBJMETHOD(channel_trigger, trpchan_trigger), KOBJMETHOD(channel_getptr, trpchan_getptr), KOBJMETHOD(channel_getcaps, trpchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(trpchan); /* -------------------------------------------------------------------- */ /* rec channel interface */ static void * trrchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct tr_info *tr = devinfo; struct tr_rchinfo *ch; KASSERT(dir == PCMDIR_REC, ("trrchan_init: bad direction")); ch = &tr->recchinfo; ch->buffer = b; ch->parent = tr; ch->channel = c; if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; } static int trrchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t i, bits; bits = tr_fmttobits(format); /* set # of samples between interrupts */ i = (sndbuf_runsz(ch->buffer) >> ((bits & 0x08)? 1 : 0)) - 1; tr_wr(tr, TR_REG_SBBL, i | (i << 16), 4); /* set sample format */ i = 0x18 | (bits << 4); tr_wr(tr, TR_REG_SBCTRL, i, 1); return 0; } static u_int32_t trrchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; /* setup speed */ ch->delta = (48000 << 12) / speed; tr_wr(tr, TR_REG_SBDELTA, ch->delta, 2); /* return closest possible speed */ return (48000 << 12) / ch->delta; } static u_int32_t trrchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct tr_rchinfo *ch = data; sndbuf_resize(ch->buffer, 2, blocksize); return blocksize; } static int trrchan_trigger(kobj_t obj, void *data, int go) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; u_int32_t i; if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { /* set up dma mode regs */ tr_wr(tr, TR_REG_DMAR15, 0, 1); i = tr_rd(tr, TR_REG_DMAR11, 1) & 0x03; tr_wr(tr, TR_REG_DMAR11, i | 0x54, 1); /* set up base address */ - tr_wr(tr, TR_REG_DMAR0, sndbuf_getbufaddr(ch->buffer), 4); + tr_wr(tr, TR_REG_DMAR0, ch->buffer->buf_addr, 4); /* set up buffer size */ i = tr_rd(tr, TR_REG_DMAR4, 4) & ~0x00ffffff; tr_wr(tr, TR_REG_DMAR4, i | (sndbuf_runsz(ch->buffer) - 1), 4); /* start */ tr_wr(tr, TR_REG_SBCTRL, tr_rd(tr, TR_REG_SBCTRL, 1) | 1, 1); ch->active = 1; } else { tr_wr(tr, TR_REG_SBCTRL, tr_rd(tr, TR_REG_SBCTRL, 1) & ~7, 1); ch->active = 0; } /* return 0 if ok */ return 0; } static u_int32_t trrchan_getptr(kobj_t obj, void *data) { struct tr_rchinfo *ch = data; struct tr_info *tr = ch->parent; /* return current byte offset of channel */ - return tr_rd(tr, TR_REG_DMAR0, 4) - sndbuf_getbufaddr(ch->buffer); + return tr_rd(tr, TR_REG_DMAR0, 4) - ch->buffer->buf_addr; } static struct pcmchan_caps * trrchan_getcaps(kobj_t obj, void *data) { return &tr_reccaps; } static kobj_method_t trrchan_methods[] = { KOBJMETHOD(channel_init, trrchan_init), KOBJMETHOD(channel_setformat, trrchan_setformat), KOBJMETHOD(channel_setspeed, trrchan_setspeed), KOBJMETHOD(channel_setblocksize, trrchan_setblocksize), KOBJMETHOD(channel_trigger, trrchan_trigger), KOBJMETHOD(channel_getptr, trrchan_getptr), KOBJMETHOD(channel_getcaps, trrchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(trrchan); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void tr_intr(void *p) { struct tr_info *tr = (struct tr_info *)p; struct tr_chinfo *ch; u_int32_t active, mask, bufhalf, chnum, intsrc; int tmp; intsrc = tr_rd(tr, TR_REG_MISCINT, 4); if (intsrc & TR_INT_ADDR) { chnum = 0; while (chnum < tr->hwchns) { mask = 0x00000001; active = tr_rd(tr, (chnum < 32)? TR_REG_ADDRINTA : TR_REG_ADDRINTB, 4); bufhalf = tr_rd(tr, (chnum < 32)? TR_REG_CSPF_A : TR_REG_CSPF_B, 4); if (active) { do { if (active & mask) { tmp = (bufhalf & mask)? 1 : 0; if (chnum < tr->playchns) { ch = &tr->chinfo[chnum]; /* printf("%d @ %d, ", chnum, trpchan_getptr(NULL, ch)); */ if (ch->bufhalf != tmp) { chn_intr(ch->channel); ch->bufhalf = tmp; } } } chnum++; mask <<= 1; } while (chnum & 31); } else chnum += 32; tr_wr(tr, (chnum <= 32)? TR_REG_ADDRINTA : TR_REG_ADDRINTB, active, 4); } } if (intsrc & TR_INT_SB) { chn_intr(tr->recchinfo.channel); tr_rd(tr, TR_REG_SBR9, 1); tr_rd(tr, TR_REG_SBR10, 1); } } /* -------------------------------------------------------------------- */ /* * Probe and attach the card */ static int tr_init(struct tr_info *tr) { switch (tr->type) { case SPA_PCI_ID: tr_wr(tr, SPA_REG_GPIO, 0, 4); tr_wr(tr, SPA_REG_CODECST, SPA_RST_OFF, 4); break; case TDX_PCI_ID: tr_wr(tr, TDX_REG_CODECST, TDX_CDC_ON, 4); break; case TNX_PCI_ID: tr_wr(tr, TNX_REG_CODECST, TNX_CDC_ON, 4); break; } tr_wr(tr, TR_REG_CIR, TR_CIR_MIDENA | TR_CIR_ADDRENA, 4); return 0; } static int tr_pci_probe(device_t dev) { switch (pci_get_devid(dev)) { case SPA_PCI_ID: device_set_desc(dev, "SiS 7018"); return BUS_PROBE_DEFAULT; case ALI_PCI_ID: device_set_desc(dev, "Acer Labs M5451"); return BUS_PROBE_DEFAULT; case TDX_PCI_ID: device_set_desc(dev, "Trident 4DWave DX"); return BUS_PROBE_DEFAULT; case TNX_PCI_ID: device_set_desc(dev, "Trident 4DWave NX"); return BUS_PROBE_DEFAULT; } return ENXIO; } static int tr_pci_attach(device_t dev) { struct tr_info *tr; struct ac97_info *codec = NULL; bus_addr_t lowaddr; int i, dacn; char status[SND_STATUSLEN]; tr = malloc(sizeof(*tr), M_DEVBUF, M_WAITOK | M_ZERO); tr->type = pci_get_devid(dev); tr->rev = pci_get_revid(dev); tr->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_t4dwave softc"); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "dac", &i) == 0) { if (i < 1) dacn = 1; else if (i > TR_MAXPLAYCH) dacn = TR_MAXPLAYCH; else dacn = i; } else { switch (tr->type) { case ALI_PCI_ID: dacn = ALI_MAXPLAYCH; break; default: dacn = TR_MAXPLAYCH; break; } } pci_enable_busmaster(dev); tr->regid = PCIR_BAR(0); tr->regtype = SYS_RES_IOPORT; tr->reg = bus_alloc_resource_any(dev, tr->regtype, &tr->regid, RF_ACTIVE); if (tr->reg) { tr->st = rman_get_bustag(tr->reg); tr->sh = rman_get_bushandle(tr->reg); } else { device_printf(dev, "unable to map register space\n"); goto bad; } if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } tr->playchns = 0; codec = AC97_CREATE(dev, tr, tr_ac97); if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; tr->irqid = 0; tr->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &tr->irqid, RF_ACTIVE | RF_SHAREABLE); if (!tr->irq || snd_setup_intr(dev, tr->irq, 0, tr_intr, tr, &tr->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } if (tr->type == ALI_PCI_ID) { /* * The M5451 generates 31 bit of DMA and in order to do * 32-bit DMA, the 31st bit can be set via its accompanying * ISA bridge. Note that we can't predict whether bus_dma(9) * will actually supply us with a 32-bit buffer and even when * using a low address of BUS_SPACE_MAXADDR_32BIT for both * we might end up with the play buffer being in the 32-bit * range while the record buffer isn't or vice versa. So we * don't enabling the 31st bit. */ lowaddr = ALI_MAXADDR; tr->hwchns = ALI_MAXHWCH; tr->bufsz = ALI_BUFSZ; } else { lowaddr = TR_MAXADDR; tr->hwchns = TR_MAXHWCH; tr->bufsz = pcm_getbuffersize(dev, 4096, TR_DEFAULT_BUFSZ, 65536); } if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/TR_BUFALGN, /*boundary*/0, /*lowaddr*/lowaddr, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/tr->bufsz, /*nsegments*/1, /*maxsegz*/tr->bufsz, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &tr->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(tr->reg), rman_get_start(tr->irq), device_get_nameunit(device_get_parent(dev))); pcm_init(dev, tr); pcm_addchan(dev, PCMDIR_REC, &trrchan_class, tr); for (i = 0; i < dacn; i++) pcm_addchan(dev, PCMDIR_PLAY, &trpchan_class, tr); if (pcm_register(dev, status)) goto bad; return 0; bad: if (codec) ac97_destroy(codec); if (tr->reg) bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); if (tr->ih) bus_teardown_intr(dev, tr->irq, tr->ih); if (tr->irq) bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); if (tr->parent_dmat) bus_dma_tag_destroy(tr->parent_dmat); if (tr->lock) snd_mtxfree(tr->lock); free(tr, M_DEVBUF); return ENXIO; } static int tr_pci_detach(device_t dev) { int r; struct tr_info *tr; r = pcm_unregister(dev); if (r) return r; tr = pcm_getdevinfo(dev); bus_release_resource(dev, tr->regtype, tr->regid, tr->reg); bus_teardown_intr(dev, tr->irq, tr->ih); bus_release_resource(dev, SYS_RES_IRQ, tr->irqid, tr->irq); bus_dma_tag_destroy(tr->parent_dmat); snd_mtxfree(tr->lock); free(tr, M_DEVBUF); return 0; } static int tr_pci_suspend(device_t dev) { int i; struct tr_info *tr; tr = pcm_getdevinfo(dev); for (i = 0; i < tr->playchns; i++) { tr->chinfo[i].was_active = tr->chinfo[i].active; if (tr->chinfo[i].active) { trpchan_trigger(NULL, &tr->chinfo[i], PCMTRIG_STOP); } } tr->recchinfo.was_active = tr->recchinfo.active; if (tr->recchinfo.active) { trrchan_trigger(NULL, &tr->recchinfo, PCMTRIG_STOP); } return 0; } static int tr_pci_resume(device_t dev) { int i; struct tr_info *tr; tr = pcm_getdevinfo(dev); if (tr_init(tr) == -1) { device_printf(dev, "unable to initialize the card\n"); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to initialize the mixer\n"); return ENXIO; } for (i = 0; i < tr->playchns; i++) { if (tr->chinfo[i].was_active) { trpchan_trigger(NULL, &tr->chinfo[i], PCMTRIG_START); } } if (tr->recchinfo.was_active) { trrchan_trigger(NULL, &tr->recchinfo, PCMTRIG_START); } return 0; } static device_method_t tr_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tr_pci_probe), DEVMETHOD(device_attach, tr_pci_attach), DEVMETHOD(device_detach, tr_pci_detach), DEVMETHOD(device_suspend, tr_pci_suspend), DEVMETHOD(device_resume, tr_pci_resume), { 0, 0 } }; static driver_t tr_driver = { "pcm", tr_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_t4dwave, pci, tr_driver, 0, 0); MODULE_DEPEND(snd_t4dwave, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_t4dwave, 1); diff --git a/sys/dev/sound/pci/via8233.c b/sys/dev/sound/pci/via8233.c index 243353805b94..47caa7ea4459 100644 --- a/sys/dev/sound/pci/via8233.c +++ b/sys/dev/sound/pci/via8233.c @@ -1,1443 +1,1441 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002 Orion Hodson * Portions of this code derived from via82c686.c: * Copyright (c) 2000 David Jones * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * Credits due to: * * Grzybowski Rafal, Russell Davies, Mark Handley, Daniel O'Connor for * comments, machine time, testing patches, and patience. VIA for * providing specs. ALSA for helpful comments and some register poke * ordering. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #define VIA8233_PCI_ID 0x30591106 #define VIA8233_REV_ID_8233PRE 0x10 #define VIA8233_REV_ID_8233C 0x20 #define VIA8233_REV_ID_8233 0x30 #define VIA8233_REV_ID_8233A 0x40 #define VIA8233_REV_ID_8235 0x50 #define VIA8233_REV_ID_8237 0x60 #define VIA8233_REV_ID_8251 0x70 #define SEGS_PER_CHAN 2 /* Segments per channel */ #define NDXSCHANS 4 /* No of DXS channels */ #define NMSGDCHANS 1 /* No of multichannel SGD */ #define NWRCHANS 1 /* No of write channels */ #define NCHANS (NWRCHANS + NDXSCHANS + NMSGDCHANS) #define NSEGS NCHANS * SEGS_PER_CHAN /* Segments in SGD table */ #define VIA_SEGS_MIN 2 #define VIA_SEGS_MAX 64 #define VIA_SEGS_DEFAULT 2 #define VIA_BLK_MIN 32 #define VIA_BLK_ALIGN (~(VIA_BLK_MIN - 1)) #define VIA_DEFAULT_BUFSZ 0x1000 /* we rely on this struct being packed to 64 bits */ struct via_dma_op { volatile uint32_t ptr; volatile uint32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 #define VIA_DMAOP_COUNT(x) ((x)&0x00FFFFFF) }; struct via_info; struct via_chinfo { struct via_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; int dir, rbase, active; unsigned int blksz, blkcnt; unsigned int ptr, prevptr; }; struct via_info { device_t dev; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regid, irqid; void *ih; struct ac97_info *codec; unsigned int bufsz, blkcnt; int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; uint16_t codec_caps; uint16_t n_dxs_registered; int play_num, rec_num; struct mtx *lock; struct callout poll_timer; int poll_ticks, polling; }; static uint32_t via_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; static __inline int via_chan_active(struct via_info *via) { int i, ret = 0; if (via == NULL) return (0); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) ret += via->pch[i].active; for (i = 0; i < NWRCHANS; i++) ret += via->rch[i].active; return (ret); } static int sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; uint32_t r; int err, new_en; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); snd_mtxlock(via->lock); r = pci_read_config(dev, VIA_PCI_SPDIF, 1); snd_mtxunlock(via->lock); new_en = (r & VIA_SPDIF_EN) ? 1 : 0; err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) return (err); if (new_en < 0 || new_en > 1) return (EINVAL); if (new_en) r |= VIA_SPDIF_EN; else r &= ~VIA_SPDIF_EN; snd_mtxlock(via->lock); pci_write_config(dev, VIA_PCI_SPDIF, r, 1); snd_mtxunlock(via->lock); return (0); } static int sysctl_via8233_dxs_src(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; int err, val; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); snd_mtxlock(via->lock); val = via->dxs_src; snd_mtxunlock(via->lock); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); snd_mtxlock(via->lock); via->dxs_src = val; snd_mtxunlock(via->lock); return (0); } static int sysctl_via_polling(SYSCTL_HANDLER_ARGS) { struct via_info *via; device_t dev; int err, val; dev = oidp->oid_arg1; via = pcm_getdevinfo(dev); if (via == NULL) return (EINVAL); snd_mtxlock(via->lock); val = via->polling; snd_mtxunlock(via->lock); err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) return (err); if (val < 0 || val > 1) return (EINVAL); snd_mtxlock(via->lock); if (val != via->polling) { if (via_chan_active(via) != 0) err = EBUSY; else if (val == 0) via->polling = 0; else via->polling = 1; } snd_mtxunlock(via->lock); return (err); } static void via_init_sysctls(device_t dev) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_via8233_spdif_enable, "I", "Enable S/PDIF output on primary playback channel"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "dxs_src", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_via8233_dxs_src, "I", "Enable VIA DXS Sample Rate Converter"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "polling", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, sizeof(dev), sysctl_via_polling, "I", "Enable polling mode"); } static __inline uint32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: return (bus_space_read_1(via->st, via->sh, regno)); case 2: return (bus_space_read_2(via->st, via->sh, regno)); case 4: return (bus_space_read_4(via->st, via->sh, regno)); default: return (0xFFFFFFFF); } } static __inline void via_wr(struct via_info *via, int regno, uint32_t data, int size) { switch (size) { case 1: bus_space_write_1(via->st, via->sh, regno, data); break; case 2: bus_space_write_2(via->st, via->sh, regno, data); break; case 4: bus_space_write_4(via->st, via->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* Codec interface */ static int via_waitready_codec(struct via_info *via) { int i; /* poll until codec not busy */ for (i = 0; i < 1000; i++) { if ((via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_BUSY) == 0) return (0); DELAY(1); } device_printf(via->dev, "%s: codec busy\n", __func__); return (1); } static int via_waitvalid_codec(struct via_info *via) { int i; /* poll until codec valid */ for (i = 0; i < 1000; i++) { if (via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_CODEC00_VALID) return (0); DELAY(1); } device_printf(via->dev, "%s: codec invalid\n", __func__); return (1); } static int via_write_codec(kobj_t obj, void *addr, int reg, uint32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return (-1); via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_INDEX(reg) | VIA_AC97_DATA(val), 4); return (0); } static int via_read_codec(kobj_t obj, void *addr, int reg) { struct via_info *via = addr; if (via_waitready_codec(via)) return (-1); via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); if (via_waitready_codec(via)) return (-1); if (via_waitvalid_codec(via)) return (-1); return (via_rd(via, VIA_AC97_CONTROL, 2)); } static kobj_method_t via_ac97_methods[] = { KOBJMETHOD(ac97_read, via_read_codec), KOBJMETHOD(ac97_write, via_write_codec), KOBJMETHOD_END }; AC97_DECLARE(via_ac97); /* -------------------------------------------------------------------- */ static int via_buildsgdt(struct via_chinfo *ch) { uint32_t phys_addr, flag; int i; - phys_addr = sndbuf_getbufaddr(ch->buffer); + phys_addr = ch->buffer->buf_addr; for (i = 0; i < ch->blkcnt; i++) { flag = (i == ch->blkcnt - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ch->sgd_table[i].ptr = phys_addr + (i * ch->blksz); ch->sgd_table[i].flags = flag | ch->blksz; } return (0); } /* -------------------------------------------------------------------- */ /* Format setting functions */ static int via8233wr_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t f = WR_FORMAT_STOP_INDEX; if (AFMT_CHANNEL(format) > 1) f |= WR_FORMAT_STEREO; if (format & AFMT_S16_LE) f |= WR_FORMAT_16BIT; snd_mtxlock(via->lock); via_wr(via, VIA_WR0_FORMAT, f, 4); snd_mtxunlock(via->lock); return (0); } static int via8233dxs_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); v = via_rd(via, r, 4); v &= ~(VIA8233_DXS_RATEFMT_STEREO | VIA8233_DXS_RATEFMT_16BIT); if (AFMT_CHANNEL(format) > 1) v |= VIA8233_DXS_RATEFMT_STEREO; if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); snd_mtxunlock(via->lock); return (0); } static int via8233msgd_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t s = 0xff000000; uint8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; if (AFMT_CHANNEL(format) > 1) { v |= MC_SGD_CHANNELS(2); s |= SLOT3(1) | SLOT4(2); } else { v |= MC_SGD_CHANNELS(1); s |= SLOT3(1) | SLOT4(1); } snd_mtxlock(via->lock); via_wr(via, VIA_MC_SLOT_SELECT, s, 4); via_wr(via, VIA_MC_SGD_FORMAT, v, 1); snd_mtxunlock(via->lock); return (0); } /* -------------------------------------------------------------------- */ /* Speed setting functions */ static uint32_t via8233wr_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) return (ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed)); return (48000); } static uint32_t via8233dxs_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); v = via_rd(via, r, 4) & ~VIA8233_DXS_RATEFMT_48K; /* Careful to avoid overflow (divide by 48 per vt8233c docs) */ v |= VIA8233_DXS_RATEFMT_48K * (speed / 48) / (48000 / 48); via_wr(via, r, v, 4); snd_mtxunlock(via->lock); return (speed); } static uint32_t via8233msgd_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) return (ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed)); return (48000); } /* -------------------------------------------------------------------- */ /* Format probing functions */ static struct pcmchan_caps * via8233wr_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) return (&via_vracaps); return (&via_caps); } static struct pcmchan_caps * via8233dxs_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* * Controlled by onboard registers * * Apparently, few boards can do DXS sample rate * conversion. */ if (via->dxs_src) return (&via_vracaps); return (&via_caps); } static struct pcmchan_caps * via8233msgd_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) return (&via_vracaps); return (&via_caps); } /* -------------------------------------------------------------------- */ /* Common functions */ static int via8233chan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; blksz &= VIA_BLK_ALIGN; - if (blksz > (sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN)) - blksz = sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN; + if (blksz > (ch->buffer->maxsize / VIA_SEGS_MIN)) + blksz = ch->buffer->maxsize / VIA_SEGS_MIN; if (blksz < VIA_BLK_MIN) blksz = VIA_BLK_MIN; if (blkcnt > VIA_SEGS_MAX) blkcnt = VIA_SEGS_MAX; if (blkcnt < VIA_SEGS_MIN) blkcnt = VIA_SEGS_MIN; - while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + while ((blksz * blkcnt) > ch->buffer->maxsize) { if ((blkcnt >> 1) >= VIA_SEGS_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= VIA_BLK_MIN) blksz >>= 1; else break; } - if ((sndbuf_getblksz(ch->buffer) != blksz || - sndbuf_getblkcnt(ch->buffer) != blkcnt) && + if ((ch->buffer->blksz != blksz || + ch->buffer->blkcnt != blkcnt) && sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) device_printf(via->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); - ch->blksz = sndbuf_getblksz(ch->buffer); - ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + ch->blksz = ch->buffer->blksz; + ch->blkcnt = ch->buffer->blkcnt; return (0); } static uint32_t via8233chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; via8233chan_setfragments(obj, data, blksz, via->blkcnt); return (ch->blksz); } static uint32_t via8233chan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; uint32_t v, index, count, ptr; snd_mtxlock(via->lock); if (via->polling != 0) { ptr = ch->ptr; snd_mtxunlock(via->lock); } else { v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); snd_mtxunlock(via->lock); index = v >> 24; /* Last completed buffer */ count = v & 0x00ffffff; /* Bytes remaining */ ptr = (index + 1) * ch->blksz - count; ptr %= ch->blkcnt * ch->blksz; /* Wrap to available space */ } return (ptr); } static void via8233chan_reset(struct via_info *via, struct via_chinfo *ch) { via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via_wr(via, ch->rbase + VIA_RP_CONTROL, 0x00, 1); via_wr(via, ch->rbase + VIA_RP_STATUS, SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); } /* -------------------------------------------------------------------- */ /* Channel initialization functions */ static void via8233chan_sgdinit(struct via_info *via, struct via_chinfo *ch, int chnum) { ch->sgd_table = &via->sgd_table[chnum * VIA_SEGS_MAX]; ch->sgd_addr = via->sgd_addr + chnum * VIA_SEGS_MAX * sizeof(struct via_dma_op); } static void* via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->rec_num++; ch = &via->rch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->blkcnt = via->blkcnt; ch->rbase = VIA_WR_BASE(num); via_wr(via, ch->rbase + VIA_WR_RP_SGD_FORMAT, WR_FIFO_ENABLE, 1); snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void* via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->play_num++; ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->blkcnt = via->blkcnt; /* * All cards apparently support DXS3, but not other DXS * channels. We therefore want to align first DXS channel to * DXS3. */ ch->rbase = VIA_DXS_BASE(NDXSCHANS - 1 - via->n_dxs_registered); via->n_dxs_registered++; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void* via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; int num; snd_mtxlock(via->lock); num = via->play_num++; ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->rbase = VIA_MC_SGD_STATUS; ch->blkcnt = via->blkcnt; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); return (ch); } static void via8233chan_mute(struct via_info *via, struct via_chinfo *ch, int muted) { if (BASE_IS_VIA_DXS_REG(ch->rbase)) { int r; muted = (muted) ? VIA8233_DXS_MUTE : 0; via_wr(via, ch->rbase + VIA8233_RP_DXS_LVOL, muted, 1); via_wr(via, ch->rbase + VIA8233_RP_DXS_RVOL, muted, 1); r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & VIA8233_DXS_MUTE; if (r != muted) device_printf(via->dev, "%s: failed to set dxs volume " "(dxs base 0x%02x).\n", __func__, ch->rbase); } } static __inline int via_poll_channel(struct via_chinfo *ch) { struct via_info *via; uint32_t sz, delta; uint32_t v, index, count; int ptr; if (ch == NULL || ch->channel == NULL || ch->active == 0) return (0); via = ch->parent; sz = ch->blksz * ch->blkcnt; v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); index = v >> 24; count = v & 0x00ffffff; ptr = ((index + 1) * ch->blksz) - count; ptr %= sz; ptr &= ~(ch->blksz - 1); ch->ptr = ptr; delta = (sz + ptr - ch->prevptr) % sz; if (delta < ch->blksz) return (0); ch->prevptr = ptr; return (1); } static void via_poll_callback(void *arg) { struct via_info *via = arg; uint32_t ptrigger = 0, rtrigger = 0; int i; if (via == NULL) return; snd_mtxlock(via->lock); if (via->polling == 0 || via_chan_active(via) == 0) { snd_mtxunlock(via->lock); return; } for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) ptrigger |= (via_poll_channel(&via->pch[i]) != 0) ? (1 << i) : 0; for (i = 0; i < NWRCHANS; i++) rtrigger |= (via_poll_channel(&via->rch[i]) != 0) ? (1 << i) : 0; /* XXX */ callout_reset(&via->poll_timer, 1/*via->poll_ticks*/, via_poll_callback, via); snd_mtxunlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (ptrigger & (1 << i)) chn_intr(via->pch[i].channel); } for (i = 0; i < NWRCHANS; i++) { if (rtrigger & (1 << i)) chn_intr(via->rch[i].channel); } } static int via_poll_ticks(struct via_info *via) { struct via_chinfo *ch; int i; int ret = hz; int pollticks; for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { ch = &via->pch[i]; if (ch->channel == NULL || ch->active == 0) continue; pollticks = ((uint64_t)hz * ch->blksz) / - ((uint64_t)sndbuf_getalign(ch->buffer) * - sndbuf_getspd(ch->buffer)); + ((uint64_t)ch->buffer->align * ch->buffer->spd); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks < ret) ret = pollticks; } for (i = 0; i < NWRCHANS; i++) { ch = &via->rch[i]; if (ch->channel == NULL || ch->active == 0) continue; pollticks = ((uint64_t)hz * ch->blksz) / - ((uint64_t)sndbuf_getalign(ch->buffer) * - sndbuf_getspd(ch->buffer)); + ((uint64_t)ch->buffer->align * ch->buffer->spd); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (pollticks < ret) ret = pollticks; } return (ret); } static int via8233chan_trigger(kobj_t obj, void* data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int pollticks; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(via->lock); switch(go) { case PCMTRIG_START: via_buildsgdt(ch); via8233chan_mute(via, ch, 0); via_wr(via, ch->rbase + VIA_RP_TABLE_PTR, ch->sgd_addr, 4); if (via->polling != 0) { ch->ptr = 0; ch->prevptr = 0; pollticks = ((uint64_t)hz * ch->blksz) / - ((uint64_t)sndbuf_getalign(ch->buffer) * - sndbuf_getspd(ch->buffer)); + ((uint64_t)ch->buffer->align * + ch->buffer->spd); pollticks >>= 2; if (pollticks > hz) pollticks = hz; if (pollticks < 1) pollticks = 1; if (via_chan_active(via) == 0 || pollticks < via->poll_ticks) { if (bootverbose) { if (via_chan_active(via) == 0) printf("%s: pollticks=%d\n", __func__, pollticks); else printf("%s: " "pollticks %d -> %d\n", __func__, via->poll_ticks, pollticks); } via->poll_ticks = pollticks; callout_reset(&via->poll_timer, 1, via_poll_callback, via); } } via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | ((via->polling == 0) ? (SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG) : 0), 1); ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via8233chan_mute(via, ch, 1); via8233chan_reset(via, ch); ch->active = 0; if (via->polling != 0) { if (via_chan_active(via) == 0) { callout_stop(&via->poll_timer); via->poll_ticks = 1; } else { pollticks = via_poll_ticks(via); if (pollticks > via->poll_ticks) { if (bootverbose) printf("%s: pollticks " "%d -> %d\n", __func__, via->poll_ticks, pollticks); via->poll_ticks = pollticks; callout_reset(&via->poll_timer, 1, via_poll_callback, via); } } } break; default: break; } snd_mtxunlock(via->lock); return (0); } static kobj_method_t via8233wr_methods[] = { KOBJMETHOD(channel_init, via8233wr_init), KOBJMETHOD(channel_setformat, via8233wr_setformat), KOBJMETHOD(channel_setspeed, via8233wr_setspeed), KOBJMETHOD(channel_getcaps, via8233wr_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), KOBJMETHOD_END }; CHANNEL_DECLARE(via8233wr); static kobj_method_t via8233dxs_methods[] = { KOBJMETHOD(channel_init, via8233dxs_init), KOBJMETHOD(channel_setformat, via8233dxs_setformat), KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), KOBJMETHOD_END }; CHANNEL_DECLARE(via8233dxs); static kobj_method_t via8233msgd_methods[] = { KOBJMETHOD(channel_init, via8233msgd_init), KOBJMETHOD(channel_setformat, via8233msgd_setformat), KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), KOBJMETHOD(channel_setfragments, via8233chan_setfragments), KOBJMETHOD(channel_trigger, via8233chan_trigger), KOBJMETHOD(channel_getptr, via8233chan_getptr), KOBJMETHOD_END }; CHANNEL_DECLARE(via8233msgd); /* -------------------------------------------------------------------- */ static void via_intr(void *p) { struct via_info *via = p; uint32_t ptrigger = 0, rtrigger = 0; int i, reg, stat; snd_mtxlock(via->lock); if (via->polling != 0) { snd_mtxunlock(via->lock); return; } /* Poll playback channels */ for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (via->pch[i].channel == NULL || via->pch[i].active == 0) continue; reg = via->pch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || !(stat & SGD_STATUS_ACTIVE))) via_wr(via, via->pch[i].rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); ptrigger |= 1 << i; } } /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { if (via->rch[i].channel == NULL || via->rch[i].active == 0) continue; reg = via->rch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || !(stat & SGD_STATUS_ACTIVE))) via_wr(via, via->rch[i].rbase + VIA_RP_CONTROL, SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); rtrigger |= 1 << i; } } snd_mtxunlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { if (ptrigger & (1 << i)) chn_intr(via->pch[i].channel); } for (i = 0; i < NWRCHANS; i++) { if (rtrigger & (1 << i)) chn_intr(via->rch[i].channel); } } /* * Probe and attach the card */ static int via_probe(device_t dev) { switch(pci_get_devid(dev)) { case VIA8233_PCI_ID: switch(pci_get_revid(dev)) { case VIA8233_REV_ID_8233PRE: device_set_desc(dev, "VIA VT8233 (pre)"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233C: device_set_desc(dev, "VIA VT8233C"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233: device_set_desc(dev, "VIA VT8233"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233A: device_set_desc(dev, "VIA VT8233A"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8235: device_set_desc(dev, "VIA VT8235"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8251: device_set_desc(dev, "VIA VT8251"); return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct via_info *via = (struct via_info *)p; via->sgd_addr = bds->ds_addr; } static int via_chip_init(device_t dev) { uint32_t data, cnt; /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); if ((data & VIA_PCI_ACLINK_C00_READY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ /* Assert low */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); /* Wait T_rst_low */ DELAY(100); /* Assert high */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); /* Wait T_rst2clk */ DELAY(5); /* Assert low */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); } else { /* Warm reset */ /* Force no sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); DELAY(100); /* Sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); /* Wait T_sync_high */ DELAY(5); /* Force no sync */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_EN, 1); /* Wait T_sync2clk */ DELAY(5); } /* Power everything up */ pci_write_config(dev, VIA_PCI_ACLINK_CTRL, VIA_PCI_ACLINK_DESIRED, 1); /* Wait for codec to become ready (largest reported delay 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); if (data & VIA_PCI_ACLINK_C00_READY) return (0); DELAY(5000); } device_printf(dev, "primary codec not ready (cnt = 0x%02x)\n", cnt); return (ENXIO); } static int via_attach(device_t dev) { struct via_info *via = NULL; char status[SND_STATUSLEN]; int i, via_dxs_disabled, via_dxs_src, via_dxs_chnum, via_sgd_chnum; int nsegs; uint32_t revid; via = malloc(sizeof *via, M_DEVBUF, M_WAITOK | M_ZERO); via->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_via8233 softc"); via->dev = dev; callout_init(&via->poll_timer, 1); via->poll_ticks = 1; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "polling", &i) == 0 && i != 0) via->polling = 1; else via->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); if (!via->reg) { device_printf(dev, "cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= VIA_BLK_ALIGN; if (i < VIA_BLK_MIN) i = VIA_BLK_MIN; via->blkcnt = via->bufsz / i; i = 0; while (via->blkcnt >> i) i++; via->blkcnt = 1 << (i - 1); if (via->blkcnt < VIA_SEGS_MIN) via->blkcnt = VIA_SEGS_MIN; else if (via->blkcnt > VIA_SEGS_MAX) via->blkcnt = VIA_SEGS_MAX; } else via->blkcnt = VIA_SEGS_DEFAULT; revid = pci_get_revid(dev); /* * VIA8251 lost its interrupt after DMA EOL, and need * a gentle spank on its face within interrupt handler. */ if (revid == VIA8233_REV_ID_8251) via->dma_eol_wake = 1; else via->dma_eol_wake = 0; /* * Decide whether DXS had to be disabled or not */ if (revid == VIA8233_REV_ID_8233A) { /* * DXS channel is disabled. Reports from multiple users * that it plays at half-speed. Do not see this behaviour * on available 8233C or when emulating 8233A register set * on 8233C (either with or without ac97 VRA). */ via_dxs_disabled = 1; } else if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_disabled", &via_dxs_disabled) == 0) via_dxs_disabled = (via_dxs_disabled > 0) ? 1 : 0; else via_dxs_disabled = 0; if (via_dxs_disabled) { via_dxs_chnum = 0; via_sgd_chnum = 1; } else { if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_channels", &via_dxs_chnum) != 0) via_dxs_chnum = NDXSCHANS; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "via_sgd_channels", &via_sgd_chnum) != 0) via_sgd_chnum = NMSGDCHANS; } if (via_dxs_chnum > NDXSCHANS) via_dxs_chnum = NDXSCHANS; else if (via_dxs_chnum < 0) via_dxs_chnum = 0; if (via_sgd_chnum > NMSGDCHANS) via_sgd_chnum = NMSGDCHANS; else if (via_sgd_chnum < 0) via_sgd_chnum = 0; if (via_dxs_chnum + via_sgd_chnum < 1) { /* Minimalist ? */ via_dxs_chnum = 1; via_sgd_chnum = 0; } if (via_dxs_chnum > 0 && resource_int_value(device_get_name(dev), device_get_unit(dev), "via_dxs_src", &via_dxs_src) == 0) via->dxs_src = (via_dxs_src > 0) ? 1 : 0; else via->dxs_src = 0; nsegs = (via_dxs_chnum + via_sgd_chnum + NWRCHANS) * VIA_SEGS_MAX; /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* * DMA tag for SGD table. The 686 uses scatter/gather DMA and * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/nsegs * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) goto bad; if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, nsegs * sizeof(struct via_dma_op), dma_cb, via, 0)) goto bad; if (via_chip_init(dev)) goto bad; via->codec = AC97_CREATE(dev, via, via_ac97); if (!via->codec) goto bad; mixer_init(dev, ac97_getmixerclass(), via->codec); via->codec_caps = ac97_getextcaps(via->codec); /* Try to set VRA without generating an error, VRM not reqrd yet */ if (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { uint16_t ext = ac97_getextmode(via->codec); ext |= (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); ext &= ~AC97_EXTCAP_DRA; ac97_setextmode(via->codec, ext); } snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(via->reg), rman_get_start(via->irq), device_get_nameunit(device_get_parent(dev))); /* Register */ pcm_init(dev, via); for (i = 0; i < via_dxs_chnum; i++) pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); for (i = 0; i < via_sgd_chnum; i++) pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); for (i = 0; i < NWRCHANS; i++) pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); if (via_dxs_chnum > 0) via_init_sysctls(dev); device_printf(dev, "\n", (via_dxs_chnum > 0) ? "En" : "Dis", (via->dxs_src) ? "(SRC)" : "", via_dxs_chnum, via_sgd_chnum, NWRCHANS); if (pcm_register(dev, status)) goto bad; return (0); bad: if (via->codec) ac97_destroy(via->codec); if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_addr) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return (ENXIO); } static int via_detach(device_t dev) { int r; struct via_info *via; r = pcm_unregister(dev); if (r) return (r); via = pcm_getdevinfo(dev); if (via != NULL && (via->play_num != 0 || via->rec_num != 0)) { snd_mtxlock(via->lock); via->polling = 0; callout_stop(&via->poll_timer); snd_mtxunlock(via->lock); callout_drain(&via->poll_timer); } bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); return (0); } static device_method_t via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), DEVMETHOD(device_detach, via_detach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_via8233, pci, via_driver, 0, 0); MODULE_DEPEND(snd_via8233, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_via8233, 1); diff --git a/sys/dev/sound/pci/via82c686.c b/sys/dev/sound/pci/via82c686.c index 40f3521a57a2..34b52d0bdd58 100644 --- a/sys/dev/sound/pci/via82c686.c +++ b/sys/dev/sound/pci/via82c686.c @@ -1,646 +1,646 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 David Jones * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #define VIA_PCI_ID 0x30581106 #define NSEGS 4 /* Number of segments in SGD table */ #define SEGS_PER_CHAN (NSEGS/2) #define TIMEOUT 50 #define VIA_DEFAULT_BUFSZ 0x1000 #undef DEB #define DEB(x) /* we rely on this struct being packed to 64 bits */ struct via_dma_op { u_int32_t ptr; u_int32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 #define VIA_DMAOP_COUNT(x) ((x)&0x00FFFFFF) }; struct via_info; struct via_chinfo { struct via_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; int dir, blksz; int base, count, mode, ctrl; }; struct via_info { bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; bus_dma_tag_t sgd_dmat; bus_dmamap_t sgd_dmamap; bus_addr_t sgd_addr; struct resource *reg, *irq; int regid, irqid; void *ih; struct ac97_info *codec; unsigned int bufsz; struct via_chinfo pch, rch; struct via_dma_op *sgd_table; u_int16_t codec_caps; struct mtx *lock; }; static u_int32_t via_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps via_vracaps = {4000, 48000, via_fmt, 0}; static struct pcmchan_caps via_caps = {48000, 48000, via_fmt, 0}; static __inline u_int32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: return bus_space_read_1(via->st, via->sh, regno); case 2: return bus_space_read_2(via->st, via->sh, regno); case 4: return bus_space_read_4(via->st, via->sh, regno); default: return 0xFFFFFFFF; } } static __inline void via_wr(struct via_info *via, int regno, u_int32_t data, int size) { switch (size) { case 1: bus_space_write_1(via->st, via->sh, regno, data); break; case 2: bus_space_write_2(via->st, via->sh, regno, data); break; case 4: bus_space_write_4(via->st, via->sh, regno, data); break; } } /* -------------------------------------------------------------------- */ /* Codec interface */ static int via_waitready_codec(struct via_info *via) { int i; /* poll until codec not busy */ for (i = 0; (i < TIMEOUT) && (via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_BUSY); i++) DELAY(1); if (i >= TIMEOUT) { printf("via: codec busy\n"); return 1; } return 0; } static int via_waitvalid_codec(struct via_info *via) { int i; /* poll until codec valid */ for (i = 0; (i < TIMEOUT) && !(via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_PRIVALID); i++) DELAY(1); if (i >= TIMEOUT) { printf("via: codec invalid\n"); return 1; } return 0; } static int via_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val) { struct via_info *via = addr; if (via_waitready_codec(via)) return -1; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_INDEX(reg) | val, 4); return 0; } static int via_read_codec(kobj_t obj, void *addr, int reg) { struct via_info *via = addr; if (via_waitready_codec(via)) return -1; via_wr(via, VIA_CODEC_CTL, VIA_CODEC_PRIVALID | VIA_CODEC_READ | VIA_CODEC_INDEX(reg),4); if (via_waitready_codec(via)) return -1; if (via_waitvalid_codec(via)) return -1; return via_rd(via, VIA_CODEC_CTL, 2); } static kobj_method_t via_ac97_methods[] = { KOBJMETHOD(ac97_read, via_read_codec), KOBJMETHOD(ac97_write, via_write_codec), KOBJMETHOD_END }; AC97_DECLARE(via_ac97); /* -------------------------------------------------------------------- */ static int via_buildsgdt(struct via_chinfo *ch) { u_int32_t phys_addr, flag; int i, segs, seg_size; /* * Build the scatter/gather DMA (SGD) table. * There are four slots in the table: two for play, two for record. * This creates two half-buffers, one of which is playing; the other * is feeding. */ seg_size = ch->blksz; - segs = sndbuf_getsize(ch->buffer) / seg_size; - phys_addr = sndbuf_getbufaddr(ch->buffer); + segs = ch->buffer->bufsize / seg_size; + phys_addr = ch->buffer->buf_addr; for (i = 0; i < segs; i++) { flag = (i == segs - 1)? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; ch->sgd_table[i].ptr = phys_addr + (i * seg_size); ch->sgd_table[i].flags = flag | seg_size; } return 0; } /* channel interface */ static void * viachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch; snd_mtxlock(via->lock); if (dir == PCMDIR_PLAY) { ch = &via->pch; ch->base = VIA_PLAY_DMAOPS_BASE; ch->count = VIA_PLAY_DMAOPS_COUNT; ch->ctrl = VIA_PLAY_CONTROL; ch->mode = VIA_PLAY_MODE; ch->sgd_addr = via->sgd_addr; ch->sgd_table = &via->sgd_table[0]; } else { ch = &via->rch; ch->base = VIA_RECORD_DMAOPS_BASE; ch->count = VIA_RECORD_DMAOPS_COUNT; ch->ctrl = VIA_RECORD_CONTROL; ch->mode = VIA_RECORD_MODE; ch->sgd_addr = via->sgd_addr + sizeof(struct via_dma_op) * SEGS_PER_CHAN; ch->sgd_table = &via->sgd_table[SEGS_PER_CHAN]; } ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return NULL; return ch; } static int viachan_setformat(kobj_t obj, void *data, u_int32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int mode, mode_set; mode_set = 0; if (AFMT_CHANNEL(format) > 1) mode_set |= VIA_RPMODE_STEREO; if (format & AFMT_S16_LE) mode_set |= VIA_RPMODE_16BIT; DEB(printf("set format: dir = %d, format=%x\n", ch->dir, format)); snd_mtxlock(via->lock); mode = via_rd(via, ch->mode, 1); mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO); mode |= mode_set; via_wr(via, ch->mode, mode, 1); snd_mtxunlock(via->lock); return 0; } static u_int32_t viachan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; int reg; /* * Basic AC'97 defines a 48 kHz sample rate only. For other rates, * upsampling is required. * * The VT82C686A does not perform upsampling, and neither do we. * If the codec supports variable-rate audio (i.e. does the upsampling * itself), then negotiate the rate with the codec. Otherwise, * return 48 kHz cuz that's all you got. */ if (via->codec_caps & AC97_EXTCAP_VRA) { reg = (ch->dir == PCMDIR_PLAY)? AC97_REGEXT_FDACRATE : AC97_REGEXT_LADCRATE; return ac97_setrate(via->codec, reg, speed); } else return 48000; } static u_int32_t viachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct via_chinfo *ch = data; ch->blksz = blocksize; sndbuf_resize(ch->buffer, SEGS_PER_CHAN, ch->blksz); return ch->blksz; } static int viachan_trigger(kobj_t obj, void *data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; bus_addr_t sgd_addr = ch->sgd_addr; if (!PCMTRIG_COMMON(go)) return 0; DEB(printf("ado located at va=%p pa=%x\n", ch->sgd_table, sgd_addr)); snd_mtxlock(via->lock); if (go == PCMTRIG_START) { via_buildsgdt(ch); via_wr(via, ch->base, sgd_addr, 4); via_wr(via, ch->ctrl, VIA_RPCTRL_START, 1); } else via_wr(via, ch->ctrl, VIA_RPCTRL_TERMINATE, 1); snd_mtxunlock(via->lock); DEB(printf("viachan_trigger: go=%d\n", go)); return 0; } static u_int32_t viachan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; bus_addr_t sgd_addr = ch->sgd_addr; u_int32_t ptr, base, base1, len, seg; snd_mtxlock(via->lock); base1 = via_rd(via, ch->base, 4); len = via_rd(via, ch->count, 4); base = via_rd(via, ch->base, 4); if (base != base1) /* Avoid race hazard */ len = via_rd(via, ch->count, 4); snd_mtxunlock(via->lock); DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base)); /* Base points to SGD segment to do, one past current */ /* Determine how many segments have been done */ seg = (base - sgd_addr) / sizeof(struct via_dma_op); if (seg == 0) seg = SEGS_PER_CHAN; /* Now work out offset: seg less count */ - ptr = (seg * sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN) - len; + ptr = (seg * ch->buffer->bufsize / SEGS_PER_CHAN) - len; if (ch->dir == PCMDIR_REC) { /* DMA appears to operate on memory 'lines' of 32 bytes */ /* so don't return any part line - it isn't in RAM yet */ ptr = ptr & ~0x1f; } DEB(printf("return ptr=%u\n", ptr)); return ptr; } static struct pcmchan_caps * viachan_getcaps(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; return (via->codec_caps & AC97_EXTCAP_VRA)? &via_vracaps : &via_caps; } static kobj_method_t viachan_methods[] = { KOBJMETHOD(channel_init, viachan_init), KOBJMETHOD(channel_setformat, viachan_setformat), KOBJMETHOD(channel_setspeed, viachan_setspeed), KOBJMETHOD(channel_setblocksize, viachan_setblocksize), KOBJMETHOD(channel_trigger, viachan_trigger), KOBJMETHOD(channel_getptr, viachan_getptr), KOBJMETHOD(channel_getcaps, viachan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(viachan); /* -------------------------------------------------------------------- */ static void via_intr(void *p) { struct via_info *via = p; /* DEB(printf("viachan_intr\n")); */ /* Read channel */ snd_mtxlock(via->lock); if (via_rd(via, VIA_PLAY_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_PLAY_STAT, VIA_RPSTAT_INTR, 1); snd_mtxunlock(via->lock); chn_intr(via->pch.channel); snd_mtxlock(via->lock); } /* Write channel */ if (via_rd(via, VIA_RECORD_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_RECORD_STAT, VIA_RPSTAT_INTR, 1); snd_mtxunlock(via->lock); chn_intr(via->rch.channel); return; } snd_mtxunlock(via->lock); } /* * Probe and attach the card */ static int via_probe(device_t dev) { if (pci_get_devid(dev) == VIA_PCI_ID) { device_set_desc(dev, "VIA VT82C686A"); return BUS_PROBE_DEFAULT; } return ENXIO; } static void dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) { struct via_info *via = (struct via_info *)p; via->sgd_addr = bds->ds_addr; } static int via_attach(device_t dev) { struct via_info *via = NULL; char status[SND_STATUSLEN]; u_int32_t data, cnt; via = malloc(sizeof(*via), M_DEVBUF, M_WAITOK | M_ZERO); via->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_via82c686 softc"); pci_enable_busmaster(dev); /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_AC97STATUS, 1); if ((data & VIA_AC97STATUS_RDY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Assert low */ DELAY(100); /* Wait T_rst_low */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN | VIA_ACLINK_NRST, 1); /* Assert high */ DELAY(5); /* Wait T_rst2clk */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Assert low */ } else { /* Warm reset */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Force no sync */ DELAY(100); pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN | VIA_ACLINK_SYNC, 1); /* Sync */ DELAY(5); /* Wait T_sync_high */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_EN, 1); /* Force no sync */ DELAY(5); /* Wait T_sync2clk */ } /* Power everything up */ pci_write_config(dev, VIA_ACLINKCTRL, VIA_ACLINK_DESIRED, 1); /* Wait for codec to become ready (largest reported delay here 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_AC97STATUS, 1); if (data & VIA_AC97STATUS_RDY) break; DELAY(5000); } via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); if (!via->reg) { device_printf(dev, "cannot allocate bus resource."); goto bad; } via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } via_wr(via, VIA_PLAY_MODE, VIA_RPMODE_AUTOSTART | VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1); via_wr(via, VIA_RECORD_MODE, VIA_RPMODE_AUTOSTART | VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1); via->codec = AC97_CREATE(dev, via, via_ac97); if (!via->codec) goto bad; if (mixer_init(dev, ac97_getmixerclass(), via->codec)) goto bad; via->codec_caps = ac97_getextcaps(via->codec); ac97_setextmode(via->codec, via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); /* DMA tag for buffers */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } /* * DMA tag for SGD table. The 686 uses scatter/gather DMA and * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/NSEGS * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, BUS_DMA_NOWAIT, &via->sgd_dmamap) != 0) goto bad; if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, NSEGS * sizeof(struct via_dma_op), dma_cb, via, 0) != 0) goto bad; snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(via->reg), rman_get_start(via->irq), device_get_nameunit(device_get_parent(dev))); /* Register */ pcm_init(dev, via); pcm_addchan(dev, PCMDIR_PLAY, &viachan_class, via); pcm_addchan(dev, PCMDIR_REC, &viachan_class, via); if (pcm_register(dev, status)) goto bad; return 0; bad: if (via->codec) ac97_destroy(via->codec); if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_addr) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return ENXIO; } static int via_detach(device_t dev) { int r; struct via_info *via = NULL; r = pcm_unregister(dev); if (r) return r; via = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); return 0; } static device_method_t via_methods[] = { DEVMETHOD(device_probe, via_probe), DEVMETHOD(device_attach, via_attach), DEVMETHOD(device_detach, via_detach), { 0, 0} }; static driver_t via_driver = { "pcm", via_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_via82c686, pci, via_driver, 0, 0); MODULE_DEPEND(snd_via82c686, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_via82c686, 1); diff --git a/sys/dev/sound/pci/vibes.c b/sys/dev/sound/pci/vibes.c index 7e908f188614..6df76306b28f 100644 --- a/sys/dev/sound/pci/vibes.c +++ b/sys/dev/sound/pci/vibes.c @@ -1,942 +1,942 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Orion Hodson * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ /* * This card has the annoying habit of "clicking" when attached and * detached, haven't been able to remedy this with any combination of * muting. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "mixer_if.h" /* ------------------------------------------------------------------------- */ /* Constants */ #define SV_PCI_ID 0xca005333 #define SV_DEFAULT_BUFSZ 16384 #define SV_MIN_BLKSZ 128 #define SV_INTR_PER_BUFFER 2 #ifndef DEB #define DEB(x) /* (x) */ #endif /* ------------------------------------------------------------------------- */ /* Structures */ struct sc_info; struct sc_chinfo { struct sc_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; u_int32_t fmt, spd; int dir; int dma_active, dma_was_active; }; struct sc_info { device_t dev; /* DMA buffer allocator */ bus_dma_tag_t parent_dmat; /* Enhanced register resources */ struct resource *enh_reg; bus_space_tag_t enh_st; bus_space_handle_t enh_sh; int enh_type; int enh_rid; /* DMA configuration */ struct resource *dmaa_reg, *dmac_reg; bus_space_tag_t dmaa_st, dmac_st; bus_space_handle_t dmaa_sh, dmac_sh; int dmaa_type, dmac_type; int dmaa_rid, dmac_rid; /* Interrupt resources */ struct resource *irq; int irqid; void *ih; /* User configurable buffer size */ unsigned int bufsz; struct sc_chinfo rch, pch; u_int8_t rev; }; static u_int32_t sc_fmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps sc_caps = {8000, 48000, sc_fmt, 0}; /* ------------------------------------------------------------------------- */ /* Register Manipulations */ #define sv_direct_set(x, y, z) _sv_direct_set(x, y, z, __LINE__) static u_int8_t sv_direct_get(struct sc_info *sc, u_int8_t reg) { return bus_space_read_1(sc->enh_st, sc->enh_sh, reg); } static void _sv_direct_set(struct sc_info *sc, u_int8_t reg, u_int8_t val, int line) { u_int8_t n; bus_space_write_1(sc->enh_st, sc->enh_sh, reg, val); n = sv_direct_get(sc, reg); if (n != val) { device_printf(sc->dev, "sv_direct_set register 0x%02x %d != %d from line %d\n", reg, n, val, line); } } static u_int8_t sv_indirect_get(struct sc_info *sc, u_int8_t reg) { if (reg == SV_REG_FORMAT || reg == SV_REG_ANALOG_PWR) reg |= SV_CM_INDEX_MCE; bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_INDEX, reg); return bus_space_read_1(sc->enh_st, sc->enh_sh, SV_CM_DATA); } #define sv_indirect_set(x, y, z) _sv_indirect_set(x, y, z, __LINE__) static void _sv_indirect_set(struct sc_info *sc, u_int8_t reg, u_int8_t val, int line) { if (reg == SV_REG_FORMAT || reg == SV_REG_ANALOG_PWR) reg |= SV_CM_INDEX_MCE; bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_INDEX, reg); bus_space_write_1(sc->enh_st, sc->enh_sh, SV_CM_DATA, val); reg &= ~SV_CM_INDEX_MCE; if (reg != SV_REG_ADC_PLLM) { u_int8_t n; n = sv_indirect_get(sc, reg); if (n != val) { device_printf(sc->dev, "sv_indirect_set register 0x%02x %d != %d line %d\n", reg, n, val, line); } } } static void sv_dma_set_config(bus_space_tag_t st, bus_space_handle_t sh, u_int32_t base, u_int32_t count, u_int8_t mode) { bus_space_write_4(st, sh, SV_DMA_ADDR, base); bus_space_write_4(st, sh, SV_DMA_COUNT, count & 0xffffff); bus_space_write_1(st, sh, SV_DMA_MODE, mode); DEB(printf("base 0x%08x count %5d mode 0x%02x\n", base, count, mode)); } static u_int32_t sv_dma_get_count(bus_space_tag_t st, bus_space_handle_t sh) { return bus_space_read_4(st, sh, SV_DMA_COUNT) & 0xffffff; } /* ------------------------------------------------------------------------- */ /* Play / Record Common Interface */ static void * svchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->parent = sc; ch->channel = c; ch->dir = dir; if (sndbuf_alloc(b, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("svchan_init failed\n")); return NULL; } ch->buffer = b; ch->fmt = SND_FORMAT(AFMT_U8, 1, 0); ch->spd = DSP_DEFAULT_SPEED; ch->dma_active = ch->dma_was_active = 0; return ch; } static struct pcmchan_caps * svchan_getcaps(kobj_t obj, void *data) { return &sc_caps; } static u_int32_t svchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; /* user has requested interrupts every blocksize bytes */ RANGE(blocksize, SV_MIN_BLKSZ, sc->bufsz / SV_INTR_PER_BUFFER); sndbuf_resize(ch->buffer, SV_INTR_PER_BUFFER, blocksize); DEB(printf("svchan_setblocksize: %d\n", blocksize)); return blocksize; } static int svchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; /* NB Just note format here as setting format register * generates noise if dma channel is inactive. */ ch->fmt = (AFMT_CHANNEL(format) > 1) ? SV_AFMT_STEREO : SV_AFMT_MONO; ch->fmt |= (format & AFMT_16BIT) ? SV_AFMT_S16 : SV_AFMT_U8; return 0; } static u_int32_t svchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct sc_chinfo *ch = data; RANGE(speed, 8000, 48000); ch->spd = speed; return speed; } /* ------------------------------------------------------------------------- */ /* Recording interface */ static int sv_set_recspeed(struct sc_info *sc, u_int32_t speed) { u_int32_t f_out, f_actual; u_int32_t rs, re, r, best_r = 0, r2, t, n, best_n = 0; int32_t m, best_m = 0, ms, me, err, min_err; /* This algorithm is a variant described in sonicvibes.pdf * appendix A. This search is marginally more extensive and * results in (nominally) better sample rate matching. */ f_out = SV_F_SCALE * speed; min_err = 0x7fffffff; /* Find bounds of r to examine, rs <= r <= re */ t = 80000000 / f_out; for (rs = 1; (1 << rs) < t; rs++); t = 150000000 / f_out; for (re = 1; (2 << re) < t; re++); if (re > 7) re = 7; /* Search over r, n, m */ for (r = rs; r <= re; r++) { r2 = (1 << r); for (n = 3; n < 34; n++) { m = f_out * n / (SV_F_REF / r2); ms = (m > 3) ? (m - 1) : 3; me = (m < 129) ? (m + 1) : 129; for (m = ms; m <= me; m++) { f_actual = m * SV_F_REF / (n * r2); if (f_actual > f_out) { err = f_actual - f_out; } else { err = f_out - f_actual; } if (err < min_err) { best_r = r; best_m = m - 2; best_n = n - 2; min_err = err; if (err == 0) break; } } } } sv_indirect_set(sc, SV_REG_ADC_PLLM, best_m); sv_indirect_set(sc, SV_REG_ADC_PLLN, SV_ADC_PLLN(best_n) | SV_ADC_PLLR(best_r)); DEB(printf("svrchan_setspeed: %d -> PLLM 0x%02x PLLNR 0x%08x\n", speed, sv_indirect_get(sc, SV_REG_ADC_PLLM), sv_indirect_get(sc, SV_REG_ADC_PLLN))); return 0; } static int svrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ sv_set_recspeed(sc, ch->spd); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAC_MSK; v |= SV_AFMT_DMAC(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ - count = sndbuf_getsize(ch->buffer) / 2; /* DMAC uses words */ + count = ch->buffer->bufsize / 2; /* DMAC uses words */ sv_dma_set_config(sc->dmac_st, sc->dmac_sh, - sndbuf_getbufaddr(ch->buffer), + ch->buffer->buf_addr, count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_RD); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAC_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAC_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE) | SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static u_int32_t svrchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; - sz = sndbuf_getsize(ch->buffer); + sz = ch->buffer->bufsize; /* DMAC uses words */ remain = (sv_dma_get_count(sc->dmac_st, sc->dmac_sh) + 1) * 2; return sz - remain; } static kobj_method_t svrchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svrchan_trigger), KOBJMETHOD(channel_getptr, svrchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(svrchan); /* ------------------------------------------------------------------------- */ /* Playback interface */ static int svpchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t count, enable, speed; u_int8_t v; switch(go) { case PCMTRIG_START: /* Set speed */ speed = (ch->spd * 65536) / 48000; if (speed > 65535) speed = 65535; sv_indirect_set(sc, SV_REG_PCM_SAMPLING_HI, speed >> 8); sv_indirect_set(sc, SV_REG_PCM_SAMPLING_LO, speed & 0xff); /* Set format */ v = sv_indirect_get(sc, SV_REG_FORMAT) & ~SV_AFMT_DMAA_MSK; v |= SV_AFMT_DMAA(ch->fmt); sv_indirect_set(sc, SV_REG_FORMAT, v); /* Program DMA */ - count = sndbuf_getsize(ch->buffer); + count = ch->buffer->bufsize; sv_dma_set_config(sc->dmaa_st, sc->dmaa_sh, - sndbuf_getbufaddr(ch->buffer), + ch->buffer->buf_addr, count - 1, SV_DMA_MODE_AUTO | SV_DMA_MODE_WR); count = count / SV_INTR_PER_BUFFER - 1; sv_indirect_set(sc, SV_REG_DMAA_COUNT_HI, count >> 8); sv_indirect_set(sc, SV_REG_DMAA_COUNT_LO, count & 0xff); /* Enable DMA */ enable = sv_indirect_get(sc, SV_REG_ENABLE); enable = (enable | SV_PLAY_ENABLE) & ~SV_PLAYBACK_PAUSE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_PLAY_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 0; break; } return 0; } static u_int32_t svpchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; u_int32_t sz, remain; - sz = sndbuf_getsize(ch->buffer); + sz = ch->buffer->bufsize; /* DMAA uses bytes */ remain = sv_dma_get_count(sc->dmaa_st, sc->dmaa_sh) + 1; return (sz - remain); } static kobj_method_t svpchan_methods[] = { KOBJMETHOD(channel_init, svchan_init), KOBJMETHOD(channel_setformat, svchan_setformat), KOBJMETHOD(channel_setspeed, svchan_setspeed), KOBJMETHOD(channel_setblocksize, svchan_setblocksize), KOBJMETHOD(channel_trigger, svpchan_trigger), KOBJMETHOD(channel_getptr, svpchan_getptr), KOBJMETHOD(channel_getcaps, svchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(svpchan); /* ------------------------------------------------------------------------- */ /* Mixer support */ struct sv_mix_props { u_int8_t reg; /* Register */ u_int8_t stereo:1; /* Supports 2 channels */ u_int8_t mute:1; /* Supports muting */ u_int8_t neg:1; /* Negative gain */ u_int8_t max; /* Max gain */ u_int8_t iselect; /* Input selector */ } static const mt [SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_LINE1] = {SV_REG_AUX1, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_AUX1}, [SOUND_MIXER_CD] = {SV_REG_CD, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_CD}, [SOUND_MIXER_LINE] = {SV_REG_LINE, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_LINE}, [SOUND_MIXER_MIC] = {SV_REG_MIC, 0, 1, 1, SV_MIC_MAX, SV_INPUT_MIC}, [SOUND_MIXER_SYNTH] = {SV_REG_SYNTH, 0, 1, 1, SV_DEFAULT_MAX, 0}, [SOUND_MIXER_LINE2] = {SV_REG_AUX2, 1, 1, 1, SV_DEFAULT_MAX, SV_INPUT_AUX2}, [SOUND_MIXER_VOLUME] = {SV_REG_MIX, 1, 1, 1, SV_DEFAULT_MAX, 0}, [SOUND_MIXER_PCM] = {SV_REG_PCM, 1, 1, 1, SV_PCM_MAX, 0}, [SOUND_MIXER_RECLEV] = {SV_REG_ADC_INPUT, 1, 0, 0, SV_ADC_MAX, 0}, }; static void sv_channel_gain(struct sc_info *sc, u_int32_t dev, u_int32_t gain, u_int32_t channel) { u_int8_t v; int32_t g; g = mt[dev].max * gain / 100; if (mt[dev].neg) g = mt[dev].max - g; v = sv_indirect_get(sc, mt[dev].reg + channel) & ~mt[dev].max; v |= g; if (mt[dev].mute) { if (gain == 0) { v |= SV_MUTE; } else { v &= ~SV_MUTE; } } sv_indirect_set(sc, mt[dev].reg + channel, v); } static int sv_gain(struct sc_info *sc, u_int32_t dev, u_int32_t left, u_int32_t right) { sv_channel_gain(sc, dev, left, 0); if (mt[dev].stereo) sv_channel_gain(sc, dev, right, 1); return 0; } static void sv_mix_mute_all(struct sc_info *sc) { int32_t i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].reg) sv_gain(sc, i, 0, 0); } } static int sv_mix_init(struct snd_mixer *m) { u_int32_t i, v; for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].max) v |= (1 << i); } mix_setdevs(m, v); for(i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mt[i].iselect) v |= (1 << i); } mix_setrecdevs(m, v); return 0; } static int sv_mix_set(struct snd_mixer *m, u_int32_t dev, u_int32_t left, u_int32_t right) { struct sc_info *sc = mix_getdevinfo(m); return sv_gain(sc, dev, left, right); } static u_int32_t sv_mix_setrecsrc(struct snd_mixer *m, u_int32_t mask) { struct sc_info *sc = mix_getdevinfo(m); u_int32_t i, v; v = sv_indirect_get(sc, SV_REG_ADC_INPUT) & SV_INPUT_GAIN_MASK; for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1 << i) & mask) { v |= mt[i].iselect; } } DEB(printf("sv_mix_setrecsrc: mask 0x%08x adc_input 0x%02x\n", mask, v)); sv_indirect_set(sc, SV_REG_ADC_INPUT, v); return mask; } static kobj_method_t sv_mixer_methods[] = { KOBJMETHOD(mixer_init, sv_mix_init), KOBJMETHOD(mixer_set, sv_mix_set), KOBJMETHOD(mixer_setrecsrc, sv_mix_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(sv_mixer); /* ------------------------------------------------------------------------- */ /* Power management and reset */ static void sv_power(struct sc_info *sc, int state) { u_int8_t v; switch (state) { case 0: /* power on */ v = sv_indirect_get(sc, SV_REG_ANALOG_PWR) &~ SV_ANALOG_OFF; v |= SV_ANALOG_OFF_SRS | SV_ANALOG_OFF_SPLL; sv_indirect_set(sc, SV_REG_ANALOG_PWR, v); v = sv_indirect_get(sc, SV_REG_DIGITAL_PWR) &~ SV_DIGITAL_OFF; v |= SV_DIGITAL_OFF_SYN | SV_DIGITAL_OFF_MU | SV_DIGITAL_OFF_GP; sv_indirect_set(sc, SV_REG_DIGITAL_PWR, v); break; default: /* power off */ v = sv_indirect_get(sc, SV_REG_ANALOG_PWR) | SV_ANALOG_OFF; sv_indirect_set(sc, SV_REG_ANALOG_PWR, v); v = sv_indirect_get(sc, SV_REG_DIGITAL_PWR) | SV_DIGITAL_OFF; sv_indirect_set(sc, SV_REG_DIGITAL_PWR, SV_DIGITAL_OFF); break; } DEB(printf("Power state %d\n", state)); } static int sv_init(struct sc_info *sc) { u_int8_t v; /* Effect reset */ v = sv_direct_get(sc, SV_CM_CONTROL) & ~SV_CM_CONTROL_ENHANCED; v |= SV_CM_CONTROL_RESET; sv_direct_set(sc, SV_CM_CONTROL, v); DELAY(50); v = sv_direct_get(sc, SV_CM_CONTROL) & ~SV_CM_CONTROL_RESET; sv_direct_set(sc, SV_CM_CONTROL, v); DELAY(50); /* Set in enhanced mode */ v = sv_direct_get(sc, SV_CM_CONTROL); v |= SV_CM_CONTROL_ENHANCED; sv_direct_set(sc, SV_CM_CONTROL, v); /* Enable interrupts (UDM and MIDM are superfluous) */ v = sv_direct_get(sc, SV_CM_IMR); v &= ~(SV_CM_IMR_AMSK | SV_CM_IMR_CMSK | SV_CM_IMR_SMSK); sv_direct_set(sc, SV_CM_IMR, v); /* Select ADC PLL for ADC clock */ v = sv_indirect_get(sc, SV_REG_CLOCK_SOURCE) & ~SV_CLOCK_ALTERNATE; sv_indirect_set(sc, SV_REG_CLOCK_SOURCE, v); /* Disable loopback - binds ADC and DAC rates */ v = sv_indirect_get(sc, SV_REG_LOOPBACK) & ~SV_LOOPBACK_ENABLE; sv_indirect_set(sc, SV_REG_LOOPBACK, v); /* Disable SRS */ v = sv_indirect_get(sc, SV_REG_SRS_SPACE) | SV_SRS_DISABLED; sv_indirect_set(sc, SV_REG_SRS_SPACE, v); /* Get revision */ sc->rev = sv_indirect_get(sc, SV_REG_REVISION); return 0; } static int sv_suspend(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); sc->rch.dma_was_active = sc->rch.dma_active; svrchan_trigger(NULL, &sc->rch, PCMTRIG_ABORT); sc->pch.dma_was_active = sc->pch.dma_active; svrchan_trigger(NULL, &sc->pch, PCMTRIG_ABORT); sv_mix_mute_all(sc); sv_power(sc, 3); return 0; } static int sv_resume(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 0); if (sv_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); return ENXIO; } if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); return ENXIO; } if (sc->rch.dma_was_active) { svrchan_trigger(0, &sc->rch, PCMTRIG_START); } if (sc->pch.dma_was_active) { svpchan_trigger(0, &sc->pch, PCMTRIG_START); } return 0; } /* ------------------------------------------------------------------------- */ /* Resource related */ static void sv_intr(void *data) { struct sc_info *sc = data; u_int8_t status; status = sv_direct_get(sc, SV_CM_STATUS); if (status & SV_CM_STATUS_AINT) chn_intr(sc->pch.channel); if (status & SV_CM_STATUS_CINT) chn_intr(sc->rch.channel); status &= ~(SV_CM_STATUS_AINT|SV_CM_STATUS_CINT); DEB(if (status) printf("intr 0x%02x ?\n", status)); return; } static int sv_probe(device_t dev) { switch(pci_get_devid(dev)) { case SV_PCI_ID: device_set_desc(dev, "S3 Sonicvibes"); return BUS_PROBE_DEFAULT; default: return ENXIO; } } static int sv_attach(device_t dev) { struct sc_info *sc; rman_res_t count, midi_start, games_start; u_int32_t data; char status[SND_STATUSLEN]; u_long sdmaa, sdmac, ml, mu; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; pci_enable_busmaster(dev); if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { device_printf(dev, "chip is in D%d power mode " "-- setting to D0\n", pci_get_powerstate(dev)); pci_set_powerstate(dev, PCI_POWERSTATE_D0); } sc->enh_rid = SV_PCI_ENHANCED; sc->enh_type = SYS_RES_IOPORT; sc->enh_reg = bus_alloc_resource_any(dev, sc->enh_type, &sc->enh_rid, RF_ACTIVE); if (sc->enh_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate enh\n"); return ENXIO; } sc->enh_st = rman_get_bustag(sc->enh_reg); sc->enh_sh = rman_get_bushandle(sc->enh_reg); data = pci_read_config(dev, SV_PCI_DMAA, 4); DEB(printf("sv_attach: initial dmaa 0x%08x\n", data)); data = pci_read_config(dev, SV_PCI_DMAC, 4); DEB(printf("sv_attach: initial dmac 0x%08x\n", data)); /* Initialize DMA_A and DMA_C */ pci_write_config(dev, SV_PCI_DMAA, SV_PCI_DMA_EXTENDED, 4); pci_write_config(dev, SV_PCI_DMAC, 0, 4); /* Register IRQ handler */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, sv_intr, sc, &sc->ih)) { device_printf(dev, "sv_attach: Unable to map interrupt\n"); goto fail; } sc->bufsz = pcm_getbuffersize(dev, 4096, SV_DEFAULT_BUFSZ, 65536); if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "sv_attach: Unable to create dma tag\n"); goto fail; } /* Power up and initialize */ sv_mix_mute_all(sc); sv_power(sc, 0); sv_init(sc); if (mixer_init(dev, &sv_mixer_class, sc) != 0) { device_printf(dev, "sv_attach: Mixer failed to initialize\n"); goto fail; } /* XXX This is a hack, and it's ugly. Okay, the deal is this * card has two more io regions that available for automatic * configuration by the pci code. These need to be allocated * to used as control registers for the DMA engines. * Unfortunately FBSD has no bus_space_foo() functions so we * have to grab port space in region of existing resources. Go * for space between midi and game ports. */ bus_get_resource(dev, SYS_RES_IOPORT, SV_PCI_MIDI, &midi_start, &count); bus_get_resource(dev, SYS_RES_IOPORT, SV_PCI_GAMES, &games_start, &count); if (games_start < midi_start) { ml = games_start; mu = midi_start; } else { ml = midi_start; mu = games_start; } /* Check assumptions about space availability and alignment. How driver loaded can determine whether games_start > midi_start or vice versa */ if ((mu - ml >= 0x800) || ((mu - ml) % 0x200)) { device_printf(dev, "sv_attach: resource assumptions not met " "(midi 0x%08lx, games 0x%08lx)\n", (u_long)midi_start, (u_long)games_start); goto fail; } sdmaa = ml + 0x40; sdmac = sdmaa + 0x40; /* Add resources to list of pci resources for this device - from here on * they look like normal pci resources. */ bus_set_resource(dev, SYS_RES_IOPORT, SV_PCI_DMAA, sdmaa, SV_PCI_DMAA_SIZE); bus_set_resource(dev, SYS_RES_IOPORT, SV_PCI_DMAC, sdmac, SV_PCI_DMAC_SIZE); /* Cache resource short-cuts for dma_a */ sc->dmaa_rid = SV_PCI_DMAA; sc->dmaa_type = SYS_RES_IOPORT; sc->dmaa_reg = bus_alloc_resource_any(dev, sc->dmaa_type, &sc->dmaa_rid, RF_ACTIVE); if (sc->dmaa_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmaa\n"); goto fail; } sc->dmaa_st = rman_get_bustag(sc->dmaa_reg); sc->dmaa_sh = rman_get_bushandle(sc->dmaa_reg); /* Poke port into dma_a configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAA, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmaa & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAA, data, 4); DEB(printf("dmaa: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAA, 4))); /* Cache resource short-cuts for dma_c */ sc->dmac_rid = SV_PCI_DMAC; sc->dmac_type = SYS_RES_IOPORT; sc->dmac_reg = bus_alloc_resource_any(dev, sc->dmac_type, &sc->dmac_rid, RF_ACTIVE); if (sc->dmac_reg == NULL) { device_printf(dev, "sv_attach: cannot allocate dmac\n"); goto fail; } sc->dmac_st = rman_get_bustag(sc->dmac_reg); sc->dmac_sh = rman_get_bushandle(sc->dmac_reg); /* Poke port into dma_c configuration, nb bit flags to enable dma */ data = pci_read_config(dev, SV_PCI_DMAC, 4) | SV_PCI_DMA_ENABLE | SV_PCI_DMA_EXTENDED; data = ((u_int32_t)sdmac & 0xfffffff0) | (data & 0x0f); pci_write_config(dev, SV_PCI_DMAC, data, 4); DEB(printf("dmac: 0x%x 0x%x\n", data, pci_read_config(dev, SV_PCI_DMAC, 4))); if (bootverbose) printf("Sonicvibes: revision %d.\n", sc->rev); pcm_init(dev, sc); pcm_addchan(dev, PCMDIR_PLAY, &svpchan_class, sc); pcm_addchan(dev, PCMDIR_REC, &svrchan_class, sc); snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s", rman_get_start(sc->enh_reg), rman_get_start(sc->irq), device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) { device_printf(dev, "sv_attach: pcm_register fail\n"); goto fail; } DEB(printf("sv_attach: succeeded\n")); return 0; fail: if (sc->parent_dmat) bus_dma_tag_destroy(sc->parent_dmat); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->enh_reg) bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); if (sc->dmaa_reg) bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); if (sc->dmac_reg) bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); return ENXIO; } static int sv_detach(device_t dev) { struct sc_info *sc; int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); sv_mix_mute_all(sc); sv_power(sc, 3); bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->enh_type, sc->enh_rid, sc->enh_reg); bus_release_resource(dev, sc->dmaa_type, sc->dmaa_rid, sc->dmaa_reg); bus_release_resource(dev, sc->dmac_type, sc->dmac_rid, sc->dmac_reg); free(sc, M_DEVBUF); return 0; } static device_method_t sc_methods[] = { DEVMETHOD(device_probe, sv_probe), DEVMETHOD(device_attach, sv_attach), DEVMETHOD(device_detach, sv_detach), DEVMETHOD(device_resume, sv_resume), DEVMETHOD(device_suspend, sv_suspend), { 0, 0 } }; static driver_t sonicvibes_driver = { "pcm", sc_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_vibes, pci, sonicvibes_driver, 0, 0); MODULE_DEPEND(snd_vibes, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_vibes, 1); diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index 0cfa04fc3167..841fc5b0d8a9 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -1,732 +1,604 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include "feeder_if.h" #define SND_USE_FXDIV #define SND_DECLARE_FXDIV #include "snd_fxdiv_gen.h" struct snd_dbuf * sndbuf_create(struct pcm_channel *channel, const char *desc) { struct snd_dbuf *b; b = malloc(sizeof(*b), M_DEVBUF, M_WAITOK | M_ZERO); snprintf(b->name, SNDBUF_NAMELEN, "%s:%s", channel->name, desc); b->channel = channel; return b; } void sndbuf_destroy(struct snd_dbuf *b) { sndbuf_free(b); free(b, M_DEVBUF); } -bus_addr_t -sndbuf_getbufaddr(struct snd_dbuf *buf) -{ - return (buf->buf_addr); -} - static void sndbuf_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct snd_dbuf *b = (struct snd_dbuf *)arg; if (snd_verbose > 3) { printf("sndbuf_setmap %lx, %lx; ", (u_long)segs[0].ds_addr, (u_long)segs[0].ds_len); printf("%p -> %lx\n", b->buf, (u_long)segs[0].ds_addr); } if (error == 0) b->buf_addr = segs[0].ds_addr; else b->buf_addr = 0; } /* * Allocate memory for DMA buffer. If the device does not use DMA transfers, * the driver can call malloc(9) and sndbuf_setup() itself. */ int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, int dmaflags, unsigned int size) { int ret; b->dmatag = dmatag; b->dmaflags = dmaflags | BUS_DMA_NOWAIT | BUS_DMA_COHERENT; b->maxsize = size; b->bufsize = b->maxsize; b->buf_addr = 0; b->flags |= SNDBUF_F_MANAGED; if (bus_dmamem_alloc(b->dmatag, (void **)&b->buf, b->dmaflags, &b->dmamap)) { sndbuf_free(b); return (ENOMEM); } if (bus_dmamap_load(b->dmatag, b->dmamap, b->buf, b->maxsize, sndbuf_setmap, b, BUS_DMA_NOWAIT) != 0 || b->buf_addr == 0) { sndbuf_free(b); return (ENOMEM); } ret = sndbuf_resize(b, 2, b->maxsize / 2); if (ret != 0) sndbuf_free(b); return (ret); } int sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size) { b->flags &= ~SNDBUF_F_MANAGED; if (buf) b->flags |= SNDBUF_F_MANAGED; b->buf = buf; b->maxsize = size; b->bufsize = b->maxsize; return sndbuf_resize(b, 2, b->maxsize / 2); } void sndbuf_free(struct snd_dbuf *b) { if (b->tmpbuf) free(b->tmpbuf, M_DEVBUF); if (b->shadbuf) free(b->shadbuf, M_DEVBUF); if (b->buf) { if (b->flags & SNDBUF_F_MANAGED) { if (b->buf_addr) bus_dmamap_unload(b->dmatag, b->dmamap); if (b->dmatag) bus_dmamem_free(b->dmatag, b->buf, b->dmamap); } else free(b->buf, M_DEVBUF); } - seldrain(sndbuf_getsel(b)); + seldrain(&b->sel); b->tmpbuf = NULL; b->shadbuf = NULL; b->buf = NULL; b->sl = 0; b->dmatag = NULL; b->dmamap = NULL; } #define SNDBUF_CACHE_SHIFT 5 int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { unsigned int bufsize, allocsize; u_int8_t *tmpbuf; CHN_LOCK(b->channel); if (b->maxsize == 0) goto out; if (blkcnt == 0) blkcnt = b->blkcnt; if (blksz == 0) blksz = b->blksz; if (blkcnt < 2 || blksz < 16 || (blkcnt * blksz) > b->maxsize) { CHN_UNLOCK(b->channel); return EINVAL; } if (blkcnt == b->blkcnt && blksz == b->blksz) goto out; bufsize = blkcnt * blksz; if (bufsize > b->allocsize || bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { allocsize = round_page(bufsize); CHN_UNLOCK(b->channel); tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); CHN_LOCK(b->channel); if (snd_verbose > 3) printf("%s(): b=%p %p -> %p [%d -> %d : %d]\n", __func__, b, b->tmpbuf, tmpbuf, b->allocsize, allocsize, bufsize); if (b->tmpbuf != NULL) free(b->tmpbuf, M_DEVBUF); b->tmpbuf = tmpbuf; b->allocsize = allocsize; } else if (snd_verbose > 3) printf("%s(): b=%p %d [%d] NOCHANGE\n", __func__, b, b->allocsize, b->bufsize); b->blkcnt = blkcnt; b->blksz = blksz; b->bufsize = bufsize; sndbuf_reset(b); out: CHN_UNLOCK(b->channel); return 0; } int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { unsigned int bufsize, allocsize; u_int8_t *buf, *tmpbuf, *shadbuf; if (blkcnt < 2 || blksz < 16) return EINVAL; bufsize = blksz * blkcnt; if (bufsize > b->allocsize || bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { allocsize = round_page(bufsize); CHN_UNLOCK(b->channel); buf = malloc(allocsize, M_DEVBUF, M_WAITOK); tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); shadbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); CHN_LOCK(b->channel); if (b->buf != NULL) free(b->buf, M_DEVBUF); b->buf = buf; if (b->tmpbuf != NULL) free(b->tmpbuf, M_DEVBUF); b->tmpbuf = tmpbuf; if (b->shadbuf != NULL) free(b->shadbuf, M_DEVBUF); b->shadbuf = shadbuf; if (snd_verbose > 3) printf("%s(): b=%p %d -> %d [%d]\n", __func__, b, b->allocsize, allocsize, bufsize); b->allocsize = allocsize; } else if (snd_verbose > 3) printf("%s(): b=%p %d [%d] NOCHANGE\n", __func__, b, b->allocsize, b->bufsize); b->blkcnt = blkcnt; b->blksz = blksz; b->bufsize = bufsize; b->maxsize = bufsize; b->sl = bufsize; sndbuf_reset(b); return 0; } /** * @brief Zero out space in buffer free area * * This function clears a chunk of @c length bytes in the buffer free area * (i.e., where the next write will be placed). * * @param b buffer context * @param length number of bytes to blank */ void sndbuf_clear(struct snd_dbuf *b, unsigned int length) { int i; u_char data, *p; if (length == 0) return; if (length > b->bufsize) length = b->bufsize; data = sndbuf_zerodata(b->fmt); i = sndbuf_getfreeptr(b); - p = sndbuf_getbuf(b); + p = b->buf; for (; length > 0; length--, i++) p[i % b->bufsize] = data; } /** * @brief Zap buffer contents, resetting "ready area" fields * * @param b buffer context */ void sndbuf_fillsilence(struct snd_dbuf *b) { if (b->bufsize > 0) - memset(sndbuf_getbuf(b), sndbuf_zerodata(b->fmt), b->bufsize); + memset(b->buf, sndbuf_zerodata(b->fmt), b->bufsize); b->rp = 0; b->rl = b->bufsize; } void sndbuf_fillsilence_rl(struct snd_dbuf *b, u_int rl) { if (b->bufsize > 0) - memset(sndbuf_getbuf(b), sndbuf_zerodata(b->fmt), b->bufsize); + memset(b->buf, sndbuf_zerodata(b->fmt), b->bufsize); b->rp = 0; b->rl = min(b->bufsize, rl); } /** * @brief Reset buffer w/o flushing statistics * * This function just zeroes out buffer contents and sets the "ready length" * to zero. This was originally to facilitate minimal playback interruption * (i.e., dropped samples) in SNDCTL_DSP_SILENCE/SKIP ioctls. * * @param b buffer context */ void sndbuf_softreset(struct snd_dbuf *b) { b->rl = 0; if (b->buf && b->bufsize > 0) sndbuf_clear(b, b->bufsize); } void sndbuf_reset(struct snd_dbuf *b) { b->hp = 0; b->rp = 0; b->rl = 0; b->dl = 0; b->prev_total = 0; b->total = 0; b->xrun = 0; if (b->buf && b->bufsize > 0) sndbuf_clear(b, b->bufsize); sndbuf_clearshadow(b); } -u_int32_t -sndbuf_getfmt(struct snd_dbuf *b) -{ - return b->fmt; -} - int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt) { b->fmt = fmt; b->bps = AFMT_BPS(b->fmt); b->align = AFMT_ALIGN(b->fmt); return 0; } -unsigned int -sndbuf_getspd(struct snd_dbuf *b) -{ - return b->spd; -} - void sndbuf_setspd(struct snd_dbuf *b, unsigned int spd) { b->spd = spd; } -unsigned int -sndbuf_getalign(struct snd_dbuf *b) -{ - return (b->align); -} - -unsigned int -sndbuf_getblkcnt(struct snd_dbuf *b) -{ - return b->blkcnt; -} - -void -sndbuf_setblkcnt(struct snd_dbuf *b, unsigned int blkcnt) -{ - b->blkcnt = blkcnt; -} - -unsigned int -sndbuf_getblksz(struct snd_dbuf *b) -{ - return b->blksz; -} - -void -sndbuf_setblksz(struct snd_dbuf *b, unsigned int blksz) -{ - b->blksz = blksz; -} - -unsigned int -sndbuf_getbps(struct snd_dbuf *b) -{ - return b->bps; -} - -void * -sndbuf_getbuf(struct snd_dbuf *b) -{ - return b->buf; -} - void * sndbuf_getbufofs(struct snd_dbuf *b, unsigned int ofs) { KASSERT(ofs < b->bufsize, ("%s: ofs invalid %d", __func__, ofs)); return b->buf + ofs; } -unsigned int -sndbuf_getsize(struct snd_dbuf *b) -{ - return b->bufsize; -} - -unsigned int -sndbuf_getmaxsize(struct snd_dbuf *b) -{ - return b->maxsize; -} - -unsigned int -sndbuf_getallocsize(struct snd_dbuf *b) -{ - return b->allocsize; -} - unsigned int sndbuf_runsz(struct snd_dbuf *b) { return b->dl; } void sndbuf_setrun(struct snd_dbuf *b, int go) { b->dl = go? b->blksz : 0; } -struct selinfo * -sndbuf_getsel(struct snd_dbuf *b) -{ - return &b->sel; -} - -/************************************************************/ -unsigned int -sndbuf_getxrun(struct snd_dbuf *b) -{ - return b->xrun; -} - void sndbuf_setxrun(struct snd_dbuf *b, unsigned int xrun) { b->xrun = xrun; } -unsigned int -sndbuf_gethwptr(struct snd_dbuf *b) -{ - return b->hp; -} - -void -sndbuf_sethwptr(struct snd_dbuf *b, unsigned int ptr) -{ - b->hp = ptr; -} - unsigned int sndbuf_getready(struct snd_dbuf *b) { KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); return b->rl; } unsigned int sndbuf_getreadyptr(struct snd_dbuf *b) { KASSERT((b->rp >= 0) && (b->rp <= b->bufsize), ("%s: b->rp invalid %d", __func__, b->rp)); return b->rp; } unsigned int sndbuf_getfree(struct snd_dbuf *b) { KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); return b->bufsize - b->rl; } unsigned int sndbuf_getfreeptr(struct snd_dbuf *b) { KASSERT((b->rp >= 0) && (b->rp <= b->bufsize), ("%s: b->rp invalid %d", __func__, b->rp)); KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); return (b->rp + b->rl) % b->bufsize; } u_int64_t sndbuf_getblocks(struct snd_dbuf *b) { return b->total / b->blksz; } -u_int64_t -sndbuf_getprevblocks(struct snd_dbuf *b) -{ - return b->prev_total / b->blksz; -} - -u_int64_t -sndbuf_gettotal(struct snd_dbuf *b) -{ - return b->total; -} - -u_int64_t -sndbuf_getprevtotal(struct snd_dbuf *b) -{ - return b->prev_total; -} - -void -sndbuf_updateprevtotal(struct snd_dbuf *b) -{ - b->prev_total = b->total; -} - unsigned int sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to) { if (from == NULL || to == NULL || v == 0) return 0; - return snd_xbytes(v, sndbuf_getalign(from) * sndbuf_getspd(from), - sndbuf_getalign(to) * sndbuf_getspd(to)); + return snd_xbytes(v, from->align * from->spd, to->align * to->spd); } u_int8_t sndbuf_zerodata(u_int32_t fmt) { if (fmt & (AFMT_SIGNED | AFMT_PASSTHROUGH)) return (0x00); else if (fmt & AFMT_MU_LAW) return (0x7f); else if (fmt & AFMT_A_LAW) return (0x55); return (0x80); } /************************************************************/ /** * @brief Acquire buffer space to extend ready area * * This function extends the ready area length by @c count bytes, and may * optionally copy samples from another location stored in @c from. The * counter @c snd_dbuf::total is also incremented by @c count bytes. * * @param b audio buffer * @param from sample source (optional) * @param count number of bytes to acquire * * @retval 0 Unconditional */ int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count) { int l; KASSERT(count <= sndbuf_getfree(b), ("%s: count %d > free %d", __func__, count, sndbuf_getfree(b))); KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); b->total += count; if (from != NULL) { while (count > 0) { - l = min(count, sndbuf_getsize(b) - sndbuf_getfreeptr(b)); + l = min(count, b->bufsize - sndbuf_getfreeptr(b)); bcopy(from, sndbuf_getbufofs(b, sndbuf_getfreeptr(b)), l); from += l; b->rl += l; count -= l; } } else b->rl += count; KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d, count %d", __func__, b->rl, count)); return 0; } /** * @brief Dispose samples from channel buffer, increasing size of ready area * * This function discards samples from the supplied buffer by advancing the * ready area start pointer and decrementing the ready area length. If * @c to is not NULL, then the discard samples will be copied to the location * it points to. * * @param b PCM channel sound buffer * @param to destination buffer (optional) * @param count number of bytes to discard * * @returns 0 unconditionally */ int sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) { int l; KASSERT(count <= sndbuf_getready(b), ("%s: count %d > ready %d", __func__, count, sndbuf_getready(b))); KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); if (to != NULL) { while (count > 0) { - l = min(count, sndbuf_getsize(b) - sndbuf_getreadyptr(b)); + l = min(count, b->bufsize - sndbuf_getreadyptr(b)); bcopy(sndbuf_getbufofs(b, sndbuf_getreadyptr(b)), to, l); to += l; b->rl -= l; b->rp = (b->rp + l) % b->bufsize; count -= l; } } else { b->rl -= count; b->rp = (b->rp + count) % b->bufsize; } KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d, count %d", __func__, b->rl, count)); return 0; } #ifdef SND_DIAGNOSTIC static uint32_t snd_feeder_maxfeed = 0; SYSCTL_UINT(_hw_snd, OID_AUTO, feeder_maxfeed, CTLFLAG_RD, &snd_feeder_maxfeed, 0, "maximum feeder count request"); static uint32_t snd_feeder_maxcycle = 0; SYSCTL_UINT(_hw_snd, OID_AUTO, feeder_maxcycle, CTLFLAG_RD, &snd_feeder_maxcycle, 0, "maximum feeder cycle"); #endif /* count is number of bytes we want added to destination buffer */ int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) { unsigned int cnt, maxfeed; #ifdef SND_DIAGNOSTIC unsigned int cycle; if (count > snd_feeder_maxfeed) snd_feeder_maxfeed = count; cycle = 0; #endif KASSERT(count > 0, ("can't feed 0 bytes")); if (sndbuf_getfree(to) < count) return (EINVAL); - maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(to)); + maxfeed = SND_FXROUND(SND_FXDIV_MAX, to->align); do { cnt = FEEDER_FEED(feeder, channel, to->tmpbuf, min(count, maxfeed), from); if (cnt == 0) break; sndbuf_acquire(to, to->tmpbuf, cnt); count -= cnt; #ifdef SND_DIAGNOSTIC cycle++; #endif } while (count != 0); #ifdef SND_DIAGNOSTIC if (cycle > snd_feeder_maxcycle) snd_feeder_maxcycle = cycle; #endif return (0); } /** * @brief Clear the shadow buffer by filling with samples equal to zero. * * @param b buffer to clear */ void sndbuf_clearshadow(struct snd_dbuf *b) { KASSERT(b != NULL, ("b is a null pointer")); KASSERT(b->sl >= 0, ("illegal shadow length")); if ((b->shadbuf != NULL) && (b->sl > 0)) memset(b->shadbuf, sndbuf_zerodata(b->fmt), b->sl); } #ifdef OSSV4_EXPERIMENT /** * @brief Return peak value from samples in buffer ready area. * * Peak ranges from 0-32767. If channel is monaural, most significant 16 * bits will be zero. For now, only expects to work with 1-2 channel * buffers. * * @note Currently only operates with linear PCM formats. * * @param b buffer to analyze * @param lpeak pointer to store left peak value * @param rpeak pointer to store right peak value */ void sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp) { u_int32_t lpeak, rpeak; lpeak = 0; rpeak = 0; /** * @todo fill this in later */ } #endif diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h index 421db261566d..5509256ea433 100644 --- a/sys/dev/sound/pcm/buffer.h +++ b/sys/dev/sound/pcm/buffer.h @@ -1,127 +1,105 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ #define SNDBUF_F_MANAGED 0x00000001 #define SNDBUF_NAMELEN 48 struct snd_dbuf { u_int8_t *buf, *tmpbuf; u_int8_t *shadbuf; /**< shadow buffer used w/ S_D_SILENCE/SKIP */ volatile int sl; /**< shadbuf ready length in # of bytes */ unsigned int bufsize, maxsize, allocsize; volatile int dl; /* transfer size */ volatile int rp; /* pointers to the ready area */ volatile int rl; /* length of ready area */ volatile int hp; volatile u_int64_t total, prev_total; int dmachan; /* dma channel */ u_int32_t fmt, spd, bps, align; unsigned int blksz, blkcnt; int xrun; u_int32_t flags; bus_dmamap_t dmamap; bus_dma_tag_t dmatag; bus_addr_t buf_addr; int dmaflags; struct selinfo sel; struct pcm_channel *channel; char name[SNDBUF_NAMELEN]; }; struct snd_dbuf *sndbuf_create(struct pcm_channel *channel, const char *desc); void sndbuf_destroy(struct snd_dbuf *b); int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, int dmaflags, unsigned int size); int sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size); void sndbuf_free(struct snd_dbuf *b); int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz); int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz); void sndbuf_reset(struct snd_dbuf *b); void sndbuf_clear(struct snd_dbuf *b, unsigned int length); void sndbuf_fillsilence(struct snd_dbuf *b); void sndbuf_fillsilence_rl(struct snd_dbuf *b, u_int rl); void sndbuf_softreset(struct snd_dbuf *b); void sndbuf_clearshadow(struct snd_dbuf *b); -u_int32_t sndbuf_getfmt(struct snd_dbuf *b); int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt); -unsigned int sndbuf_getspd(struct snd_dbuf *b); void sndbuf_setspd(struct snd_dbuf *b, unsigned int spd); -unsigned int sndbuf_getbps(struct snd_dbuf *b); -bus_addr_t sndbuf_getbufaddr(struct snd_dbuf *buf); - -void *sndbuf_getbuf(struct snd_dbuf *b); void *sndbuf_getbufofs(struct snd_dbuf *b, unsigned int ofs); -unsigned int sndbuf_getsize(struct snd_dbuf *b); -unsigned int sndbuf_getmaxsize(struct snd_dbuf *b); -unsigned int sndbuf_getallocsize(struct snd_dbuf *b); -unsigned int sndbuf_getalign(struct snd_dbuf *b); -unsigned int sndbuf_getblkcnt(struct snd_dbuf *b); -void sndbuf_setblkcnt(struct snd_dbuf *b, unsigned int blkcnt); -unsigned int sndbuf_getblksz(struct snd_dbuf *b); -void sndbuf_setblksz(struct snd_dbuf *b, unsigned int blksz); unsigned int sndbuf_runsz(struct snd_dbuf *b); void sndbuf_setrun(struct snd_dbuf *b, int go); -struct selinfo *sndbuf_getsel(struct snd_dbuf *b); -unsigned int sndbuf_getxrun(struct snd_dbuf *b); void sndbuf_setxrun(struct snd_dbuf *b, unsigned int xrun); -unsigned int sndbuf_gethwptr(struct snd_dbuf *b); -void sndbuf_sethwptr(struct snd_dbuf *b, unsigned int ptr); unsigned int sndbuf_getfree(struct snd_dbuf *b); unsigned int sndbuf_getfreeptr(struct snd_dbuf *b); unsigned int sndbuf_getready(struct snd_dbuf *b); unsigned int sndbuf_getreadyptr(struct snd_dbuf *b); u_int64_t sndbuf_getblocks(struct snd_dbuf *b); -u_int64_t sndbuf_getprevblocks(struct snd_dbuf *b); -u_int64_t sndbuf_gettotal(struct snd_dbuf *b); -u_int64_t sndbuf_getprevtotal(struct snd_dbuf *b); unsigned int sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to); u_int8_t sndbuf_zerodata(u_int32_t fmt); -void sndbuf_updateprevtotal(struct snd_dbuf *b); int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count); int sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count); int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count); #ifdef OSSV4_EXPERIMENT void sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp); #endif static inline u_int32_t snd_xbytes(u_int32_t v, u_int32_t from, u_int32_t to) { if (from == to) return (v); if (from == 0) return (0); return ((u_int64_t)v * to / from); } diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 54d8976d46bf..fda4cd2314a6 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -1,2665 +1,2660 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Portions Copyright (c) Luigi Rizzo - 1997-99 * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "feeder_if.h" int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 0, "report software-emulated formats"); int report_soft_matrix = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_matrix, CTLFLAG_RW, &report_soft_matrix, 0, "report software-emulated channel matrixing"); int chn_latency = CHN_LATENCY_DEFAULT; static int sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) err = EINVAL; else chn_latency = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_latency, "I", "buffering latency (0=low ... 10=high)"); int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; static int sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency_profile; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) err = EINVAL; else chn_latency_profile = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", "buffering latency profile (0=aggressive 1=safe)"); static int chn_timeout = CHN_TIMEOUT; static int sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_timeout; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX) err = EINVAL; else chn_timeout = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_timeout, "I", "interrupt timeout (1 - 10) seconds"); static int chn_vpc_autoreset = 1; SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RWTUN, &chn_vpc_autoreset, 0, "automatically reset channels volume to 0db"); static int chn_vol_0db_pcm = SND_VOL_0DB_PCM; static void chn_vpc_proc(int reset, int db) { struct snddev_info *d; struct pcm_channel *c; int i; bus_topo_lock(); for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_VOL_0DB, db); if (reset != 0) chn_vpc_reset(c, SND_VOL_C_PCM, 1); CHN_UNLOCK(c); } PCM_RELEASE(d); PCM_UNLOCK(d); } bus_topo_unlock(); } static int sysctl_hw_snd_vpc_0db(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_vol_0db_pcm; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (val < SND_VOL_0DB_MIN || val > SND_VOL_0DB_MAX) return (EINVAL); chn_vol_0db_pcm = val; chn_vpc_proc(0, val); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_0db, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_vpc_0db, "I", "0db relative level"); static int sysctl_hw_snd_vpc_reset(SYSCTL_HANDLER_ARGS) { int err, val; val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == 0) return (err); chn_vol_0db_pcm = SND_VOL_0DB_PCM; chn_vpc_proc(1, SND_VOL_0DB_PCM); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_reset, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_vpc_reset, "I", "reset volume on all channels"); static int chn_usefrags = 0; static int chn_syncdelay = -1; SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RWTUN, &chn_usefrags, 0, "prefer setfragments() over setblocksize()"); SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RWTUN, &chn_syncdelay, 0, "append (0-1000) millisecond trailing buffer delay on each sync"); /** * @brief Channel sync group lock * * Clients should acquire this lock @b without holding any channel locks * before touching syncgroups or the main syncgroup list. */ struct mtx snd_pcm_syncgroups_mtx; MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); /** * @brief syncgroups' master list * * Each time a channel syncgroup is created, it's added to this list. This * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. * * See SNDCTL_DSP_SYNCGROUP for more information. */ struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(snd_pcm_syncgroups); static void chn_lockinit(struct pcm_channel *c, int dir) { switch (dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); cv_init(&c->intr_cv, "pcmwr"); break; case PCMDIR_PLAY_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); cv_init(&c->intr_cv, "pcmwrv"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); cv_init(&c->intr_cv, "pcmrd"); break; case PCMDIR_REC_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); cv_init(&c->intr_cv, "pcmrdv"); break; default: panic("%s(): Invalid direction=%d", __func__, dir); break; } cv_init(&c->cv, "pcmchn"); } static void chn_lockdestroy(struct pcm_channel *c) { CHN_LOCKASSERT(c); CHN_BROADCAST(&c->cv); CHN_BROADCAST(&c->intr_cv); cv_destroy(&c->cv); cv_destroy(&c->intr_cv); snd_mtxfree(c->lock); } /** * @brief Determine channel is ready for I/O * * @retval 1 = ready for I/O * @retval 0 = not ready for I/O */ int chn_polltrigger(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; u_int delta; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MMAP) { - if (sndbuf_getprevtotal(bs) < c->lw) + if (bs->prev_total < c->lw) delta = c->lw; else - delta = sndbuf_gettotal(bs) - sndbuf_getprevtotal(bs); + delta = bs->total - bs->prev_total; } else { if (c->direction == PCMDIR_PLAY) delta = sndbuf_getfree(bs); else delta = sndbuf_getready(bs); } return ((delta < c->lw) ? 0 : 1); } static void chn_pollreset(struct pcm_channel *c) { CHN_LOCKASSERT(c); - sndbuf_updateprevtotal(c->bufsoft); + c->bufsoft->prev_total = c->bufsoft->total; } static void chn_wakeup(struct pcm_channel *c) { struct snd_dbuf *bs; struct pcm_channel *ch; CHN_LOCKASSERT(c); bs = c->bufsoft; if (CHN_EMPTY(c, children.busy)) { KNOTE_LOCKED(&bs->sel.si_note, 0); - if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) - selwakeuppri(sndbuf_getsel(bs), PRIBIO); + if (SEL_WAITING(&bs->sel) && chn_polltrigger(c)) + selwakeuppri(&bs->sel, PRIBIO); CHN_BROADCAST(&c->intr_cv); } else { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); chn_wakeup(ch); CHN_UNLOCK(ch); } } } static int chn_sleep(struct pcm_channel *c, int timeout) { int ret; CHN_LOCKASSERT(c); if (c->flags & CHN_F_DEAD) return (EINVAL); c->sleeping++; ret = cv_timedwait_sig(&c->intr_cv, c->lock, timeout); c->sleeping--; return ((c->flags & CHN_F_DEAD) ? EINVAL : ret); } /* * chn_dmaupdate() tracks the status of a dma transfer, * updating pointers. */ static unsigned int chn_dmaupdate(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; unsigned int delta, old, hwptr, amt; - KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0")); + KASSERT(b->bufsize > 0, ("bufsize == 0")); CHN_LOCKASSERT(c); - old = sndbuf_gethwptr(b); + old = b->hp; hwptr = chn_getptr(c); - delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); - sndbuf_sethwptr(b, hwptr); + delta = (b->bufsize + hwptr - old) % b->bufsize; + b->hp = hwptr; if (c->direction == PCMDIR_PLAY) { amt = min(delta, sndbuf_getready(b)); - amt -= amt % sndbuf_getalign(b); + amt -= amt % b->align; if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = min(delta, sndbuf_getfree(b)); - amt -= amt % sndbuf_getalign(b); + amt -= amt % b->align; if (amt > 0) sndbuf_acquire(b, NULL, amt); } if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) { device_printf(c->dev, "WARNING: %s DMA completion " "too fast/slow ! hwptr=%u, old=%u " "delta=%u amt=%u ready=%u free=%u\n", CHN_DIRSTR(c), hwptr, old, delta, amt, sndbuf_getready(b), sndbuf_getfree(b)); } return delta; } static void chn_wrfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int amt, want, wasfree; CHN_LOCKASSERT(c); if ((c->flags & CHN_F_MMAP) && !(c->flags & CHN_F_CLOSING)) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); wasfree = sndbuf_getfree(b); - want = min(sndbuf_getsize(b), - imax(0, sndbuf_xbytes(sndbuf_getsize(bs), bs, b) - + want = min(b->bufsize, imax(0, sndbuf_xbytes(bs->bufsize, bs, b) - sndbuf_getready(b))); amt = min(wasfree, want); if (amt > 0) sndbuf_feed(bs, b, c, c->feeder, amt); /* * Possible xruns. There should be no empty space left in buffer. */ if (sndbuf_getready(b) < want) c->xruns++; if (sndbuf_getfree(b) < wasfree) chn_wakeup(c); } static void chn_wrintr(struct pcm_channel *c) { CHN_LOCKASSERT(c); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from secondary to primary */ chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); } /* * user write routine - uiomove data into secondary buffer, trigger if necessary * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_write(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getfree(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getfreeptr(bs); - t = min(sz, sndbuf_getsize(bs) - p); + t = min(sz, bs->bufsize - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_acquire(bs, NULL, t); } ret = 0; if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) c->flags |= CHN_F_DEAD; } } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) { /** * @todo Evaluate whether EAGAIN is truly desirable. * 4Front drivers behave like this, but I'm * not sure if it at all violates the "write * should be allowed to block" model. * * The idea is that, while set with CHN_F_NOTRIGGER, * a channel isn't playing, *but* without this we * end up with "interrupt timeout / channel dead". */ ret = EAGAIN; } else { ret = chn_sleep(c, timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; device_printf(c->dev, "%s(): %s: " "play interrupt timeout, channel dead\n", __func__, c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return (ret); } /* * Feed new data from the read buffer. Can be called in the bottom half. */ static void chn_rdfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int amt; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MMAP) sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); amt = sndbuf_getfree(bs); if (amt > 0) sndbuf_feed(b, bs, c, c->feeder, amt); amt = sndbuf_getready(b); if (amt > 0) { c->xruns++; sndbuf_dispose(b, NULL, amt); } if (sndbuf_getready(bs) > 0) chn_wakeup(c); } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(struct pcm_channel *c) { CHN_LOCKASSERT(c); /* tell the driver to update the primary buffer if non-dma */ chn_trigger(c, PCMTRIG_EMLDMARD); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from primary to secondary */ chn_rdfeed(c); } /* * user read routine - trigger if necessary, uiomove data from secondary buffer * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_read(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) { c->flags |= CHN_F_DEAD; return (ret); } } ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getreadyptr(bs); - t = min(sz, sndbuf_getsize(bs) - p); + t = min(sz, bs->bufsize - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_dispose(bs, NULL, t); } ret = 0; } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) ret = EAGAIN; else { ret = chn_sleep(c, timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; device_printf(c->dev, "%s(): %s: " "record interrupt timeout, channel dead\n", __func__, c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return (ret); } void chn_intr_locked(struct pcm_channel *c) { CHN_LOCKASSERT(c); c->interrupts++; if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); } void chn_intr(struct pcm_channel *c) { if (CHN_LOCKOWNED(c)) { chn_intr_locked(c); return; } CHN_LOCK(c); chn_intr_locked(c); CHN_UNLOCK(c); } u_int32_t chn_start(struct pcm_channel *c, int force) { u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; int err; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return (EINVAL); err = 0; if (force) { i = 1; j = 0; } else { if (c->direction == PCMDIR_REC) { i = sndbuf_getfree(bs); j = (i > 0) ? 1 : sndbuf_getready(b); } else { if (sndbuf_getfree(bs) == 0) { i = 1; j = 0; } else { struct snd_dbuf *pb; pb = CHN_BUF_PARENT(c, b); i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb); - j = sndbuf_getalign(pb); + j = pb->align; } } if (snd_verbose > 3 && CHN_EMPTY(c, children)) device_printf(c->dev, "%s(): %s (%s) threshold " "i=%d j=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", i, j); } if (i >= j) { c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); if (c->flags & CHN_F_CLOSING) c->feedcount = 2; else { c->feedcount = 0; c->interrupts = 0; c->xruns = 0; } if (c->parentchannel == NULL) { if (c->direction == PCMDIR_PLAY) sndbuf_fillsilence_rl(b, - sndbuf_xbytes(sndbuf_getsize(bs), bs, b)); + sndbuf_xbytes(bs->bufsize, bs, b)); if (snd_verbose > 3) device_printf(c->dev, "%s(): %s starting! (%s/%s) " "(ready=%d force=%d i=%d j=%d " "intrtimeout=%u latency=%dms)\n", __func__, (c->flags & CHN_F_HAS_VCHAN) ? "VCHAN PARENT" : "HW", CHN_DIRSTR(c), (c->flags & CHN_F_CLOSING) ? "closing" : "running", sndbuf_getready(b), force, i, j, c->timeout, - (sndbuf_getsize(b) * 1000) / - (sndbuf_getalign(b) * sndbuf_getspd(b))); + (b->bufsize * 1000) / + (b->align * b->spd)); } err = chn_trigger(c, PCMTRIG_START); } return (err); } void chn_resetbuf(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; c->blocks = 0; sndbuf_reset(b); sndbuf_reset(bs); } /* * chn_sync waits until the space in the given channel goes above * a threshold. The threshold is checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */ int chn_sync(struct pcm_channel *c, int threshold) { struct snd_dbuf *b, *bs; int ret, count, hcount, minflush, resid, residp, syncdelay, blksz; u_int32_t cflag; CHN_LOCKASSERT(c); if (c->direction != PCMDIR_PLAY) return (EINVAL); bs = c->bufsoft; if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) || (threshold < 1 && sndbuf_getready(bs) < 1)) return (0); /* if we haven't yet started and nothing is buffered, else start*/ if (CHN_STOPPED(c)) { if (threshold > 0 || sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret != 0) return (ret); } else return (0); } b = CHN_BUF_PARENT(c, c->bufhard); minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs); syncdelay = chn_syncdelay; if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0)) - minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs); + minflush += sndbuf_xbytes(b->bufsize, b, bs); /* * Append (0-1000) millisecond trailing buffer (if needed) * for slower / high latency hardwares (notably USB audio) * to avoid audible truncation. */ if (syncdelay > 0) - minflush += (sndbuf_getalign(bs) * sndbuf_getspd(bs) * + minflush += (bs->align * bs->spd * ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000; - minflush -= minflush % sndbuf_getalign(bs); + minflush -= minflush % bs->align; if (minflush > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); minflush -= threshold; } resid = sndbuf_getready(bs); residp = resid; - blksz = sndbuf_getblksz(b); + blksz = b->blksz; if (blksz < 1) { device_printf(c->dev, "%s(): WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n", - __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b), - sndbuf_getblksz(b), sndbuf_getblkcnt(b)); - if (sndbuf_getblkcnt(b) > 0) - blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b); + __func__, b->maxsize, b->bufsize, + b->blksz, b->blkcnt); + if (b->blkcnt > 0) + blksz = b->bufsize / b->blkcnt; if (blksz < 1) blksz = 1; } count = sndbuf_xbytes(minflush + resid, bs, b) / blksz; hcount = count; ret = 0; if (snd_verbose > 3) device_printf(c->dev, "%s(): [begin] timeout=%d count=%d " "minflush=%d resid=%d\n", __func__, c->timeout, count, minflush, resid); cflag = c->flags & CHN_F_CLOSING; c->flags |= CHN_F_CLOSING; while (count > 0 && (resid > 0 || minflush > 0)) { ret = chn_sleep(c, c->timeout); if (ret == ERESTART || ret == EINTR) { c->flags |= CHN_F_ABORTING; break; } else if (ret == 0 || ret == EAGAIN) { resid = sndbuf_getready(bs); if (resid == residp) { --count; if (snd_verbose > 3) device_printf(c->dev, "%s(): [stalled] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } else if (resid < residp && count < hcount) { ++count; if (snd_verbose > 3) device_printf(c->dev, "%s((): [resume] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } if (minflush > 0 && sndbuf_getfree(bs) > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); resid = sndbuf_getready(bs); minflush -= threshold; } residp = resid; } else break; } c->flags &= ~CHN_F_CLOSING; c->flags |= cflag; if (snd_verbose > 3) device_printf(c->dev, "%s(): timeout=%d count=%d hcount=%d resid=%d residp=%d " "minflush=%d ret=%d\n", __func__, c->timeout, count, hcount, resid, residp, minflush, ret); return (0); } /* called externally, handle locking */ int chn_poll(struct pcm_channel *c, int ev, struct thread *td) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); if (!(c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED))) { ret = chn_start(c, 1); if (ret != 0) return (0); } ret = 0; if (chn_polltrigger(c)) { chn_pollreset(c); ret = ev; } else - selrecord(td, sndbuf_getsel(bs)); + selrecord(td, &bs->sel); return (ret); } /* * chn_abort terminates a running dma transfer. it may sleep up to 200ms. * it returns the number of bytes that have not been transferred. * * called from: dsp_close, dsp_ioctl, with channel locked */ int chn_abort(struct pcm_channel *c) { int missing = 0; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); if (CHN_STOPPED(c)) return 0; c->flags |= CHN_F_ABORTING; c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); missing = sndbuf_getready(bs); c->flags &= ~CHN_F_ABORTING; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close of a playback channel. * first, if there is data in the buffer, but the dma has not yet * begun, we need to start it. * next, we wait for the play buffer to drain * finally, we stop the dma. * * called from: dsp_close, not valid for record channels. */ int chn_flush(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); c->flags |= CHN_F_CLOSING; chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); c->flags &= ~CHN_F_CLOSING; return 0; } int snd_fmtvalid(uint32_t fmt, uint32_t *fmtlist) { int i; for (i = 0; fmtlist[i] != 0; i++) { if (fmt == fmtlist[i] || ((fmt & AFMT_PASSTHROUGH) && (AFMT_ENCODING(fmt) & fmtlist[i]))) return (1); } return (0); } static const struct { char *name, *alias1, *alias2; uint32_t afmt; } afmt_tab[] = { { "alaw", NULL, NULL, AFMT_A_LAW }, { "mulaw", NULL, NULL, AFMT_MU_LAW }, { "u8", "8", NULL, AFMT_U8 }, { "s8", NULL, NULL, AFMT_S8 }, { "ac3", NULL, NULL, AFMT_AC3 }, #if BYTE_ORDER == LITTLE_ENDIAN { "s16le", "s16", "16", AFMT_S16_LE }, { "s16be", NULL, NULL, AFMT_S16_BE }, { "s24le", "s24", "24", AFMT_S24_LE }, { "s24be", NULL, NULL, AFMT_S24_BE }, { "s32le", "s32", "32", AFMT_S32_LE }, { "s32be", NULL, NULL, AFMT_S32_BE }, { "f32le", "f32", NULL, AFMT_F32_LE }, { "f32be", NULL, NULL, AFMT_F32_BE }, { "u16le", "u16", NULL, AFMT_U16_LE }, { "u16be", NULL, NULL, AFMT_U16_BE }, { "u24le", "u24", NULL, AFMT_U24_LE }, { "u24be", NULL, NULL, AFMT_U24_BE }, { "u32le", "u32", NULL, AFMT_U32_LE }, { "u32be", NULL, NULL, AFMT_U32_BE }, #else { "s16le", NULL, NULL, AFMT_S16_LE }, { "s16be", "s16", "16", AFMT_S16_BE }, { "s24le", NULL, NULL, AFMT_S24_LE }, { "s24be", "s24", "24", AFMT_S24_BE }, { "s32le", NULL, NULL, AFMT_S32_LE }, { "s32be", "s32", "32", AFMT_S32_BE }, { "f32le", NULL, NULL, AFMT_F32_LE }, { "f32be", "f32", NULL, AFMT_F32_BE }, { "u16le", NULL, NULL, AFMT_U16_LE }, { "u16be", "u16", NULL, AFMT_U16_BE }, { "u24le", NULL, NULL, AFMT_U24_LE }, { "u24be", "u24", NULL, AFMT_U24_BE }, { "u32le", NULL, NULL, AFMT_U32_LE }, { "u32be", "u32", NULL, AFMT_U32_BE }, #endif { NULL, NULL, NULL, 0 } }; uint32_t snd_str2afmt(const char *req) { int ext; int ch; int i; char b1[8]; char b2[8]; memset(b1, 0, sizeof(b1)); memset(b2, 0, sizeof(b2)); i = sscanf(req, "%5[^:]:%6s", b1, b2); if (i == 1) { if (strlen(req) != strlen(b1)) return (0); strlcpy(b2, "2.0", sizeof(b2)); } else if (i == 2) { if (strlen(req) != (strlen(b1) + 1 + strlen(b2))) return (0); } else return (0); i = sscanf(b2, "%d.%d", &ch, &ext); if (i == 0) { if (strcasecmp(b2, "mono") == 0) { ch = 1; ext = 0; } else if (strcasecmp(b2, "stereo") == 0) { ch = 2; ext = 0; } else if (strcasecmp(b2, "quad") == 0) { ch = 4; ext = 0; } else return (0); } else if (i == 1) { if (ch < 1 || ch > AFMT_CHANNEL_MAX) return (0); ext = 0; } else if (i == 2) { if (ext < 0 || ext > AFMT_EXTCHANNEL_MAX) return (0); if (ch < 1 || (ch + ext) > AFMT_CHANNEL_MAX) return (0); } else return (0); for (i = 0; afmt_tab[i].name != NULL; i++) { if (strcasecmp(afmt_tab[i].name, b1) != 0) { if (afmt_tab[i].alias1 == NULL) continue; if (strcasecmp(afmt_tab[i].alias1, b1) != 0) { if (afmt_tab[i].alias2 == NULL) continue; if (strcasecmp(afmt_tab[i].alias2, b1) != 0) continue; } } /* found a match */ return (SND_FORMAT(afmt_tab[i].afmt, ch + ext, ext)); } /* not a valid format */ return (0); } uint32_t snd_afmt2str(uint32_t afmt, char *buf, size_t len) { uint32_t enc; uint32_t ext; uint32_t ch; int i; if (buf == NULL || len < AFMTSTR_LEN) return (0); memset(buf, 0, len); enc = AFMT_ENCODING(afmt); ch = AFMT_CHANNEL(afmt); ext = AFMT_EXTCHANNEL(afmt); /* check there is at least one channel */ if (ch <= ext) return (0); for (i = 0; afmt_tab[i].name != NULL; i++) { if (enc != afmt_tab[i].afmt) continue; /* found a match */ snprintf(buf, len, "%s:%d.%d", afmt_tab[i].name, ch - ext, ext); return (SND_FORMAT(enc, ch, ext)); } return (0); } int chn_reset(struct pcm_channel *c, uint32_t fmt, uint32_t spd) { int r; CHN_LOCKASSERT(c); c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; c->timeout = 1; c->xruns = 0; c->flags |= (pcm_getflags(c->dev) & SD_F_BITPERFECT) ? CHN_F_BITPERFECT : 0; r = CHANNEL_RESET(c->methods, c->devinfo); if (r == 0 && fmt != 0 && spd != 0) { r = chn_setparam(c, fmt, spd); fmt = 0; spd = 0; } if (r == 0 && fmt != 0) r = chn_setformat(c, fmt); if (r == 0 && spd != 0) r = chn_setspeed(c, spd); if (r == 0) r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); } return r; } static struct unrhdr * chn_getunr(struct snddev_info *d, int type) { switch (type) { case PCMDIR_PLAY: return (d->p_unr); case PCMDIR_PLAY_VIRTUAL: return (d->vp_unr); case PCMDIR_REC: return (d->r_unr); case PCMDIR_REC_VIRTUAL: return (d->vr_unr); default: __assert_unreachable(); } } char * chn_mkname(char *buf, size_t len, struct pcm_channel *c) { const char *str; KASSERT(buf != NULL && len != 0, ("%s(): bogus buf=%p len=%zu", __func__, buf, len)); switch (c->type) { case PCMDIR_PLAY: str = "play"; break; case PCMDIR_PLAY_VIRTUAL: str = "virtual_play"; break; case PCMDIR_REC: str = "record"; break; case PCMDIR_REC_VIRTUAL: str = "virtual_record"; break; default: __assert_unreachable(); } snprintf(buf, len, "dsp%d.%s.%d", device_get_unit(c->dev), str, c->unit); return (buf); } struct pcm_channel * chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) { struct pcm_channel *c; struct feeder_class *fc; struct snd_dbuf *b, *bs; char buf[CHN_NAMELEN]; int err, i, direction, *vchanrate, *vchanformat; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); switch (dir) { case PCMDIR_PLAY: d->playcount++; /* FALLTHROUGH */ case PCMDIR_PLAY_VIRTUAL: if (dir == PCMDIR_PLAY_VIRTUAL) d->pvchancount++; direction = PCMDIR_PLAY; vchanrate = &d->pvchanrate; vchanformat = &d->pvchanformat; break; case PCMDIR_REC: d->reccount++; /* FALLTHROUGH */ case PCMDIR_REC_VIRTUAL: if (dir == PCMDIR_REC_VIRTUAL) d->rvchancount++; direction = PCMDIR_REC; vchanrate = &d->rvchanrate; vchanformat = &d->rvchanformat; break; default: device_printf(d->dev, "%s(): invalid channel direction: %d\n", __func__, dir); return (NULL); } PCM_UNLOCK(d); b = NULL; bs = NULL; c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); c->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); chn_lockinit(c, dir); CHN_INIT(c, children); CHN_INIT(c, children.busy); c->direction = direction; c->type = dir; c->unit = alloc_unr(chn_getunr(d, c->type)); c->format = SND_FORMAT(AFMT_S16_LE, 2, 0); c->speed = 48000; c->pid = -1; c->latency = -1; c->timeout = 1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); c->parentsnddev = d; c->parentchannel = parent; c->dev = d->dev; c->trigger = PCMTRIG_STOP; strlcpy(c->name, chn_mkname(buf, sizeof(buf), c), sizeof(c->name)); c->matrix = *feeder_matrix_id_map(SND_CHN_MATRIX_1_0); c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; for (i = 0; i < SND_CHN_T_MAX; i++) c->volume[SND_VOL_C_MASTER][i] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; CHN_LOCK(c); chn_vpc_reset(c, SND_VOL_C_PCM, 1); CHN_UNLOCK(c); fc = feeder_getclass(NULL); if (fc == NULL) { device_printf(d->dev, "%s(): failed to get feeder class\n", __func__); goto fail; } if (feeder_add(c, fc, NULL)) { device_printf(d->dev, "%s(): failed to add feeder\n", __func__); goto fail; } b = sndbuf_create(c, "primary"); bs = sndbuf_create(c, "secondary"); if (b == NULL || bs == NULL) { device_printf(d->dev, "%s(): failed to create %s buffer\n", __func__, b == NULL ? "hardware" : "software"); goto fail; } c->bufhard = b; c->bufsoft = bs; knlist_init_mtx(&bs->sel.si_note, c->lock); c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, direction); if (c->devinfo == NULL) { device_printf(d->dev, "%s(): CHANNEL_INIT() failed\n", __func__); goto fail; } - if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) { + if (b->bufsize == 0 && ((c->flags & CHN_F_VIRTUAL) == 0)) { device_printf(d->dev, "%s(): hardware buffer's size is 0\n", __func__); goto fail; } sndbuf_setfmt(b, c->format); sndbuf_setspd(b, c->speed); sndbuf_setfmt(bs, c->format); sndbuf_setspd(bs, c->speed); sndbuf_setup(bs, NULL, 0); /** * @todo Should this be moved somewhere else? The primary buffer * is allocated by the driver or via DMA map setup, and tmpbuf * seems to only come into existence in sndbuf_resize(). */ if (c->direction == PCMDIR_PLAY) { - bs->sl = sndbuf_getmaxsize(bs); + bs->sl = bs->maxsize; bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_WAITOK); } if ((c->flags & CHN_F_VIRTUAL) == 0) { CHN_LOCK(c); err = chn_reset(c, c->format, c->speed); CHN_UNLOCK(c); if (err != 0) goto fail; } PCM_LOCK(d); CHN_INSERT_SORT_ASCEND(d, c, channels.pcm); if ((c->flags & CHN_F_VIRTUAL) == 0) { CHN_INSERT_SORT_ASCEND(d, c, channels.pcm.primary); /* Initialize the *vchanrate/vchanformat parameters. */ - *vchanrate = sndbuf_getspd(c->bufsoft); - *vchanformat = sndbuf_getfmt(c->bufsoft); + *vchanrate = c->bufsoft->spd; + *vchanformat = c->bufsoft->fmt; } return (c); fail: chn_kill(c); PCM_LOCK(d); return (NULL); } void chn_kill(struct pcm_channel *c) { struct snddev_info *d = c->parentsnddev; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; PCM_BUSYASSERT(c->parentsnddev); PCM_LOCK(d); CHN_REMOVE(d, c, channels.pcm); if ((c->flags & CHN_F_VIRTUAL) == 0) CHN_REMOVE(d, c, channels.pcm.primary); switch (c->type) { case PCMDIR_PLAY: d->playcount--; break; case PCMDIR_PLAY_VIRTUAL: d->pvchancount--; break; case PCMDIR_REC: d->reccount--; break; case PCMDIR_REC_VIRTUAL: d->rvchancount--; break; default: __assert_unreachable(); } PCM_UNLOCK(d); if (CHN_STARTED(c)) { CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); CHN_UNLOCK(c); } free_unr(chn_getunr(d, c->type), c->unit); feeder_remove(c); if (c->devinfo) CHANNEL_FREE(c->methods, c->devinfo); if (bs) { knlist_clear(&bs->sel.si_note, 0); knlist_destroy(&bs->sel.si_note); sndbuf_destroy(bs); } if (b) sndbuf_destroy(b); CHN_LOCK(c); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); kobj_delete(c->methods, M_DEVBUF); free(c, M_DEVBUF); } void chn_shutdown(struct pcm_channel *c) { CHN_LOCKASSERT(c); chn_wakeup(c); c->flags |= CHN_F_DEAD; } /* release a locked channel and unlock it */ int chn_release(struct pcm_channel *c) { PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); c->flags &= ~CHN_F_BUSY; c->pid = -1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); CHN_UNLOCK(c); return (0); } int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right, int center) { int i, ret; ret = 0; for (i = 0; i < SND_CHN_T_MAX; i++) { if ((1 << i) & SND_CHN_LEFT_MASK) ret |= chn_setvolume_matrix(c, vc, i, left); else if ((1 << i) & SND_CHN_RIGHT_MASK) ret |= chn_setvolume_matrix(c, vc, i, right) << 8; else ret |= chn_setvolume_matrix(c, vc, i, center) << 16; } return (ret); } int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vc == SND_VOL_C_MASTER || (vc & 1)) && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB || (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)), ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d", __func__, c, vc, vt, val)); CHN_LOCKASSERT(c); if (val < 0) val = 0; if (val > 100) val = 100; c->volume[vc][vt] = val; /* * Do relative calculation here and store it into class + 1 * to ease the job of feeder_volume. */ if (vc == SND_VOL_C_MASTER) { for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; vc += SND_VOL_C_STEP) c->volume[SND_VOL_C_VAL(vc)][vt] = SND_VOL_CALC_VAL(c->volume, vc, vt); } else if (vc & 1) { if (vt == SND_CHN_T_VOL_0DB) for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) { c->volume[SND_VOL_C_VAL(vc)][i] = SND_VOL_CALC_VAL(c->volume, vc, i); } else c->volume[SND_VOL_C_VAL(vc)][vt] = SND_VOL_CALC_VAL(c->volume, vc, vt); } return (val); } int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt) { KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid volume matrix c=%p vc=%d vt=%d", __func__, c, vc, vt)); CHN_LOCKASSERT(c); return (c->volume[vc][vt]); } int chn_setmute_multi(struct pcm_channel *c, int vc, int mute) { int i, ret; ret = 0; for (i = 0; i < SND_CHN_T_MAX; i++) { if ((1 << i) & SND_CHN_LEFT_MASK) ret |= chn_setmute_matrix(c, vc, i, mute); else if ((1 << i) & SND_CHN_RIGHT_MASK) ret |= chn_setmute_matrix(c, vc, i, mute) << 8; else ret |= chn_setmute_matrix(c, vc, i, mute) << 16; } return (ret); } int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vc == SND_VOL_C_MASTER || (vc & 1)) && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d", __func__, c, vc, vt, mute)); CHN_LOCKASSERT(c); mute = (mute != 0); c->muted[vc][vt] = mute; /* * Do relative calculation here and store it into class + 1 * to ease the job of feeder_volume. */ if (vc == SND_VOL_C_MASTER) { for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; vc += SND_VOL_C_STEP) c->muted[SND_VOL_C_VAL(vc)][vt] = mute; } else if (vc & 1) { if (vt == SND_CHN_T_VOL_0DB) { for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) { c->muted[SND_VOL_C_VAL(vc)][i] = mute; } } else { c->muted[SND_VOL_C_VAL(vc)][vt] = mute; } } return (mute); } int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt) { KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid mute matrix c=%p vc=%d vt=%d", __func__, c, vc, vt)); CHN_LOCKASSERT(c); return (c->muted[vc][vt]); } struct pcmchan_matrix * chn_getmatrix(struct pcm_channel *c) { KASSERT(c != NULL, ("%s(): NULL channel", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (NULL); return (&c->matrix); } int chn_setmatrix(struct pcm_channel *c, struct pcmchan_matrix *m) { KASSERT(c != NULL && m != NULL, ("%s(): NULL channel or matrix", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); c->matrix = *m; c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; return (chn_setformat(c, SND_FORMAT(c->format, m->channels, m->ext))); } /* * XXX chn_oss_* exists for the sake of compatibility. */ int chn_oss_getorder(struct pcm_channel *c, unsigned long long *map) { KASSERT(c != NULL && map != NULL, ("%s(): NULL channel or map", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); return (feeder_matrix_oss_get_channel_order(&c->matrix, map)); } int chn_oss_setorder(struct pcm_channel *c, unsigned long long *map) { struct pcmchan_matrix m; int ret; KASSERT(c != NULL && map != NULL, ("%s(): NULL channel or map", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); m = c->matrix; ret = feeder_matrix_oss_set_channel_order(&m, map); if (ret != 0) return (ret); return (chn_setmatrix(c, &m)); } #define SND_CHN_OSS_FRONT (SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR) #define SND_CHN_OSS_SURR (SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR) #define SND_CHN_OSS_CENTER_LFE (SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF) #define SND_CHN_OSS_REAR (SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR) int chn_oss_getmask(struct pcm_channel *c, uint32_t *retmask) { struct pcmchan_matrix *m; struct pcmchan_caps *caps; uint32_t i, format; KASSERT(c != NULL && retmask != NULL, ("%s(): NULL channel or retmask", __func__)); CHN_LOCKASSERT(c); caps = chn_getcaps(c); if (caps == NULL || caps->fmtlist == NULL) return (ENODEV); for (i = 0; caps->fmtlist[i] != 0; i++) { format = caps->fmtlist[i]; if (!(format & AFMT_CONVERTIBLE)) { *retmask |= DSP_BIND_SPDIF; continue; } m = CHANNEL_GETMATRIX(c->methods, c->devinfo, format); if (m == NULL) continue; if (m->mask & SND_CHN_OSS_FRONT) *retmask |= DSP_BIND_FRONT; if (m->mask & SND_CHN_OSS_SURR) *retmask |= DSP_BIND_SURR; if (m->mask & SND_CHN_OSS_CENTER_LFE) *retmask |= DSP_BIND_CENTER_LFE; if (m->mask & SND_CHN_OSS_REAR) *retmask |= DSP_BIND_REAR; } /* report software-supported binding mask */ if (!CHN_BITPERFECT(c) && report_soft_matrix) *retmask |= DSP_BIND_FRONT | DSP_BIND_SURR | DSP_BIND_CENTER_LFE | DSP_BIND_REAR; return (0); } void chn_vpc_reset(struct pcm_channel *c, int vc, int force) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_BEGIN && vc <= SND_VOL_C_END, ("%s(): invalid reset c=%p vc=%d", __func__, c, vc)); CHN_LOCKASSERT(c); if (force == 0 && chn_vpc_autoreset == 0) return; for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) CHN_SETVOLUME(c, vc, i, c->volume[vc][SND_CHN_T_VOL_0DB]); } static u_int32_t round_pow2(u_int32_t v) { u_int32_t ret; if (v < 2) v = 2; ret = 0; while (v >> ret) ret++; ret = 1 << (ret - 1); while (ret < v) ret <<= 1; return ret; } static u_int32_t round_blksz(u_int32_t v, int round) { u_int32_t ret, tmp; if (round < 1) round = 1; ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) ret >>= 1; tmp = ret - (ret % round); while (tmp < 16 || tmp < round) { ret <<= 1; tmp = ret - (ret % round); } return ret; } /* * 4Front call it DSP Policy, while we call it "Latency Profile". The idea * is to keep 2nd buffer short so that it doesn't cause long queue during * buffer transfer. * * Latency reference table for 48khz stereo 16bit: (PLAY) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 2 | 64 | 128 | * +---------+------------+-----------+------------+ * | 1 | 4 | 128 | 512 | * +---------+------------+-----------+------------+ * | 2 | 8 | 512 | 4096 | * +---------+------------+-----------+------------+ * | 3 | 16 | 512 | 8192 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Recording need a different reference table. All we care is * gobbling up everything within reasonable buffering threshold. * * Latency reference table for 48khz stereo 16bit: (REC) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 512 | 32 | 16384 | * +---------+------------+-----------+------------+ * | 1 | 256 | 64 | 16384 | * +---------+------------+-----------+------------+ * | 2 | 128 | 128 | 16384 | * +---------+------------+-----------+------------+ * | 3 | 64 | 256 | 16384 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Calculations for other data rate are entirely based on these reference * tables. For normal operation, Latency 5 seems give the best, well * balanced performance for typical workload. Anything below 5 will * eat up CPU to keep up with increasing context switches because of * shorter buffer space and usually require the application to handle it * aggressively through possibly real time programming technique. * */ #define CHN_LATENCY_PBLKCNT_REF \ {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_PBUFSZ_REF \ {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_RBLKCNT_REF \ {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_RBUFSZ_REF \ {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ static int chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, u_int32_t max, int *rblksz, int *rblkcnt) { static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBLKCNT_REF; static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBUFSZ_REF; static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBLKCNT_REF; static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBUFSZ_REF; u_int32_t bufsz; int lprofile, blksz, blkcnt; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || bps < 1 || datarate < 1 || !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { if (rblksz != NULL) *rblksz = CHN_2NDBUFMAXSIZE >> 1; if (rblkcnt != NULL) *rblkcnt = 2; printf("%s(): FAILED dir=%d latency=%d bps=%d " "datarate=%u max=%u\n", __func__, dir, latency, bps, datarate, max); return CHN_2NDBUFMAXSIZE; } lprofile = chn_latency_profile; if (dir == PCMDIR_PLAY) { blkcnt = pblkcnts[lprofile][latency]; bufsz = pbufszs[lprofile][latency]; } else { blkcnt = rblkcnts[lprofile][latency]; bufsz = rbufszs[lprofile][latency]; } bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate)); if (bufsz > max) bufsz = max; blksz = round_blksz(bufsz >> blkcnt, bps); if (rblksz != NULL) *rblksz = blksz; if (rblkcnt != NULL) *rblkcnt = 1 << blkcnt; return blksz << blkcnt; } static int chn_resizebuf(struct pcm_channel *c, int latency, int blkcnt, int blksz) { struct snd_dbuf *b, *bs, *pb; int sblksz, sblkcnt, hblksz, hblkcnt, limit = 0, nsblksz, nsblkcnt; int ret; CHN_LOCKASSERT(c); if ((c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED)) || !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) return EINVAL; if (latency == -1) { c->latency = -1; latency = chn_latency; } else if (latency == -2) { latency = c->latency; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) latency = chn_latency; } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) return EINVAL; else { c->latency = latency; } bs = c->bufsoft; b = c->bufhard; if (!(blksz == 0 || blkcnt == -1) && - (blksz < 16 || blksz < sndbuf_getalign(bs) || blkcnt < 2 || + (blksz < 16 || blksz < bs->align || blkcnt < 2 || (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) return EINVAL; - chn_calclatency(c->direction, latency, sndbuf_getalign(bs), - sndbuf_getalign(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, + chn_calclatency(c->direction, latency, bs->align, + bs->align * bs->spd, CHN_2NDBUFMAXSIZE, &sblksz, &sblkcnt); if (blksz == 0 || blkcnt == -1) { if (blkcnt == -1) c->flags &= ~CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { - blksz = sndbuf_getblksz(bs); - blkcnt = sndbuf_getblkcnt(bs); + blksz = bs->blksz; + blkcnt = bs->blkcnt; } } else c->flags |= CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { /* * The application has requested their own blksz/blkcnt. * Just obey with it, and let them toast alone. We can * clamp it to the nearest latency profile, but that would * defeat the purpose of having custom control. The least * we can do is round it to the nearest ^2 and align it. */ - sblksz = round_blksz(blksz, sndbuf_getalign(bs)); + sblksz = round_blksz(blksz, bs->align); sblkcnt = round_pow2(blkcnt); } if (c->parentchannel != NULL) { pb = c->parentchannel->bufsoft; CHN_UNLOCK(c); CHN_LOCK(c->parentchannel); chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); CHN_UNLOCK(c->parentchannel); CHN_LOCK(c); if (c->direction == PCMDIR_PLAY) { limit = (pb != NULL) ? - sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; + sndbuf_xbytes(pb->bufsize, pb, bs) : 0; } else { limit = (pb != NULL) ? - sndbuf_xbytes(sndbuf_getblksz(pb), pb, bs) * 2 : 0; + sndbuf_xbytes(pb->blksz, pb, bs) * 2 : 0; } } else { hblkcnt = 2; if (c->flags & CHN_F_HAS_SIZE) { hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), - sndbuf_getalign(b)); - hblkcnt = round_pow2(sndbuf_getblkcnt(bs)); + b->align); + hblkcnt = round_pow2(bs->blkcnt); } else chn_calclatency(c->direction, latency, - sndbuf_getalign(b), - sndbuf_getalign(b) * sndbuf_getspd(b), + b->align, b->align * b->spd, CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); - if ((hblksz << 1) > sndbuf_getmaxsize(b)) - hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1, - sndbuf_getalign(b)); + if ((hblksz << 1) > b->maxsize) + hblksz = round_blksz(b->maxsize >> 1, b->align); - while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) { + while ((hblksz * hblkcnt) > b->maxsize) { if (hblkcnt < 4) hblksz >>= 1; else hblkcnt >>= 1; } - hblksz -= hblksz % sndbuf_getalign(b); + hblksz -= hblksz % b->align; CHN_UNLOCK(c); if (chn_usefrags == 0 || CHANNEL_SETFRAGMENTS(c->methods, c->devinfo, hblksz, hblkcnt) != 0) - sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, - c->devinfo, hblksz)); + b->blksz = CHANNEL_SETBLOCKSIZE(c->methods, + c->devinfo, hblksz); CHN_LOCK(c); if (!CHN_EMPTY(c, children)) { nsblksz = round_blksz( - sndbuf_xbytes(sndbuf_getblksz(b), b, bs), - sndbuf_getalign(bs)); - nsblkcnt = sndbuf_getblkcnt(b); + sndbuf_xbytes(b->blksz, b, bs), bs->align); + nsblkcnt = b->blkcnt; if (c->direction == PCMDIR_PLAY) { do { nsblkcnt--; } while (nsblkcnt >= 2 && nsblksz * nsblkcnt >= sblksz * sblkcnt); nsblkcnt++; } sblksz = nsblksz; sblkcnt = nsblkcnt; limit = 0; } else - limit = sndbuf_xbytes(sndbuf_getblksz(b), b, bs) * 2; + limit = sndbuf_xbytes(b->blksz, b, bs) * 2; } if (limit > CHN_2NDBUFMAXSIZE) limit = CHN_2NDBUFMAXSIZE; while ((sblksz * sblkcnt) < limit) sblkcnt <<= 1; while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { if (sblkcnt < 4) sblksz >>= 1; else sblkcnt >>= 1; } - sblksz -= sblksz % sndbuf_getalign(bs); + sblksz -= sblksz % bs->align; - if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || - sndbuf_getsize(bs) != (sblkcnt * sblksz)) { + if (bs->blkcnt != sblkcnt || bs->blksz != sblksz || + bs->bufsize != (sblkcnt * sblksz)) { ret = sndbuf_remalloc(bs, sblkcnt, sblksz); if (ret != 0) { device_printf(c->dev, "%s(): Failed: %d %d\n", __func__, sblkcnt, sblksz); return ret; } } /* * Interrupt timeout */ - c->timeout = ((u_int64_t)hz * sndbuf_getsize(bs)) / - ((u_int64_t)sndbuf_getspd(bs) * sndbuf_getalign(bs)); + c->timeout = ((u_int64_t)hz * bs->bufsize) / + ((u_int64_t)bs->spd * bs->align); if (c->parentchannel != NULL) c->timeout = min(c->timeout, c->parentchannel->timeout); if (c->timeout < 1) c->timeout = 1; /* * OSSv4 docs: "By default OSS will set the low water level equal * to the fragment size which is optimal in most cases." */ - c->lw = sndbuf_getblksz(bs); + c->lw = bs->blksz; chn_resetbuf(c); if (snd_verbose > 3) device_printf(c->dev, "%s(): %s (%s) timeout=%u " "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", c->timeout, - sndbuf_getsize(b), sndbuf_getblksz(b), - sndbuf_getblkcnt(b), - sndbuf_getsize(bs), sndbuf_getblksz(bs), - sndbuf_getblkcnt(bs), limit); + b->bufsize, b->blksz, + b->blkcnt, + bs->bufsize, bs->blksz, + bs->blkcnt, limit); return 0; } int chn_setlatency(struct pcm_channel *c, int latency) { CHN_LOCKASSERT(c); /* Destroy blksz/blkcnt, enforce latency profile. */ return chn_resizebuf(c, latency, -1, 0); } int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) { CHN_LOCKASSERT(c); /* Destroy latency profile, enforce blksz/blkcnt */ return chn_resizebuf(c, -1, blkcnt, blksz); } int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed) { struct pcmchan_caps *caps; uint32_t hwspeed, delta; int ret; CHN_LOCKASSERT(c); if (speed < 1 || format == 0 || CHN_STARTED(c)) return (EINVAL); c->format = format; c->speed = speed; caps = chn_getcaps(c); hwspeed = speed; RANGE(hwspeed, caps->minspeed, caps->maxspeed); sndbuf_setspd(c->bufhard, CHANNEL_SETSPEED(c->methods, c->devinfo, hwspeed)); - hwspeed = sndbuf_getspd(c->bufhard); + hwspeed = c->bufhard->spd; delta = (hwspeed > speed) ? (hwspeed - speed) : (speed - hwspeed); if (delta <= feeder_rate_round) c->speed = hwspeed; ret = feeder_chain(c); if (ret == 0) - ret = CHANNEL_SETFORMAT(c->methods, c->devinfo, - sndbuf_getfmt(c->bufhard)); + ret = CHANNEL_SETFORMAT(c->methods, c->devinfo, c->bufhard->fmt); if (ret == 0) ret = chn_resizebuf(c, -2, 0, 0); return (ret); } int chn_setspeed(struct pcm_channel *c, uint32_t speed) { uint32_t oldformat, oldspeed; int ret; oldformat = c->format; oldspeed = c->speed; if (c->speed == speed) return (0); ret = chn_setparam(c, c->format, speed); if (ret != 0) { if (snd_verbose > 3) device_printf(c->dev, "%s(): Setting speed %d failed, " "falling back to %d\n", __func__, speed, oldspeed); chn_setparam(c, oldformat, oldspeed); } return (ret); } int chn_setformat(struct pcm_channel *c, uint32_t format) { uint32_t oldformat, oldspeed; int ret; /* XXX force stereo */ if ((format & AFMT_PASSTHROUGH) && AFMT_CHANNEL(format) < 2) { format = SND_FORMAT(format, AFMT_PASSTHROUGH_CHANNEL, AFMT_PASSTHROUGH_EXTCHANNEL); } oldformat = c->format; oldspeed = c->speed; if (c->format == format) return (0); ret = chn_setparam(c, format, c->speed); if (ret != 0) { if (snd_verbose > 3) device_printf(c->dev, "%s(): Format change 0x%08x failed, " "falling back to 0x%08x\n", __func__, format, oldformat); chn_setparam(c, oldformat, oldspeed); } return (ret); } void chn_syncstate(struct pcm_channel *c) { struct snddev_info *d; struct snd_mixer *m; d = (c != NULL) ? c->parentsnddev : NULL; m = (d != NULL && d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; if (d == NULL || m == NULL) return; CHN_LOCKASSERT(c); if (c->feederflags & (1 << FEEDER_VOLUME)) { uint32_t parent; int vol, pvol, left, right, center; if (c->direction == PCMDIR_PLAY && (d->flags & SD_F_SOFTPCMVOL)) { /* CHN_UNLOCK(c); */ vol = mix_get(m, SOUND_MIXER_PCM); parent = mix_getparent(m, SOUND_MIXER_PCM); if (parent != SOUND_MIXER_NONE) pvol = mix_get(m, parent); else pvol = 100 | (100 << 8); /* CHN_LOCK(c); */ } else { vol = 100 | (100 << 8); pvol = vol; } if (vol == -1) { device_printf(c->dev, "Soft PCM Volume: Failed to read pcm " "default value\n"); vol = 100 | (100 << 8); } if (pvol == -1) { device_printf(c->dev, "Soft PCM Volume: Failed to read parent " "default value\n"); pvol = 100 | (100 << 8); } left = ((vol & 0x7f) * (pvol & 0x7f)) / 100; right = (((vol >> 8) & 0x7f) * ((pvol >> 8) & 0x7f)) / 100; center = (left + right) >> 1; chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right, center); } if (c->feederflags & (1 << FEEDER_EQ)) { struct pcm_feeder *f; int treble, bass, state; /* CHN_UNLOCK(c); */ treble = mix_get(m, SOUND_MIXER_TREBLE); bass = mix_get(m, SOUND_MIXER_BASS); /* CHN_LOCK(c); */ if (treble == -1) treble = 50; else treble = ((treble & 0x7f) + ((treble >> 8) & 0x7f)) >> 1; if (bass == -1) bass = 50; else bass = ((bass & 0x7f) + ((bass >> 8) & 0x7f)) >> 1; f = feeder_find(c, FEEDER_EQ); if (f != NULL) { if (FEEDER_SET(f, FEEDEQ_TREBLE, treble) != 0) device_printf(c->dev, "EQ: Failed to set treble -- %d\n", treble); if (FEEDER_SET(f, FEEDEQ_BASS, bass) != 0) device_printf(c->dev, "EQ: Failed to set bass -- %d\n", bass); if (FEEDER_SET(f, FEEDEQ_PREAMP, d->eqpreamp) != 0) device_printf(c->dev, "EQ: Failed to set preamp -- %d\n", d->eqpreamp); if (d->flags & SD_F_EQ_BYPASSED) state = FEEDEQ_BYPASS; else if (d->flags & SD_F_EQ_ENABLED) state = FEEDEQ_ENABLE; else state = FEEDEQ_DISABLE; if (FEEDER_SET(f, FEEDEQ_STATE, state) != 0) device_printf(c->dev, "EQ: Failed to set state -- %d\n", state); } } } int chn_trigger(struct pcm_channel *c, int go) { struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); if (!PCMTRIG_COMMON(go)) return (CHANNEL_TRIGGER(c->methods, c->devinfo, go)); if (go == c->trigger) return (0); if (snd_verbose > 3) { device_printf(c->dev, "%s() %s: calling go=0x%08x , " "prev=0x%08x\n", __func__, c->name, go, c->trigger); } c->trigger = go; ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); if (ret != 0) return (ret); CHN_UNLOCK(c); PCM_LOCK(d); CHN_LOCK(c); /* * Do nothing if another thread set a different trigger while we had * dropped the mutex. */ if (go != c->trigger) { PCM_UNLOCK(d); return (0); } /* * Use the SAFE variants to prevent inserting/removing an already * existing/missing element. */ switch (go) { case PCMTRIG_START: CHN_INSERT_HEAD_SAFE(d, c, channels.pcm.busy); PCM_UNLOCK(d); chn_syncstate(c); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: CHN_REMOVE(d, c, channels.pcm.busy); PCM_UNLOCK(d); break; default: PCM_UNLOCK(d); break; } return (0); } /** * @brief Queries sound driver for sample-aligned hardware buffer pointer index * * This function obtains the hardware pointer location, then aligns it to * the current bytes-per-sample value before returning. (E.g., a channel * running in 16 bit stereo mode would require 4 bytes per sample, so a * hwptr value ranging from 32-35 would be returned as 32.) * * @param c PCM channel context * @returns sample-aligned hardware buffer pointer index */ int chn_getptr(struct pcm_channel *c) { int hwptr; CHN_LOCKASSERT(c); hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; - return (hwptr - (hwptr % sndbuf_getalign(c->bufhard))); + return (hwptr - (hwptr % c->bufhard->align)); } struct pcmchan_caps * chn_getcaps(struct pcm_channel *c) { CHN_LOCKASSERT(c); return CHANNEL_GETCAPS(c->methods, c->devinfo); } u_int32_t chn_getformats(struct pcm_channel *c) { u_int32_t *fmtlist, fmts; int i; fmtlist = chn_getcaps(c)->fmtlist; fmts = 0; for (i = 0; fmtlist[i]; i++) fmts |= fmtlist[i]; /* report software-supported formats */ if (!CHN_BITPERFECT(c) && report_soft_formats) fmts |= AFMT_CONVERTIBLE; return (AFMT_ENCODING(fmts)); } int chn_notify(struct pcm_channel *c, u_int32_t flags) { struct pcm_channel *ch; struct pcmchan_caps *caps; uint32_t bestformat, bestspeed, besthwformat, *vchanformat, *vchanrate; uint32_t vpflags; int dirty, err, run, nrun; CHN_LOCKASSERT(c); if (CHN_EMPTY(c, children)) return (0); err = 0; /* * If the hwchan is running, we can't change its rate, format or * blocksize */ run = (CHN_STARTED(c)) ? 1 : 0; if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { /* * XXX I'll make good use of this someday. * However this is currently being superseded by * the availability of CHN_F_VCHAN_DYNAMIC. */ } if (flags & CHN_N_FORMAT) { /* * XXX I'll make good use of this someday. * However this is currently being superseded by * the availability of CHN_F_VCHAN_DYNAMIC. */ } if (flags & CHN_N_VOLUME) { /* * XXX I'll make good use of this someday, though * soft volume control is currently pretty much * integrated. */ } if (flags & CHN_N_BLOCKSIZE) { /* * Set to default latency profile */ chn_setlatency(c, chn_latency); } if ((flags & CHN_N_TRIGGER) && !(c->flags & CHN_F_VCHAN_DYNAMIC)) { nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) err = chn_start(c, 1); if (!nrun && run) chn_abort(c); flags &= ~CHN_N_TRIGGER; } if (flags & CHN_N_TRIGGER) { if (c->direction == PCMDIR_PLAY) { vchanformat = &c->parentsnddev->pvchanformat; vchanrate = &c->parentsnddev->pvchanrate; } else { vchanformat = &c->parentsnddev->rvchanformat; vchanrate = &c->parentsnddev->rvchanrate; } /* Dynamic Virtual Channel */ if (!(c->flags & CHN_F_VCHAN_ADAPTIVE)) { bestformat = *vchanformat; bestspeed = *vchanrate; } else { bestformat = 0; bestspeed = 0; } besthwformat = 0; nrun = 0; caps = chn_getcaps(c); dirty = 0; vpflags = 0; CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if ((ch->format & AFMT_PASSTHROUGH) && snd_fmtvalid(ch->format, caps->fmtlist)) { bestformat = ch->format; bestspeed = ch->speed; CHN_UNLOCK(ch); vpflags = CHN_F_PASSTHROUGH; nrun++; break; } if ((ch->flags & CHN_F_EXCLUSIVE) && vpflags == 0) { if (c->flags & CHN_F_VCHAN_ADAPTIVE) { bestspeed = ch->speed; RANGE(bestspeed, caps->minspeed, caps->maxspeed); besthwformat = snd_fmtbest(ch->format, caps->fmtlist); if (besthwformat != 0) bestformat = besthwformat; } CHN_UNLOCK(ch); vpflags = CHN_F_EXCLUSIVE; nrun++; continue; } if (!(c->flags & CHN_F_VCHAN_ADAPTIVE) || vpflags != 0) { CHN_UNLOCK(ch); nrun++; continue; } if (ch->speed > bestspeed) { bestspeed = ch->speed; RANGE(bestspeed, caps->minspeed, caps->maxspeed); } besthwformat = snd_fmtbest(ch->format, caps->fmtlist); if (!(besthwformat & AFMT_VCHAN)) { CHN_UNLOCK(ch); nrun++; continue; } if (AFMT_CHANNEL(besthwformat) > AFMT_CHANNEL(bestformat)) bestformat = besthwformat; else if (AFMT_CHANNEL(besthwformat) == AFMT_CHANNEL(bestformat) && AFMT_BIT(besthwformat) > AFMT_BIT(bestformat)) bestformat = besthwformat; CHN_UNLOCK(ch); nrun++; } if (bestformat == 0) bestformat = c->format; if (bestspeed == 0) bestspeed = c->speed; if (bestformat != c->format || bestspeed != c->speed) dirty = 1; c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE); c->flags |= vpflags; if (nrun && !run) { if (dirty) { bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo, bestspeed); err = chn_reset(c, bestformat, bestspeed); } if (err == 0 && dirty) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } } if (err == 0) { if (dirty) c->flags |= CHN_F_DIRTY; err = chn_start(c, 1); } } if (nrun && run && dirty) { chn_abort(c); bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo, bestspeed); err = chn_reset(c, bestformat, bestspeed); if (err == 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } } if (err == 0) { c->flags |= CHN_F_DIRTY; err = chn_start(c, 1); } } if (err == 0 && !(bestformat & AFMT_PASSTHROUGH) && (bestformat & AFMT_VCHAN)) { *vchanformat = bestformat; *vchanrate = bestspeed; } if (!nrun && run) { c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE); bestformat = *vchanformat; bestspeed = *vchanrate; chn_abort(c); if (c->format != bestformat || c->speed != bestspeed) chn_reset(c, bestformat, bestspeed); } } return (err); } /** * @brief Fetch array of supported discrete sample rates * * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for * detailed information. * * @note If the operation isn't supported, this function will just return 0 * (no rates in the array), and *rates will be set to NULL. Callers * should examine rates @b only if this function returns non-zero. * * @param c pcm channel to examine * @param rates pointer to array of integers; rate table will be recorded here * * @return number of rates in the array pointed to be @c rates */ int chn_getrates(struct pcm_channel *c, int **rates) { KASSERT(rates != NULL, ("rates is null")); CHN_LOCKASSERT(c); return CHANNEL_GETRATES(c->methods, c->devinfo, rates); } /** * @brief Remove channel from a sync group, if there is one. * * This function is initially intended for the following conditions: * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) * - Closing a device. (A channel can't be destroyed if it's still in use.) * * @note Before calling this function, the syncgroup list mutex must be * held. (Consider pcm_channel::sm protected by the SG list mutex * whether @c c is locked or not.) * * @param c channel device to be started or closed * @returns If this channel was the only member of a group, the group ID * is returned to the caller so that the caller can release it * via free_unr() after giving up the syncgroup lock. Else it * returns 0. */ int chn_syncdestroy(struct pcm_channel *c) { struct pcmchan_syncmember *sm; struct pcmchan_syncgroup *sg; int sg_id; sg_id = 0; PCM_SG_LOCKASSERT(MA_OWNED); if (c->sm != NULL) { sm = c->sm; sg = sm->parent; c->sm = NULL; KASSERT(sg != NULL, ("syncmember has null parent")); SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); free(sm, M_DEVBUF); if (SLIST_EMPTY(&sg->members)) { SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); sg_id = sg->id; free(sg, M_DEVBUF); } } return sg_id; } #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) { CHN_LOCKASSERT(c); return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); } #endif diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 1b205d7229fc..d0ed405fb25b 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -1,3178 +1,3178 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include #include #include struct dsp_cdevpriv { struct snddev_info *sc; struct pcm_channel *rdch; struct pcm_channel *wrch; }; static int dsp_mmap_allow_prot_exec = 0; SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RWTUN, &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility (-1=force disable 0=auto 1=force enable)"); static int dsp_basename_clone = 1; SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN, &dsp_basename_clone, 0, "DSP basename cloning (0: Disable; 1: Enabled)"); #define DSP_REGISTERED(x) (PCM_REGISTERED(x) && (x)->dsp_dev != NULL) #define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) #define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) #define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) #define DSP_F_READ(x) ((x) & FREAD) #define DSP_F_WRITE(x) ((x) & FWRITE) #define OLDPCM_IOCTL static d_open_t dsp_open; static d_read_t dsp_read; static d_write_t dsp_write; static d_ioctl_t dsp_ioctl; static d_poll_t dsp_poll; static d_mmap_t dsp_mmap; static d_mmap_single_t dsp_mmap_single; static d_kqfilter_t dsp_kqfilter; struct cdevsw dsp_cdevsw = { .d_version = D_VERSION, .d_open = dsp_open, .d_read = dsp_read, .d_write = dsp_write, .d_ioctl = dsp_ioctl, .d_poll = dsp_poll, .d_kqfilter = dsp_kqfilter, .d_mmap = dsp_mmap, .d_mmap_single = dsp_mmap_single, .d_name = "dsp", }; static eventhandler_tag dsp_ehtag = NULL; static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); static int dsp_oss_syncstart(int sg_id); static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy); static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled); static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask); #ifdef OSSV4_EXPERIMENT static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif int dsp_make_dev(device_t dev) { struct make_dev_args devargs; struct snddev_info *sc; int err, unit; sc = device_get_softc(dev); unit = device_get_unit(dev); make_dev_args_init(&devargs); devargs.mda_devsw = &dsp_cdevsw; devargs.mda_uid = UID_ROOT; devargs.mda_gid = GID_WHEEL; devargs.mda_mode = 0666; devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->dsp_dev, "dsp%d", unit); if (err != 0) { device_printf(dev, "failed to create dsp%d: error %d", unit, err); return (ENXIO); } return (0); } void dsp_destroy_dev(device_t dev) { struct snddev_info *d; d = device_get_softc(dev); destroy_dev(d->dsp_dev); } static void dsp_lock_chans(struct dsp_cdevpriv *priv, uint32_t prio) { if (priv->rdch != NULL && DSP_F_READ(prio)) CHN_LOCK(priv->rdch); if (priv->wrch != NULL && DSP_F_WRITE(prio)) CHN_LOCK(priv->wrch); } static void dsp_unlock_chans(struct dsp_cdevpriv *priv, uint32_t prio) { if (priv->rdch != NULL && DSP_F_READ(prio)) CHN_UNLOCK(priv->rdch); if (priv->wrch != NULL && DSP_F_WRITE(prio)) CHN_UNLOCK(priv->wrch); } static int dsp_chn_alloc(struct snddev_info *d, struct pcm_channel **ch, int direction, int flags, struct thread *td) { struct pcm_channel *c; char *comm; pid_t pid; int err; bool vdir_enabled; KASSERT(d != NULL && ch != NULL && (direction == PCMDIR_PLAY || direction == PCMDIR_REC), ("%s(): invalid d=%p ch=%p direction=%d", __func__, d, ch, direction)); PCM_BUSYASSERT(d); pid = td->td_proc->p_pid; comm = td->td_proc->p_comm; vdir_enabled = (direction == PCMDIR_PLAY && d->flags & SD_F_PVCHANS) || (direction == PCMDIR_REC && d->flags & SD_F_RVCHANS); *ch = NULL; CHN_FOREACH(c, d, channels.pcm.primary) { CHN_LOCK(c); if (c->direction != direction) { CHN_UNLOCK(c); continue; } /* Find an available primary channel to use. */ if ((c->flags & CHN_F_BUSY) == 0 || (vdir_enabled && (c->flags & CHN_F_HAS_VCHAN))) break; CHN_UNLOCK(c); } if (c == NULL) return (EBUSY); /* * We can have the following cases: * - vchans are enabled, add a new vchan to the primary channel. * - vchans are disabled, use the primary channel directly. */ if (vdir_enabled && ((c->flags & CHN_F_BUSY) == 0 || c->flags & CHN_F_HAS_VCHAN)) { err = vchan_create(c, ch); CHN_UNLOCK(c); if (err != 0) return (err); CHN_LOCK(*ch); } else if ((c->flags & CHN_F_BUSY) == 0) { *ch = c; } else { CHN_UNLOCK(c); return (ENODEV); } (*ch)->flags |= CHN_F_BUSY; if (flags & O_NONBLOCK) (*ch)->flags |= CHN_F_NBIO; if (flags & O_EXCL) (*ch)->flags |= CHN_F_EXCLUSIVE; (*ch)->pid = pid; strlcpy((*ch)->comm, (comm != NULL) ? comm : CHN_COMM_UNKNOWN, sizeof((*ch)->comm)); if ((err = chn_reset(*ch, (*ch)->format, (*ch)->speed)) != 0) return (err); chn_vpc_reset(*ch, SND_VOL_C_PCM, 0); CHN_UNLOCK(*ch); return (0); } static void dsp_close(void *data) { struct dsp_cdevpriv *priv = data; struct pcm_channel *rdch, *wrch, *parent; struct snddev_info *d; int sg_ids; if (priv == NULL) return; d = priv->sc; /* At this point pcm_unregister() will destroy all channels anyway. */ if (!DSP_REGISTERED(d)) goto skip; PCM_GIANT_ENTER(d); PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); rdch = priv->rdch; wrch = priv->wrch; if (rdch != NULL) CHN_REMOVE(d, rdch, channels.pcm.opened); if (wrch != NULL) CHN_REMOVE(d, wrch, channels.pcm.opened); if (rdch != NULL || wrch != NULL) { PCM_UNLOCK(d); if (rdch != NULL) { /* * The channel itself need not be locked because: * a) Adding a channel to a syncgroup happens only * in dsp_ioctl(), which cannot run concurrently * to dsp_close(). * b) The syncmember pointer (sm) is protected by * the global syncgroup list lock. * c) A channel can't just disappear, invalidating * pointers, unless it's closed/dereferenced * first. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(rdch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); /* * Go through the channel abort/flush path for both * primary and virtual channels to ensure that, in the * case of vchans, the stream is always properly * stopped, and the primary channels do not keep being * interrupted even if all vchans are gone. */ CHN_LOCK(rdch); chn_abort(rdch); /* won't sleep */ rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | CHN_F_DEAD | CHN_F_EXCLUSIVE | CHN_F_NBIO); chn_reset(rdch, 0, 0); chn_release(rdch); if (rdch->flags & CHN_F_VIRTUAL) { parent = rdch->parentchannel; CHN_LOCK(parent); CHN_LOCK(rdch); vchan_destroy(rdch); CHN_UNLOCK(parent); } } if (wrch != NULL) { /* * Please see block above. */ PCM_SG_LOCK(); sg_ids = chn_syncdestroy(wrch); PCM_SG_UNLOCK(); if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); CHN_LOCK(wrch); chn_flush(wrch); /* may sleep */ wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | CHN_F_DEAD | CHN_F_EXCLUSIVE | CHN_F_NBIO); chn_reset(wrch, 0, 0); chn_release(wrch); if (wrch->flags & CHN_F_VIRTUAL) { parent = wrch->parentchannel; CHN_LOCK(parent); CHN_LOCK(wrch); vchan_destroy(wrch); CHN_UNLOCK(parent); } } PCM_LOCK(d); } PCM_RELEASE(d); PCM_UNLOCK(d); PCM_GIANT_LEAVE(d); skip: free(priv, M_DEVBUF); priv = NULL; } static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct dsp_cdevpriv *priv; struct pcm_channel *ch; struct snddev_info *d; int error, dir; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return (ENODEV); d = i_dev->si_drv1; if (!DSP_REGISTERED(d)) return (EBADF); if (PCM_CHANCOUNT(d) >= PCM_MAXCHANS) return (ENOMEM); priv = malloc(sizeof(*priv), M_DEVBUF, M_WAITOK | M_ZERO); priv->sc = d; error = devfs_set_cdevpriv(priv, dsp_close); if (error != 0) return (error); PCM_GIANT_ENTER(d); /* Lock snddev so nobody else can monkey with it. */ PCM_LOCK(d); PCM_WAIT(d); error = 0; if (!DSP_F_VALID(flags)) error = EINVAL; else if (!DSP_F_DUPLEX(flags) && ((DSP_F_READ(flags) && d->reccount == 0) || (DSP_F_WRITE(flags) && d->playcount == 0))) error = ENOTSUP; if (pcm_getflags(d->dev) & SD_F_SIMPLEX) { if (DSP_F_DUPLEX(flags)) { /* * If no channels are opened yet, and we request * DUPLEX, limit to playback only, otherwise open one * channel in a direction that already exists. */ if (CHN_EMPTY(d, channels.pcm.opened)) { if (d->playcount > 0) flags &= ~FREAD; else if (d->reccount > 0) flags &= ~FWRITE; } else { ch = CHN_FIRST(d, channels.pcm.opened); if (ch->direction == PCMDIR_PLAY) flags &= ~FREAD; else if (ch->direction == PCMDIR_REC) flags &= ~FWRITE; } } else if (!CHN_EMPTY(d, channels.pcm.opened)) { /* * If we requested SIMPLEX, make sure we do not open a * channel in the opposite direction. */ ch = CHN_FIRST(d, channels.pcm.opened); dir = DSP_F_READ(flags) ? PCMDIR_REC : PCMDIR_PLAY; if (ch->direction != dir) error = ENOTSUP; } } if (error != 0) { PCM_UNLOCK(d); PCM_GIANT_EXIT(d); return (error); } /* * That is just enough. Acquire and unlock pcm lock so * the other will just have to wait until we finish doing * everything. */ PCM_ACQUIRE(d); PCM_UNLOCK(d); if (DSP_F_WRITE(flags)) { error = dsp_chn_alloc(d, &priv->wrch, PCMDIR_PLAY, flags, td); if (error != 0) { PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (error); } PCM_LOCK(d); CHN_INSERT_HEAD(d, priv->wrch, channels.pcm.opened); PCM_UNLOCK(d); } if (DSP_F_READ(flags)) { error = dsp_chn_alloc(d, &priv->rdch, PCMDIR_REC, flags, td); if (error != 0) { PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (error); } PCM_LOCK(d); CHN_INSERT_HEAD(d, priv->rdch, channels.pcm.opened); PCM_UNLOCK(d); } PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); return (0); } static __inline int dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf) { struct snddev_info *d; struct pcm_channel **ch; int (*chn_io)(struct pcm_channel *, struct uio *); int prio, ret; pid_t runpid; KASSERT(buf != NULL && (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE), ("%s(): io train wreck!", __func__)); d = priv->sc; if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); switch (buf->uio_rw) { case UIO_READ: prio = FREAD; ch = &priv->rdch; chn_io = chn_read; break; case UIO_WRITE: prio = FWRITE; ch = &priv->wrch; chn_io = chn_write; break; default: panic("invalid/corrupted uio direction: %d", buf->uio_rw); break; } runpid = buf->uio_td->td_proc->p_pid; dsp_lock_chans(priv, prio); if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { if (priv->rdch != NULL || priv->wrch != NULL) dsp_unlock_chans(priv, prio); PCM_GIANT_EXIT(d); return (EBADF); } if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) || (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { dsp_unlock_chans(priv, prio); PCM_GIANT_EXIT(d); return (EINVAL); } else if (!((*ch)->flags & CHN_F_RUNNING)) { (*ch)->flags |= CHN_F_RUNNING; (*ch)->pid = runpid; } /* * chn_read/write must give up channel lock in order to copy bytes * from/to userland, so up the "in progress" counter to make sure * someone else doesn't come along and muss up the buffer. */ ++(*ch)->inprog; ret = chn_io(*ch, buf); --(*ch)->inprog; CHN_BROADCAST(&(*ch)->cv); dsp_unlock_chans(priv, prio); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_read(struct cdev *i_dev, struct uio *buf, int flag) { struct dsp_cdevpriv *priv; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); return (dsp_io_ops(priv, buf)); } static int dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { struct dsp_cdevpriv *priv; int err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); return (dsp_io_ops(priv, buf)); } static int dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *ch, u_long cmd, caddr_t arg) { struct snddev_info *d; struct pcm_channel *rdch, *wrch; int j, left, right, center, mute; d = priv->sc; if (!PCM_REGISTERED(d) || !(pcm_getflags(d->dev) & SD_F_VPC)) return (-1); PCM_UNLOCKASSERT(d); j = cmd & 0xff; rdch = priv->rdch; wrch = priv->wrch; if (ch == NULL) { if (j == SOUND_MIXER_RECLEV && rdch != NULL) ch = rdch; else if (j == SOUND_MIXER_PCM && wrch != NULL) ch = wrch; } if (ch == NULL) return (EINVAL); CHN_LOCK(ch); if (!(ch->feederflags & (1 << FEEDER_VOLUME))) { CHN_UNLOCK(ch); return (EINVAL); } switch (cmd & ~0xff) { case MIXER_WRITE(0): switch (j) { case SOUND_MIXER_MUTE: if (ch->direction == PCMDIR_REC) { chn_setmute_multi(ch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); } else { chn_setmute_multi(ch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); } break; case SOUND_MIXER_PCM: if (ch->direction != PCMDIR_PLAY) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(ch, SND_VOL_C_PCM, left, right, center); break; case SOUND_MIXER_RECLEV: if (ch->direction != PCMDIR_REC) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; chn_setvolume_multi(ch, SND_VOL_C_PCM, left, right, center); break; default: /* ignore all other mixer writes */ break; } break; case MIXER_READ(0): switch (j) { case SOUND_MIXER_MUTE: mute = CHN_GETMUTE(ch, SND_VOL_C_PCM, SND_CHN_T_FL) || CHN_GETMUTE(ch, SND_VOL_C_PCM, SND_CHN_T_FR); if (ch->direction == PCMDIR_REC) { *(int *)arg = mute << SOUND_MIXER_RECLEV; } else { *(int *)arg = mute << SOUND_MIXER_PCM; } break; case SOUND_MIXER_PCM: if (ch->direction != PCMDIR_PLAY) break; *(int *)arg = CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_RECLEV: if (ch->direction != PCMDIR_REC) break; *(int *)arg = CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FL); *(int *)arg |= CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: if (ch->direction == PCMDIR_REC) *(int *)arg = SOUND_MASK_RECLEV; else *(int *)arg = SOUND_MASK_PCM; break; default: *(int *)arg = 0; break; } break; default: break; } CHN_UNLOCK(ch); return (0); } #ifdef COMPAT_FREEBSD32 typedef struct _snd_chan_param32 { uint32_t play_rate; uint32_t rec_rate; uint32_t play_format; uint32_t rec_format; } snd_chan_param32; #define AIOGFMT32 _IOC_NEWTYPE(AIOGFMT, snd_chan_param32) #define AIOSFMT32 _IOC_NEWTYPE(AIOSFMT, snd_chan_param32) typedef struct _snd_capabilities32 { uint32_t rate_min, rate_max; uint32_t formats; uint32_t bufsize; uint32_t mixers; uint32_t inputs; uint16_t left, right; } snd_capabilities32; #define AIOGCAP32 _IOC_NEWTYPE(AIOGCAP, snd_capabilities32) typedef struct audio_errinfo32 { int32_t play_underruns; int32_t rec_overruns; uint32_t play_ptradjust; uint32_t rec_ptradjust; int32_t play_errorcount; int32_t rec_errorcount; int32_t play_lasterror; int32_t rec_lasterror; int32_t play_errorparm; int32_t rec_errorparm; int32_t filler[16]; } audio_errinfo32; #define SNDCTL_DSP_GETERROR32 _IOC_NEWTYPE(SNDCTL_DSP_GETERROR, audio_errinfo32) #endif static int dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct dsp_cdevpriv *priv; struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; u_long xcmd; int *arg_i, ret, tmp, err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); arg_i = (int *)arg; ret = 0; xcmd = 0; chn = NULL; if (IOCGROUP(cmd) == 'M') { if (cmd == OSS_GETVERSION) { *arg_i = SOUND_VERSION; PCM_GIANT_EXIT(d); return (0); } ret = dsp_ioctl_channel(priv, NULL, cmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = EBADF; PCM_GIANT_EXIT(d); return (ret); } /* * Certain ioctls may be made on any type of device (audio, mixer, * and MIDI). Handle those special cases here. */ if (IOCGROUP(cmd) == 'X') { PCM_ACQUIRE_QUICK(d); switch(cmd) { case SNDCTL_SYSINFO: sound_oss_sysinfo((oss_sysinfo *)arg); break; case SNDCTL_CARDINFO: ret = sound_oss_card_info((oss_card_info *)arg); break; case SNDCTL_AUDIOINFO: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, false); break; case SNDCTL_AUDIOINFO_EX: ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, true); break; case SNDCTL_ENGINEINFO: ret = dsp_oss_engineinfo(i_dev, (oss_audioinfo *)arg); break; case SNDCTL_MIXERINFO: ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); break; default: ret = EINVAL; } PCM_RELEASE_QUICK(d); PCM_GIANT_EXIT(d); return (ret); } rdch = priv->rdch; wrch = priv->wrch; if (wrch != NULL && (wrch->flags & CHN_F_DEAD)) wrch = NULL; if (rdch != NULL && (rdch->flags & CHN_F_DEAD)) rdch = NULL; if (wrch == NULL && rdch == NULL) { PCM_GIANT_EXIT(d); return (EINVAL); } switch(cmd) { #ifdef OLDPCM_IOCTL /* * we start with the new ioctl interface. */ case AIONWRITE: /* how many bytes can write ? */ if (wrch) { CHN_LOCK(wrch); /* if (wrch && wrch->bufhard.dl) while (chn_wrfeed(wrch) == 0); */ *arg_i = sndbuf_getfree(wrch->bufsoft); CHN_UNLOCK(wrch); } else { *arg_i = 0; ret = EINVAL; } break; case AIOSSIZE: /* set the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; p->play_size = 0; p->rec_size = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, p->play_size); - p->play_size = sndbuf_getblksz(wrch->bufsoft); + p->play_size = wrch->bufsoft->blksz; CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, p->rec_size); - p->rec_size = sndbuf_getblksz(rdch->bufsoft); + p->rec_size = rdch->bufsoft->blksz; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); } break; case AIOGSIZE: /* get the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; if (wrch) { CHN_LOCK(wrch); - p->play_size = sndbuf_getblksz(wrch->bufsoft); + p->play_size = wrch->bufsoft->blksz; CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); - p->rec_size = sndbuf_getblksz(rdch->bufsoft); + p->rec_size = rdch->bufsoft->blksz; CHN_UNLOCK(rdch); } } break; case AIOSFMT: case AIOGFMT: #ifdef COMPAT_FREEBSD32 case AIOSFMT32: case AIOGFMT32: #endif { snd_chan_param *p = (snd_chan_param *)arg; #ifdef COMPAT_FREEBSD32 snd_chan_param32 *p32 = (snd_chan_param32 *)arg; snd_chan_param param; if (cmd == AIOSFMT32) { p = ¶m; p->play_rate = p32->play_rate; p->rec_rate = p32->rec_rate; p->play_format = p32->play_format; p->rec_format = p32->rec_format; } #endif if (cmd == AIOSFMT && ((p->play_format != 0 && p->play_rate == 0) || (p->rec_format != 0 && p->rec_rate == 0))) { ret = EINVAL; break; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); if (cmd == AIOSFMT && p->play_format != 0) { chn_setformat(wrch, SND_FORMAT(p->play_format, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); chn_setspeed(wrch, p->play_rate); } p->play_rate = wrch->speed; p->play_format = AFMT_ENCODING(wrch->format); CHN_UNLOCK(wrch); } else { p->play_rate = 0; p->play_format = 0; } if (rdch) { CHN_LOCK(rdch); if (cmd == AIOSFMT && p->rec_format != 0) { chn_setformat(rdch, SND_FORMAT(p->rec_format, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); chn_setspeed(rdch, p->rec_rate); } p->rec_rate = rdch->speed; p->rec_format = AFMT_ENCODING(rdch->format); CHN_UNLOCK(rdch); } else { p->rec_rate = 0; p->rec_format = 0; } PCM_RELEASE_QUICK(d); #ifdef COMPAT_FREEBSD32 if (cmd == AIOSFMT32 || cmd == AIOGFMT32) { p32->play_rate = p->play_rate; p32->rec_rate = p->rec_rate; p32->play_format = p->play_format; p32->rec_format = p->rec_format; } #endif } break; case AIOGCAP: /* get capabilities */ #ifdef COMPAT_FREEBSD32 case AIOGCAP32: #endif { snd_capabilities *p = (snd_capabilities *)arg; struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; #ifdef COMPAT_FREEBSD32 snd_capabilities32 *p32 = (snd_capabilities32 *)arg; snd_capabilities capabilities; if (cmd == AIOGCAP32) { p = &capabilities; p->rate_min = p32->rate_min; p->rate_max = p32->rate_max; p->formats = p32->formats; p->bufsize = p32->bufsize; p->mixers = p32->mixers; p->inputs = p32->inputs; p->left = p32->left; p->right = p32->right; } #endif PCM_LOCK(d); if (rdch) { CHN_LOCK(rdch); rcaps = chn_getcaps(rdch); } if (wrch) { CHN_LOCK(wrch); pcaps = chn_getcaps(wrch); } p->rate_min = max(rcaps? rcaps->minspeed : 0, pcaps? pcaps->minspeed : 0); p->rate_max = min(rcaps? rcaps->maxspeed : 1000000, pcaps? pcaps->maxspeed : 1000000); - p->bufsize = min(rdch? sndbuf_getsize(rdch->bufsoft) : 1000000, - wrch? sndbuf_getsize(wrch->bufsoft) : 1000000); + p->bufsize = min(rdch? rdch->bufsoft->bufsize : 1000000, + wrch? wrch->bufsoft->bufsize : 1000000); /* XXX bad on sb16 */ p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) & (wrch? chn_getformats(wrch) : 0xffffffff); if (rdch && wrch) { p->formats |= (pcm_getflags(d->dev) & SD_F_SIMPLEX) ? 0 : AFMT_FULLDUPLEX; } pdev = d->mixer_dev; p->mixers = 1; /* default: one mixer */ p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0; p->left = p->right = 100; if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_UNLOCK(d); #ifdef COMPAT_FREEBSD32 if (cmd == AIOGCAP32) { p32->rate_min = p->rate_min; p32->rate_max = p->rate_max; p32->formats = p->formats; p32->bufsize = p->bufsize; p32->mixers = p->mixers; p32->inputs = p->inputs; p32->left = p->left; p32->right = p->right; } #endif } break; case AIOSTOP: if (*arg_i == AIOSYNC_PLAY && wrch) { CHN_LOCK(wrch); *arg_i = chn_abort(wrch); CHN_UNLOCK(wrch); } else if (*arg_i == AIOSYNC_CAPTURE && rdch) { CHN_LOCK(rdch); *arg_i = chn_abort(rdch); CHN_UNLOCK(rdch); } else { printf("AIOSTOP: bad channel 0x%x\n", *arg_i); *arg_i = 0; } break; case AIOSYNC: printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n", ((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos); break; #endif /* * here follow the standard ioctls (filio.h etc.) */ case FIONREAD: /* get # bytes to read */ if (rdch) { CHN_LOCK(rdch); /* if (rdch && rdch->bufhard.dl) while (chn_rdfeed(rdch) == 0); */ *arg_i = sndbuf_getready(rdch->bufsoft); CHN_UNLOCK(rdch); } else { *arg_i = 0; ret = EINVAL; } break; case FIOASYNC: /*set/clear async i/o */ DEB( printf("FIOASYNC\n") ; ) break; case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */ case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) { CHN_LOCK(rdch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) rdch->flags |= CHN_F_NBIO; else rdch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) wrch->flags |= CHN_F_NBIO; else wrch->flags &= ~CHN_F_NBIO; CHN_UNLOCK(wrch); } break; /* * Finally, here is the linux-compatible ioctl interface */ #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int) case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); - *arg_i = sndbuf_getblksz(chn->bufsoft); + *arg_i = chn->bufsoft->blksz; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, *arg_i); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_setblocksize(rdch, 2, *arg_i); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_RESET: DEB(printf("dsp reset\n")); if (wrch) { CHN_LOCK(wrch); chn_abort(wrch); chn_resetbuf(wrch); CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); chn_abort(rdch); chn_resetbuf(rdch); CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_SYNC: DEB(printf("dsp sync\n")); /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SPEED: /* chn_setspeed may sleep */ tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setspeed(wrch, *arg_i); tmp = wrch->speed; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setspeed(rdch, *arg_i); if (tmp == 0) tmp = rdch->speed; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_READ_RATE: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn->speed; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_STEREO: tmp = -1; *arg_i = (*arg_i)? 2 : 1; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, 0)); tmp = (AFMT_CHANNEL(wrch->format) > 1)? 1 : 0; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, 0)); if (tmp == -1) tmp = (AFMT_CHANNEL(rdch->format) > 1)? 1 : 0; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_WRITE_CHANNELS: /* case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */ if (*arg_i < 0 || *arg_i > AFMT_CHANNEL_MAX) { *arg_i = 0; ret = EINVAL; break; } if (*arg_i != 0) { uint32_t ext = 0; tmp = 0; /* * Map channel number to surround sound formats. * Devices that need bitperfect mode to operate * (e.g. more than SND_CHN_MAX channels) are not * subject to any mapping. */ if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) { struct pcmchan_matrix *m; if (*arg_i > SND_CHN_MAX) *arg_i = SND_CHN_MAX; m = feeder_matrix_default_channel_map(*arg_i); if (m != NULL) ext = m->ext; } PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(wrch->format, *arg_i, ext)); tmp = AFMT_CHANNEL(wrch->format); CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(rdch->format, *arg_i, ext)); if (tmp == 0) tmp = AFMT_CHANNEL(rdch->format); CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } break; case SOUND_PCM_READ_CHANNELS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = AFMT_CHANNEL(chn->format); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); *arg_i = chn_getformats(chn); CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ if (*arg_i != AFMT_QUERY) { tmp = 0; PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, SND_FORMAT(*arg_i, AFMT_CHANNEL(wrch->format), AFMT_EXTCHANNEL(wrch->format))); tmp = wrch->format; CHN_UNLOCK(wrch); } if (rdch && ret == 0) { CHN_LOCK(rdch); ret = chn_setformat(rdch, SND_FORMAT(*arg_i, AFMT_CHANNEL(rdch->format), AFMT_EXTCHANNEL(rdch->format))); if (tmp == 0) tmp = rdch->format; CHN_UNLOCK(rdch); } PCM_RELEASE_QUICK(d); *arg_i = AFMT_ENCODING(tmp); } else { chn = wrch ? wrch : rdch; CHN_LOCK(chn); *arg_i = AFMT_ENCODING(chn->format); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_SETFRAGMENT: DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { uint32_t fragln = (*arg_i) & 0x0000ffff; uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; uint32_t fragsz; uint32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; if (maxfrags == 0) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; if (maxfrags < 2) maxfrags = 2; if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); - r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft); - r_fragsz = sndbuf_getblksz(rdch->bufsoft); + r_maxfrags = rdch->bufsoft->blkcnt; + r_fragsz = rdch->bufsoft->blksz; CHN_UNLOCK(rdch); } else { r_maxfrags = maxfrags; r_fragsz = fragsz; } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setblocksize(wrch, maxfrags, fragsz); - maxfrags = sndbuf_getblkcnt(wrch->bufsoft); - fragsz = sndbuf_getblksz(wrch->bufsoft); + maxfrags = wrch->bufsoft->blkcnt; + fragsz = wrch->bufsoft->blksz; CHN_UNLOCK(wrch); } else { /* use whatever came from the read channel */ maxfrags = r_maxfrags; fragsz = r_fragsz; } PCM_RELEASE_QUICK(d); fragln = 0; while (fragsz > 1) { fragln++; fragsz >>= 1; } *arg_i = (maxfrags << 16) | fragln; } break; case SNDCTL_DSP_GETISPACE: /* return the size of data available in the input queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); a->bytes = sndbuf_getready(bs); - a->fragments = a->bytes / sndbuf_getblksz(bs); - a->fragstotal = sndbuf_getblkcnt(bs); - a->fragsize = sndbuf_getblksz(bs); + a->fragments = a->bytes / bs->blksz; + a->fragstotal = bs->blkcnt; + a->fragsize = bs->blksz; CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOSPACE: /* return space available in the output queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); a->bytes = sndbuf_getfree(bs); - a->fragments = a->bytes / sndbuf_getblksz(bs); - a->fragstotal = sndbuf_getblkcnt(bs); - a->fragsize = sndbuf_getblksz(bs); + a->fragments = a->bytes / bs->blksz; + a->fragstotal = bs->blkcnt; + a->fragsize = bs->blksz; CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETIPTR: { count_info *a = (count_info *)arg; if (rdch) { struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); - a->bytes = sndbuf_gettotal(bs); + a->bytes = bs->total; a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getfreeptr(bs); rdch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(rdch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETOPTR: { count_info *a = (count_info *)arg; if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - a->bytes = sndbuf_gettotal(bs); + a->bytes = bs->total; a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); wrch->blocks = sndbuf_getblocks(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; } break; case SNDCTL_DSP_GETCAPS: PCM_LOCK(d); *arg_i = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; if (rdch && wrch && !(pcm_getflags(d->dev) & SD_F_SIMPLEX)) *arg_i |= PCM_CAP_DUPLEX; if (rdch && (rdch->flags & CHN_F_VIRTUAL) != 0) *arg_i |= PCM_CAP_VIRTUAL; if (wrch && (wrch->flags & CHN_F_VIRTUAL) != 0) *arg_i |= PCM_CAP_VIRTUAL; PCM_UNLOCK(d); break; case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; if (chn) { CHN_LOCK(chn); if (chn->format & AFMT_8BIT) *arg_i = 8; else if (chn->format & AFMT_16BIT) *arg_i = 16; else if (chn->format & AFMT_24BIT) *arg_i = 24; else if (chn->format & AFMT_32BIT) *arg_i = 32; else ret = EINVAL; CHN_UNLOCK(chn); } else { *arg_i = 0; ret = EINVAL; } break; case SNDCTL_DSP_SETTRIGGER: if (rdch) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_INPUT) chn_start(rdch, 1); else { chn_abort(rdch); chn_resetbuf(rdch); rdch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(rdch); } if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; if (*arg_i & PCM_ENABLE_OUTPUT) chn_start(wrch, 1); else { chn_abort(wrch); chn_resetbuf(wrch); wrch->flags |= CHN_F_NOTRIGGER; } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_GETTRIGGER: *arg_i = 0; if (wrch) { CHN_LOCK(wrch); if (wrch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_OUTPUT; CHN_UNLOCK(wrch); } if (rdch) { CHN_LOCK(rdch); if (rdch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_INPUT; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETODELAY: if (wrch) { struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; break; case SNDCTL_DSP_POST: if (wrch) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_NOTRIGGER; chn_start(wrch, 1); CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SETDUPLEX: /* * switch to full-duplex mode if card is in half-duplex * mode and is able to work in full-duplex mode */ PCM_LOCK(d); if (rdch && wrch && (pcm_getflags(d->dev) & SD_F_SIMPLEX)) pcm_setflags(d->dev, pcm_getflags(d->dev)^SD_F_SIMPLEX); PCM_UNLOCK(d); break; /* * The following four ioctls are simple wrappers around mixer_ioctl * with no further processing. xcmd is short for "translated * command". */ case SNDCTL_DSP_GETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETRECVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_RECLEV; chn = rdch; } /* FALLTHROUGH */ case SNDCTL_DSP_GETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_READ_PCM; chn = wrch; } /* FALLTHROUGH */ case SNDCTL_DSP_SETPLAYVOL: if (xcmd == 0) { xcmd = SOUND_MIXER_WRITE_PCM; chn = wrch; } ret = dsp_ioctl_channel(priv, chn, xcmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); } if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = ENOTSUP; break; case SNDCTL_DSP_GET_RECSRC_NAMES: case SNDCTL_DSP_GET_RECSRC: case SNDCTL_DSP_SET_RECSRC: if (d->mixer_dev != NULL) { PCM_ACQUIRE_QUICK(d); ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, MIXER_CMD_DIRECT); PCM_RELEASE_QUICK(d); } else ret = ENOTSUP; break; /* * The following 3 ioctls aren't very useful at the moment. For * now, only a single channel is associated with a cdev (/dev/dspN * instance), so there's only a single output routing to use (i.e., * the wrch bound to this cdev). */ case SNDCTL_DSP_GET_PLAYTGT_NAMES: { oss_mixer_enuminfo *ei; ei = (oss_mixer_enuminfo *)arg; ei->dev = 0; ei->ctrl = 0; ei->version = 0; /* static for now */ ei->strindex[0] = 0; if (wrch != NULL) { ei->nvalues = 1; strlcpy(ei->strings, wrch->name, sizeof(ei->strings)); } else { ei->nvalues = 0; ei->strings[0] = '\0'; } } break; case SNDCTL_DSP_GET_PLAYTGT: case SNDCTL_DSP_SET_PLAYTGT: /* yes, they are the same for now */ /* * Re: SET_PLAYTGT * OSSv4: "The value that was accepted by the device will * be returned back in the variable pointed by the * argument." */ if (wrch != NULL) *arg_i = 0; else ret = EINVAL; break; case SNDCTL_DSP_SILENCE: /* * Flush the software (pre-feed) buffer, but try to minimize playback * interruption. (I.e., record unplayed samples with intent to * restore by SNDCTL_DSP_SKIP.) Intended for application "pause" * functionality. */ if (wrch == NULL) ret = EINVAL; else { struct snd_dbuf *bs; CHN_LOCK(wrch); while (wrch->inprog != 0) cv_wait(&wrch->cv, wrch->lock); bs = wrch->bufsoft; if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) { bs->sl = sndbuf_getready(bs); sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs)); sndbuf_fillsilence(bs); chn_start(wrch, 0); } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_SKIP: /* * OSSv4 docs: "This ioctl call discards all unplayed samples in the * playback buffer by moving the current write position immediately * before the point where the device is currently reading the samples." */ if (wrch == NULL) ret = EINVAL; else { struct snd_dbuf *bs; CHN_LOCK(wrch); while (wrch->inprog != 0) cv_wait(&wrch->cv, wrch->lock); bs = wrch->bufsoft; if ((bs->shadbuf != NULL) && (bs->sl > 0)) { sndbuf_softreset(bs); sndbuf_acquire(bs, bs->shadbuf, bs->sl); bs->sl = 0; chn_start(wrch, 0); } CHN_UNLOCK(wrch); } break; case SNDCTL_DSP_CURRENT_OPTR: case SNDCTL_DSP_CURRENT_IPTR: /** * @note Changing formats resets the buffer counters, which differs * from the 4Front drivers. However, I don't expect this to be * much of a problem. * * @note In a test where @c CURRENT_OPTR is called immediately after write * returns, this driver is about 32K samples behind whereas * 4Front's is about 8K samples behind. Should determine source * of discrepancy, even if only out of curiosity. * * @todo Actually test SNDCTL_DSP_CURRENT_IPTR. */ chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { struct snd_dbuf *bs; /* int tmp; */ oss_count_t *oc = (oss_count_t *)arg; CHN_LOCK(chn); bs = chn->bufsoft; - oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs); - oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(bs); + oc->samples = bs->total / bs->align; + oc->fifo_samples = sndbuf_getready(bs) / bs->align; CHN_UNLOCK(chn); } break; case SNDCTL_DSP_HALT_OUTPUT: case SNDCTL_DSP_HALT_INPUT: chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { CHN_LOCK(chn); chn_abort(chn); CHN_UNLOCK(chn); } break; case SNDCTL_DSP_LOW_WATER: /* * Set the number of bytes required to attract attention by * select/poll. */ if (wrch != NULL) { CHN_LOCK(wrch); wrch->lw = (*arg_i > 1) ? *arg_i : 1; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->lw = (*arg_i > 1) ? *arg_i : 1; CHN_UNLOCK(rdch); } break; case SNDCTL_DSP_GETERROR: #ifdef COMPAT_FREEBSD32 case SNDCTL_DSP_GETERROR32: #endif /* * OSSv4 docs: "All errors and counters will automatically be * cleared to zeroes after the call so each call will return only * the errors that occurred after the previous invocation. ... The * play_underruns and rec_overrun fields are the only useful fields * returned by OSS 4.0." */ { audio_errinfo *ei = (audio_errinfo *)arg; #ifdef COMPAT_FREEBSD32 audio_errinfo errinfo; audio_errinfo32 *ei32 = (audio_errinfo32 *)arg; if (cmd == SNDCTL_DSP_GETERROR32) { ei = &errinfo; } #endif bzero((void *)ei, sizeof(*ei)); if (wrch != NULL) { CHN_LOCK(wrch); ei->play_underruns = wrch->xruns; wrch->xruns = 0; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); ei->rec_overruns = rdch->xruns; rdch->xruns = 0; CHN_UNLOCK(rdch); } #ifdef COMPAT_FREEBSD32 if (cmd == SNDCTL_DSP_GETERROR32) { bzero((void *)ei32, sizeof(*ei32)); ei32->play_underruns = ei->play_underruns; ei32->rec_overruns = ei->rec_overruns; ei32->play_ptradjust = ei->play_ptradjust; ei32->rec_ptradjust = ei->rec_ptradjust; ei32->play_errorcount = ei->play_errorcount; ei32->rec_errorcount = ei->rec_errorcount; ei32->play_lasterror = ei->play_lasterror; ei32->rec_lasterror = ei->rec_lasterror; ei32->play_errorparm = ei->play_errorparm; ei32->rec_errorparm = ei->rec_errorparm; } #endif } break; case SNDCTL_DSP_SYNCGROUP: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SYNCSTART: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_syncstart(*arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_POLICY: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_policy(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_COOKEDMODE: PCM_ACQUIRE_QUICK(d); if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_SET_CHNORDER: PCM_ACQUIRE_QUICK(d); ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_GETCHANNELMASK: /* XXX vlc */ PCM_ACQUIRE_QUICK(d); ret = dsp_oss_getchannelmask(wrch, rdch, (int *)arg); PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_BIND_CHANNEL: /* XXX what?!? */ ret = EINVAL; break; #ifdef OSSV4_EXPERIMENT /* * XXX The following ioctls are not yet supported and just return * EINVAL. */ case SNDCTL_DSP_GETOPEAKS: case SNDCTL_DSP_GETIPEAKS: chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch; if (chn == NULL) ret = EINVAL; else { oss_peaks_t *op = (oss_peaks_t *)arg; int lpeak, rpeak; CHN_LOCK(chn); ret = chn_getpeaks(chn, &lpeak, &rpeak); if (ret == -1) ret = EINVAL; else { (*op)[0] = lpeak; (*op)[1] = rpeak; } CHN_UNLOCK(chn); } break; /* * XXX Once implemented, revisit this for proper cv protection * (if necessary). */ case SNDCTL_GETLABEL: ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg); break; case SNDCTL_SETLABEL: ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg); break; case SNDCTL_GETSONG: ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg); break; case SNDCTL_SETSONG: ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg); break; case SNDCTL_SETNAME: ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); break; #endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: /* undocumented */ case SNDCTL_DSP_SUBDIVIDE: case SOUND_PCM_WRITE_FILTER: case SOUND_PCM_READ_FILTER: /* dunno what these do, don't sound important */ default: DEB(printf("default ioctl fn 0x%08lx fail\n", cmd)); ret = EINVAL; break; } PCM_GIANT_LEAVE(d); return (ret); } static int dsp_poll(struct cdev *i_dev, int events, struct thread *td) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch; int ret, e, err; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) { /* XXX many clients don't understand POLLNVAL */ return (events & (POLLHUP | POLLPRI | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)); } PCM_GIANT_ENTER(d); ret = 0; dsp_lock_chans(priv, FREAD | FWRITE); wrch = priv->wrch; rdch = priv->rdch; if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) { e = (events & (POLLOUT | POLLWRNORM)); if (e) ret |= chn_poll(wrch, e, td); } if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) { e = (events & (POLLIN | POLLRDNORM)); if (e) ret |= chn_poll(rdch, e, td); } dsp_unlock_chans(priv, FREAD | FWRITE); PCM_GIANT_LEAVE(d); return (ret); } static int dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { /* * offset is in range due to checks in dsp_mmap_single(). * XXX memattr is not honored. */ *paddr = vtophys(offset); return (0); } static int dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, vm_size_t size, struct vm_object **object, int nprot) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch, *c; int err; /* * Reject PROT_EXEC by default. It just doesn't makes sense. * Unfortunately, we have to give up this one due to linux_mmap * changes. * * https://lists.freebsd.org/pipermail/freebsd-emulation/2007-June/003698.html * */ #ifdef SV_ABI_LINUX if ((nprot & PROT_EXEC) && (dsp_mmap_allow_prot_exec < 0 || (dsp_mmap_allow_prot_exec == 0 && SV_CURPROC_ABI() != SV_ABI_LINUX))) #else if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec < 1) #endif return (EINVAL); /* * PROT_READ (alone) selects the input buffer. * PROT_WRITE (alone) selects the output buffer. * PROT_WRITE|PROT_READ together select the output buffer. */ if ((nprot & (PROT_READ | PROT_WRITE)) == 0) return (EINVAL); if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) return (EINVAL); PCM_GIANT_ENTER(d); dsp_lock_chans(priv, FREAD | FWRITE); wrch = priv->wrch; rdch = priv->rdch; c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch; if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) || - (*offset + size) > sndbuf_getallocsize(c->bufsoft) || + (*offset + size) > c->bufsoft->allocsize || (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { dsp_unlock_chans(priv, FREAD | FWRITE); PCM_GIANT_EXIT(d); return (EINVAL); } if (wrch != NULL) wrch->flags |= CHN_F_MMAP; if (rdch != NULL) rdch->flags |= CHN_F_MMAP; *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); dsp_unlock_chans(priv, FREAD | FWRITE); *object = vm_pager_allocate(OBJT_DEVICE, i_dev, size, nprot, *offset, curthread->td_ucred); PCM_GIANT_LEAVE(d); if (*object == NULL) return (EINVAL); return (0); } static const char *dsp_aliases[] = { "dsp_ac3", "dsp_mmap", "dsp_multich", "dsp_spdifout", "dsp_spdifin", }; static void dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { struct snddev_info *d; size_t i; if (*dev != NULL) return; if (strcmp(name, "dsp") == 0 && dsp_basename_clone) goto found; for (i = 0; i < nitems(dsp_aliases); i++) { if (strcmp(name, dsp_aliases[i]) == 0) goto found; } return; found: bus_topo_lock(); d = devclass_get_softc(pcm_devclass, snd_unit); /* * If we only have a single soundcard attached and we detach it right * before entering dsp_clone(), there is a chance pcm_unregister() will * have returned already, meaning it will have set snd_unit to -1, and * thus devclass_get_softc() will return NULL here. */ if (DSP_REGISTERED(d)) { *dev = d->dsp_dev; dev_ref(*dev); } bus_topo_unlock(); } static void dsp_sysinit(void *p) { if (dsp_ehtag != NULL) return; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { if (dsp_ehtag == NULL) return; EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); static void dsp_oss_audioinfo_unavail(oss_audioinfo *ai, int unit) { bzero(ai, sizeof(*ai)); ai->dev = unit; snprintf(ai->name, sizeof(ai->name), "pcm%d (unavailable)", unit); ai->pid = -1; strlcpy(ai->cmd, CHN_COMM_UNUSED, sizeof(ai->cmd)); ai->card_number = unit; ai->port_number = unit; ai->mixer_dev = -1; ai->legacy_device = unit; } /** * @brief Handler for SNDCTL_AUDIOINFO. * * Gathers information about the audio device specified in ai->dev. If * ai->dev == -1, then this function gathers information about the current * device. If the call comes in on a non-audio device and ai->dev == -1, * return EINVAL. * * This routine is supposed to go practically straight to the hardware, * getting capabilities directly from the sound card driver, side-stepping * the intermediate channel interface. * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @param dev device on which the ioctl was issued * @param ai ioctl request data container * @param ex flag to distinguish between SNDCTL_AUDIOINFO from * SNDCTL_AUDIOINFO_EX * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device */ int dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai, bool ex) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, minch, maxch, unit; /* * If probing the device that received the ioctl, make sure it's a * DSP device. (Users may use this ioctl with /dev/mixer and * /dev/midi.) */ if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) return (EINVAL); bus_topo_lock(); for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d)) { if ((ai->dev == -1 && unit == snd_unit) || ai->dev == unit) { dsp_oss_audioinfo_unavail(ai, unit); bus_topo_unlock(); return (0); } else { d = NULL; continue; } } PCM_UNLOCKASSERT(d); PCM_LOCK(d); if ((ai->dev == -1 && d->dsp_dev == i_dev) || (ai->dev == unit)) { PCM_UNLOCK(d); break; } else { PCM_UNLOCK(d); d = NULL; } } bus_topo_unlock(); /* Exhausted the search -- nothing is locked, so return. */ if (d == NULL) return (EINVAL); /* XXX Need Giant magic entry ??? */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); bzero((void *)ai, sizeof(oss_audioinfo)); ai->dev = unit; strlcpy(ai->name, device_get_desc(d->dev), sizeof(ai->name)); ai->pid = -1; strlcpy(ai->cmd, CHN_COMM_UNKNOWN, sizeof(ai->cmd)); ai->card_number = unit; ai->port_number = unit; ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; ai->legacy_device = unit; snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); ai->enabled = device_is_attached(d->dev) ? 1 : 0; ai->next_play_engine = 0; ai->next_rec_engine = 0; ai->busy = 0; ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; ai->iformats = 0; ai->oformats = 0; ai->min_rate = INT_MAX; ai->max_rate = 0; ai->min_channels = INT_MAX; ai->max_channels = 0; /* Gather global information about the device. */ CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); /* * Skip physical channels if we are servicing SNDCTL_AUDIOINFO, * or VCHANs if we are servicing SNDCTL_AUDIOINFO_EX. * * For SNDCTL_AUDIOINFO do not skip the physical channels if * there are no VCHANs. */ if ((ex && (ch->flags & CHN_F_VIRTUAL) != 0) || ((!ex && (ch->flags & CHN_F_VIRTUAL) == 0) && (d->pvchancount > 0 || d->rvchancount > 0))) { CHN_UNLOCK(ch); continue; } if ((ch->flags & CHN_F_BUSY) == 0) { ai->busy |= (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; } ai->caps |= ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); caps = chn_getcaps(ch); minch = INT_MAX; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= AFMT_ENCODING(caps->fmtlist[i]); minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); } if (ch->direction == PCMDIR_PLAY) ai->oformats |= fmts; else ai->iformats |= fmts; if (ex || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { ai->min_rate = min(ai->min_rate, caps->minspeed); ai->max_rate = max(ai->max_rate, caps->maxspeed); } else { ai->min_rate = min(ai->min_rate, feeder_rate_min); ai->max_rate = max(ai->max_rate, feeder_rate_max); } ai->min_channels = min(ai->min_channels, minch); ai->max_channels = max(ai->max_channels, maxch); CHN_UNLOCK(ch); } if (ai->min_rate == INT_MAX) ai->min_rate = 0; if (ai->min_channels == INT_MAX) ai->min_channels = 0; PCM_UNLOCK(d); return (0); } static int dsp_oss_engineinfo_cb(void *data, void *arg) { struct dsp_cdevpriv *priv = data; struct pcm_channel *ch = arg; if (DSP_REGISTERED(priv->sc) && (ch == priv->rdch || ch == priv->wrch)) return (1); return (0); } /** * @brief Handler for SNDCTL_ENGINEINFO * * Gathers information about the audio device's engine specified in ai->dev. * If ai->dev == -1, then this function gathers information about the current * device. If the call comes in on a non-audio device and ai->dev == -1, * return EINVAL. * * This routine is supposed to go practically straight to the hardware, * getting capabilities directly from the sound card driver, side-stepping * the intermediate channel interface. * * @note * Calling threads must not hold any snddev_info or pcm_channel locks. * * @param dev device on which the ioctl was issued * @param ai ioctl request data container * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device */ int dsp_oss_engineinfo(struct cdev *i_dev, oss_audioinfo *ai) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, nchan, *rates, minch, maxch, unit; /* * If probing the device that received the ioctl, make sure it's a * DSP device. (Users may use this ioctl with /dev/mixer and * /dev/midi.) */ if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) return (EINVAL); ch = NULL; nchan = 0; /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. */ bus_topo_lock(); for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); if (!PCM_REGISTERED(d)) continue; /* XXX Need Giant magic entry ??? */ /* See the note in function docblock */ PCM_UNLOCKASSERT(d); PCM_LOCK(d); CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); if ((ai->dev == -1 && devfs_foreach_cdevpriv( i_dev, dsp_oss_engineinfo_cb, ch) != 0) || ai->dev == nchan) break; CHN_UNLOCK(ch); ++nchan; } if (ch == NULL) { PCM_UNLOCK(d); continue; } /* * At this point, the following synchronization stuff * has happened: * - a specific PCM device is locked. * - a specific audio channel has been locked, so be * sure to unlock when exiting; */ caps = chn_getcaps(ch); /* * With all handles collected, zero out the user's * container and begin filling in its fields. */ bzero((void *)ai, sizeof(oss_audioinfo)); ai->dev = nchan; strlcpy(ai->name, ch->name, sizeof(ai->name)); if ((ch->flags & CHN_F_BUSY) == 0) ai->busy = 0; else ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; ai->pid = ch->pid; strlcpy(ai->cmd, ch->comm, sizeof(ai->cmd)); /* * These flags stolen from SNDCTL_DSP_GETCAPS handler. * Note, however, that a single channel operates in * only one direction, so PCM_CAP_DUPLEX is out. */ /** * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep * these in pcmchan::caps? */ ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); /* * Collect formats supported @b natively by the * device. Also determine min/max channels. */ minch = INT_MAX; maxch = 0; fmts = 0; for (i = 0; caps->fmtlist[i]; i++) { fmts |= AFMT_ENCODING(caps->fmtlist[i]); minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); } if (ch->direction == PCMDIR_PLAY) ai->oformats = fmts; else ai->iformats = fmts; /** * @note * @c magic - OSSv4 docs: "Reserved for internal use * by OSS." * * @par * @c card_number - OSSv4 docs: "Number of the sound * card where this device belongs or -1 if this * information is not available. Applications * should normally not use this field for any * purpose." */ ai->card_number = unit; /** * @todo @c song_name - depends first on * SNDCTL_[GS]ETSONG @todo @c label - depends * on SNDCTL_[GS]ETLABEL * @todo @c port_number - routing information? */ ai->port_number = unit; ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; /** * @note * @c legacy_device - OSSv4 docs: "Obsolete." */ ai->legacy_device = unit; snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); ai->enabled = device_is_attached(d->dev) ? 1 : 0; /** * @note * @c flags - OSSv4 docs: "Reserved for future use." * * @note * @c binding - OSSv4 docs: "Reserved for future use." * * @todo @c handle - haven't decided how to generate * this yet; bus, vendor, device IDs? */ if ((ch->flags & CHN_F_EXCLUSIVE) || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { ai->min_rate = caps->minspeed; ai->max_rate = caps->maxspeed; } else { ai->min_rate = feeder_rate_min; ai->max_rate = feeder_rate_max; } ai->min_channels = minch; ai->max_channels = maxch; ai->nrates = chn_getrates(ch, &rates); if (ai->nrates > OSS_MAX_SAMPLE_RATES) ai->nrates = OSS_MAX_SAMPLE_RATES; for (i = 0; i < ai->nrates; i++) ai->rates[i] = rates[i]; ai->next_play_engine = 0; ai->next_rec_engine = 0; CHN_UNLOCK(ch); PCM_UNLOCK(d); bus_topo_unlock(); return (0); } bus_topo_unlock(); /* Exhausted the search -- nothing is locked, so return. */ return (EINVAL); } /** * @brief Assigns a PCM channel to a sync group. * * Sync groups are used to enable audio operations on multiple devices * simultaneously. They may be used with any number of devices and may * span across applications. Devices are added to groups with * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the * SNDCTL_DSP_SYNCSTART ioctl. * * If the @c id field of the @c group parameter is set to zero, then a new * sync group is created. Otherwise, wrch and rdch (if set) are added to * the group specified. * * @todo As far as memory allocation, should we assume that things are * okay and allocate with M_WAITOK before acquiring channel locks, * freeing later if not? * * @param wrch output channel associated w/ device (if any) * @param rdch input channel associated w/ device (if any) * @param group Sync group parameters * * @retval 0 success * @retval non-zero error to be propagated upstream */ static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group) { struct pcmchan_syncmember *smrd, *smwr; struct pcmchan_syncgroup *sg; int ret, sg_ids[3]; smrd = NULL; smwr = NULL; sg = NULL; ret = 0; /* * Free_unr() may sleep, so store released syncgroup IDs until after * all locks are released. */ sg_ids[0] = sg_ids[1] = sg_ids[2] = 0; PCM_SG_LOCK(); /* * - Insert channel(s) into group's member list. * - Set CHN_F_NOTRIGGER on channel(s). * - Stop channel(s). */ /* * If device's channels are already mapped to a group, unmap them. */ if (wrch) { CHN_LOCK(wrch); sg_ids[0] = chn_syncdestroy(wrch); } if (rdch) { CHN_LOCK(rdch); sg_ids[1] = chn_syncdestroy(rdch); } /* * Verify that mode matches character device properites. * - Bail if PCM_ENABLE_OUTPUT && wrch == NULL. * - Bail if PCM_ENABLE_INPUT && rdch == NULL. */ if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) || ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) { ret = EINVAL; goto out; } /* * An id of zero indicates the user wants to create a new * syncgroup. */ if (group->id == 0) { sg = malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); if (sg != NULL) { SLIST_INIT(&sg->members); sg->id = alloc_unr(pcmsg_unrhdr); group->id = sg->id; SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link); } else ret = ENOMEM; } else { SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { if (sg->id == group->id) break; } if (sg == NULL) ret = EINVAL; } /* Couldn't create or find a syncgroup. Fail. */ if (sg == NULL) goto out; /* * Allocate a syncmember, assign it and a channel together, and * insert into syncgroup. */ if (group->mode & PCM_ENABLE_INPUT) { smrd = malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); if (smrd == NULL) { ret = ENOMEM; goto out; } SLIST_INSERT_HEAD(&sg->members, smrd, link); smrd->parent = sg; smrd->ch = rdch; chn_abort(rdch); rdch->flags |= CHN_F_NOTRIGGER; rdch->sm = smrd; } if (group->mode & PCM_ENABLE_OUTPUT) { smwr = malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); if (smwr == NULL) { ret = ENOMEM; goto out; } SLIST_INSERT_HEAD(&sg->members, smwr, link); smwr->parent = sg; smwr->ch = wrch; chn_abort(wrch); wrch->flags |= CHN_F_NOTRIGGER; wrch->sm = smwr; } out: if (ret != 0) { if (smrd != NULL) free(smrd, M_DEVBUF); if ((sg != NULL) && SLIST_EMPTY(&sg->members)) { sg_ids[2] = sg->id; SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); free(sg, M_DEVBUF); } if (wrch) wrch->sm = NULL; if (rdch) rdch->sm = NULL; } if (wrch) CHN_UNLOCK(wrch); if (rdch) CHN_UNLOCK(rdch); PCM_SG_UNLOCK(); if (sg_ids[0]) free_unr(pcmsg_unrhdr, sg_ids[0]); if (sg_ids[1]) free_unr(pcmsg_unrhdr, sg_ids[1]); if (sg_ids[2]) free_unr(pcmsg_unrhdr, sg_ids[2]); return (ret); } /** * @brief Launch a sync group into action * * Sync groups are established via SNDCTL_DSP_SYNCGROUP. This function * iterates over all members, triggering them along the way. * * @note Caller must not hold any channel locks. * * @param sg_id sync group identifier * * @retval 0 success * @retval non-zero error worthy of propagating upstream to user */ static int dsp_oss_syncstart(int sg_id) { struct pcmchan_syncmember *sm, *sm_tmp; struct pcmchan_syncgroup *sg; struct pcm_channel *c; int ret, needlocks; /* Get the synclists lock */ PCM_SG_LOCK(); do { ret = 0; needlocks = 0; /* Search for syncgroup by ID */ SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { if (sg->id == sg_id) break; } /* Return EINVAL if not found */ if (sg == NULL) { ret = EINVAL; break; } /* Any removals resulting in an empty group should've handled this */ KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup")); /* * Attempt to lock all member channels - if any are already * locked, unlock those acquired, sleep for a bit, and try * again. */ SLIST_FOREACH(sm, &sg->members, link) { if (CHN_TRYLOCK(sm->ch) == 0) { int timo = hz * 5/1000; if (timo < 1) timo = 1; /* Release all locked channels so far, retry */ SLIST_FOREACH(sm_tmp, &sg->members, link) { /* sm is the member already locked */ if (sm == sm_tmp) break; CHN_UNLOCK(sm_tmp->ch); } /** @todo Is PRIBIO correct/ */ ret = msleep(sm, &snd_pcm_syncgroups_mtx, PRIBIO | PCATCH, "pcmsg", timo); if (ret == EINTR || ret == ERESTART) break; needlocks = 1; ret = 0; /* Assumes ret == EAGAIN... */ } } } while (needlocks && ret == 0); /* Proceed only if no errors encountered. */ if (ret == 0) { /* Launch channels */ while ((sm = SLIST_FIRST(&sg->members)) != NULL) { SLIST_REMOVE_HEAD(&sg->members, link); c = sm->ch; c->sm = NULL; chn_start(c, 1); c->flags &= ~CHN_F_NOTRIGGER; CHN_UNLOCK(c); free(sm, M_DEVBUF); } SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); free(sg, M_DEVBUF); } PCM_SG_UNLOCK(); /* * Free_unr() may sleep, so be sure to give up the syncgroup lock * first. */ if (ret == 0) free_unr(pcmsg_unrhdr, sg_id); return (ret); } /** * @brief Handler for SNDCTL_DSP_POLICY * * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment * size and count like with SNDCTL_DSP_SETFRAGMENT. Instead of the user * specifying those two parameters, s/he simply selects a number from 0..10 * which corresponds to a buffer size. Smaller numbers request smaller * buffers with lower latencies (at greater overhead from more frequent * interrupts), while greater numbers behave in the opposite manner. * * The 4Front spec states that a value of 5 should be the default. However, * this implementation deviates slightly by using a linear scale without * consulting drivers. I.e., even though drivers may have different default * buffer sizes, a policy argument of 5 will have the same result across * all drivers. * * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for * more information. * * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to * work with hardware drivers directly. * * @note PCM channel arguments must not be locked by caller. * * @param wrch Pointer to opened playback channel (optional; may be NULL) * @param rdch " recording channel (optional; may be NULL) * @param policy Integer from [0:10] * * @retval 0 constant (for now) */ static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) { int ret; if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) return (EIO); /* Default: success */ ret = 0; if (rdch) { CHN_LOCK(rdch); ret = chn_setlatency(rdch, policy); CHN_UNLOCK(rdch); } if (wrch && ret == 0) { CHN_LOCK(wrch); ret = chn_setlatency(wrch, policy); CHN_UNLOCK(wrch); } if (ret) ret = EIO; return (ret); } /** * @brief Enable or disable "cooked" mode * * This is a handler for @c SNDCTL_DSP_COOKEDMODE. When in cooked mode, which * is the default, the sound system handles rate and format conversions * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only * operates with 44100Hz/16bit/signed samples). * * Disabling cooked mode is intended for applications wanting to mmap() * a sound card's buffer space directly, bypassing the FreeBSD 2-stage * feeder architecture, presumably to gain as much control over audio * hardware as possible. * * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html * for more details. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param enabled 0 = raw mode, 1 = cooked mode * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled) { /* * XXX I just don't get it. Why don't they call it * "BITPERFECT" ~ SNDCTL_DSP_BITPERFECT !?!?. * This is just plain so confusing, incoherent, * . */ if (!(enabled == 1 || enabled == 0)) return (EINVAL); /* * I won't give in. I'm inverting its logic here and now. * Brag all you want, but "BITPERFECT" should be the better * term here. */ enabled ^= 0x00000001; if (wrch != NULL) { CHN_LOCK(wrch); wrch->flags &= ~CHN_F_BITPERFECT; wrch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); rdch->flags &= ~CHN_F_BITPERFECT; rdch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000; CHN_UNLOCK(rdch); } return (0); } /** * @brief Retrieve channel interleaving order * * This is the handler for @c SNDCTL_DSP_GET_CHNORDER. * * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_DSP_GET_CHNORDER. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param map channel map (result will be stored there) * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) { struct pcm_channel *ch; int ret; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getorder(ch, map); CHN_UNLOCK(ch); } else ret = EINVAL; return (ret); } /** * @brief Specify channel interleaving order * * This is the handler for @c SNDCTL_DSP_SET_CHNORDER. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support @c SNDCTL_DSP_SET_CHNORDER. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param map channel map * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) { int ret; ret = 0; if (wrch != NULL) { CHN_LOCK(wrch); ret = chn_oss_setorder(wrch, map); CHN_UNLOCK(wrch); } if (ret == 0 && rdch != NULL) { CHN_LOCK(rdch); ret = chn_oss_setorder(rdch, map); CHN_UNLOCK(rdch); } return (ret); } static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask) { struct pcm_channel *ch; uint32_t chnmask; int ret; chnmask = 0; ch = (wrch != NULL) ? wrch : rdch; if (ch != NULL) { CHN_LOCK(ch); ret = chn_oss_getmask(ch, &chnmask); CHN_UNLOCK(ch); } else ret = EINVAL; if (ret == 0) *mask = chnmask; return (ret); } static void dsp_kqdetach(struct knote *kn) { struct pcm_channel *ch = kn->kn_hook; if (ch == NULL) return; CHN_LOCK(ch); knlist_remove(&ch->bufsoft->sel.si_note, kn, 1); CHN_UNLOCK(ch); } static int dsp_kqevent(struct knote *kn, long hint) { struct pcm_channel *ch = kn->kn_hook; CHN_LOCKASSERT(ch); if (ch->flags & CHN_F_DEAD) { kn->kn_flags |= EV_EOF; return (1); } kn->kn_data = 0; if (chn_polltrigger(ch)) { if (kn->kn_filter == EVFILT_READ) kn->kn_data = sndbuf_getready(ch->bufsoft); else kn->kn_data = sndbuf_getfree(ch->bufsoft); } return (kn->kn_data > 0); } static const struct filterops dsp_filtops = { .f_isfd = 1, .f_detach = dsp_kqdetach, .f_event = dsp_kqevent, }; static int dsp_kqfilter(struct cdev *dev, struct knote *kn) { struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *ch; int err = 0; if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); switch (kn->kn_filter) { case EVFILT_READ: ch = priv->rdch; break; case EVFILT_WRITE: ch = priv->wrch; break; default: kn->kn_hook = NULL; err = EINVAL; ch = NULL; break; } if (ch != NULL) { kn->kn_fop = &dsp_filtops; CHN_LOCK(ch); knlist_add(&ch->bufsoft->sel.si_note, kn, 1); CHN_UNLOCK(ch); kn->kn_hook = ch; } else err = EINVAL; PCM_GIANT_LEAVE(d); return (err); } #ifdef OSSV4_EXPERIMENT /** * @brief Retrieve an audio device's label * * This is a handler for the @c SNDCTL_GETLABEL ioctl. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html * for more details. * * From Hannu@4Front: "For example ossxmix (just like some HW mixer * consoles) can show variable "labels" for certain controls. By default * the application name (say quake) is shown as the label but * applications may change the labels themselves." * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support @c SNDCTL_GETLABEL. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param label label gets copied here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) { return (EINVAL); } /** * @brief Specify an audio device's label * * This is a handler for the @c SNDCTL_SETLABEL ioctl. Please see the * comments for @c dsp_oss_getlabel immediately above. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETLABEL. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param label label gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) { return (EINVAL); } /** * @brief Retrieve name of currently played song * * This is a handler for the @c SNDCTL_GETSONG ioctl. Audio players could * tell the system the name of the currently playing song, which would be * visible in @c /dev/sndstat. * * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_GETSONG. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param song song name gets copied here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) { return (EINVAL); } /** * @brief Retrieve name of currently played song * * This is a handler for the @c SNDCTL_SETSONG ioctl. Audio players could * tell the system the name of the currently playing song, which would be * visible in @c /dev/sndstat. * * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html * for more details. * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETSONG. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param song song name gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) { return (EINVAL); } /** * @brief Rename a device * * This is a handler for the @c SNDCTL_SETNAME ioctl. * * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for * more details. * * From Hannu@4Front: "This call is used to change the device name * reported in /dev/sndstat and ossinfo. So instead of using some generic * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull * name depending on the current context (for example 'OSS virtual wave table * synth' or 'VoIP link to London')." * * @note As the ioctl definition is still under construction, FreeBSD * does not currently support SNDCTL_SETNAME. * * @param wrch playback channel (optional; may be NULL) * @param rdch recording channel (optional; may be NULL) * @param name new device name gets copied from here * * @retval EINVAL Operation not yet supported. */ static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name) { return (EINVAL); } #endif /* !OSSV4_EXPERIMENT */ diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c index af3ada441e48..2ec0ee6fb3fd 100644 --- a/sys/dev/sound/pcm/feeder.c +++ b/sys/dev/sound/pcm/feeder.c @@ -1,475 +1,471 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "feeder_if.h" static MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); #define MAXFEEDERS 256 struct feedertab_entry { SLIST_ENTRY(feedertab_entry) link; struct feeder_class *feederclass; struct pcm_feederdesc *desc; int idx; }; static SLIST_HEAD(, feedertab_entry) feedertab; static int feedercnt = 0; /*****************************************************************************/ static void feeder_register_root(void *p) { struct feeder_class *fc = p; struct feedertab_entry *fte; MPASS(feedercnt == 0); KASSERT(fc->desc == NULL, ("first feeder not root: %s", fc->name)); SLIST_INIT(&feedertab); fte = malloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); fte->feederclass = fc; fte->desc = NULL; fte->idx = feedercnt; SLIST_INSERT_HEAD(&feedertab, fte, link); feedercnt++; } void feeder_register(void *p) { struct feeder_class *fc = p; struct feedertab_entry *fte; int i; KASSERT(fc->desc != NULL, ("feeder '%s' has no descriptor", fc->name)); /* * beyond this point failure is non-fatal but may result in some * translations being unavailable */ i = 0; while ((feedercnt < MAXFEEDERS) && (fc->desc[i].type > 0)) { fte = malloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); fte->feederclass = fc; fte->desc = &fc->desc[i]; fte->idx = feedercnt; fte->desc->idx = feedercnt; SLIST_INSERT_HEAD(&feedertab, fte, link); i++; } feedercnt++; if (feedercnt >= MAXFEEDERS) { printf("MAXFEEDERS (%d >= %d) exceeded\n", feedercnt, MAXFEEDERS); } } static void feeder_unregisterall(void *p) { struct feedertab_entry *fte, *next; next = SLIST_FIRST(&feedertab); while (next != NULL) { fte = next; next = SLIST_NEXT(fte, link); free(fte, M_FEEDER); } } static int cmpdesc(struct pcm_feederdesc *n, struct pcm_feederdesc *m) { return ((n->type == m->type) && ((n->in == 0) || (n->in == m->in)) && ((n->out == 0) || (n->out == m->out)) && (n->flags == m->flags)); } static void feeder_destroy(struct pcm_feeder *f) { FEEDER_FREE(f); kobj_delete((kobj_t)f, M_FEEDER); } static struct pcm_feeder * feeder_create(struct feeder_class *fc, struct pcm_feederdesc *desc) { struct pcm_feeder *f; int err; f = (struct pcm_feeder *)kobj_create((kobj_class_t)fc, M_FEEDER, M_NOWAIT | M_ZERO); if (f == NULL) return NULL; f->data = fc->data; f->source = NULL; f->parent = NULL; f->class = fc; f->desc = &(f->desc_static); if (desc) { *(f->desc) = *desc; } else { f->desc->type = FEEDER_ROOT; f->desc->in = 0; f->desc->out = 0; f->desc->flags = 0; f->desc->idx = 0; } err = FEEDER_INIT(f); if (err) { printf("feeder_init(%p) on %s returned %d\n", f, fc->name, err); feeder_destroy(f); return NULL; } return f; } struct feeder_class * feeder_getclass(struct pcm_feederdesc *desc) { struct feedertab_entry *fte; SLIST_FOREACH(fte, &feedertab, link) { if ((desc == NULL) && (fte->desc == NULL)) return fte->feederclass; if ((fte->desc != NULL) && (desc != NULL) && cmpdesc(desc, fte->desc)) return fte->feederclass; } return NULL; } int feeder_add(struct pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc) { struct pcm_feeder *nf; nf = feeder_create(fc, desc); if (nf == NULL) return ENOSPC; nf->source = c->feeder; if (c->feeder != NULL) c->feeder->parent = nf; c->feeder = nf; return 0; } void feeder_remove(struct pcm_channel *c) { struct pcm_feeder *f; while (c->feeder != NULL) { f = c->feeder; c->feeder = c->feeder->source; feeder_destroy(f); } } struct pcm_feeder * feeder_find(struct pcm_channel *c, u_int32_t type) { struct pcm_feeder *f; f = c->feeder; while (f != NULL) { if (f->desc->type == type) return f; f = f->source; } return NULL; } /* * 14bit format scoring * -------------------- * * 13 12 11 10 9 8 2 1 0 offset * +---+---+---+---+---+---+-------------+---+---+ * | X | X | X | X | X | X | X X X X X X | X | X | * +---+---+---+---+---+---+-------------+---+---+ * | | | | | | | | | * | | | | | | | | +--> signed? * | | | | | | | | * | | | | | | | +------> bigendian? * | | | | | | | * | | | | | | +---------------> total channels * | | | | | | * | | | | | +------------------------> AFMT_A_LAW * | | | | | * | | | | +----------------------------> AFMT_MU_LAW * | | | | * | | | +--------------------------------> AFMT_8BIT * | | | * | | +------------------------------------> AFMT_16BIT * | | * | +----------------------------------------> AFMT_24BIT * | * +--------------------------------------------> AFMT_32BIT */ #define score_signeq(s1, s2) (((s1) & 0x1) == ((s2) & 0x1)) #define score_endianeq(s1, s2) (((s1) & 0x2) == ((s2) & 0x2)) #define score_cheq(s1, s2) (((s1) & 0xfc) == ((s2) & 0xfc)) #define score_chgt(s1, s2) (((s1) & 0xfc) > ((s2) & 0xfc)) #define score_chlt(s1, s2) (((s1) & 0xfc) < ((s2) & 0xfc)) #define score_val(s1) ((s1) & 0x3f00) #define score_cse(s1) ((s1) & 0x7f) u_int32_t snd_fmtscore(u_int32_t fmt) { u_int32_t ret; ret = 0; if (fmt & AFMT_SIGNED) ret |= 1 << 0; if (fmt & AFMT_BIGENDIAN) ret |= 1 << 1; /*if (fmt & AFMT_STEREO) ret |= (2 & 0x3f) << 2; else ret |= (1 & 0x3f) << 2;*/ ret |= (AFMT_CHANNEL(fmt) & 0x3f) << 2; if (fmt & AFMT_A_LAW) ret |= 1 << 8; else if (fmt & AFMT_MU_LAW) ret |= 1 << 9; else if (fmt & AFMT_8BIT) ret |= 1 << 10; else if (fmt & AFMT_16BIT) ret |= 1 << 11; else if (fmt & AFMT_24BIT) ret |= 1 << 12; else if (fmt & AFMT_32BIT) ret |= 1 << 13; return ret; } static u_int32_t snd_fmtbestfunc(u_int32_t fmt, u_int32_t *fmts, int cheq) { u_int32_t best, score, score2, oldscore; int i; if (fmt == 0 || fmts == NULL || fmts[0] == 0) return 0; if (snd_fmtvalid(fmt, fmts)) return fmt; best = 0; score = snd_fmtscore(fmt); oldscore = 0; for (i = 0; fmts[i] != 0; i++) { score2 = snd_fmtscore(fmts[i]); if (cheq && !score_cheq(score, score2) && (score_chlt(score2, score) || (oldscore != 0 && score_chgt(score2, oldscore)))) continue; if (oldscore == 0 || (score_val(score2) == score_val(score)) || (score_val(score2) == score_val(oldscore)) || (score_val(score2) > score_val(oldscore) && score_val(score2) < score_val(score)) || (score_val(score2) < score_val(oldscore) && score_val(score2) > score_val(score)) || (score_val(oldscore) < score_val(score) && score_val(score2) > score_val(oldscore))) { if (score_val(oldscore) != score_val(score2) || score_cse(score) == score_cse(score2) || ((score_cse(oldscore) != score_cse(score) && !score_endianeq(score, oldscore) && (score_endianeq(score, score2) || (!score_signeq(score, oldscore) && score_signeq(score, score2)))))) { best = fmts[i]; oldscore = score2; } } } return best; } u_int32_t snd_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) { return snd_fmtbestfunc(fmt, fmts, 0); } u_int32_t snd_fmtbestchannel(u_int32_t fmt, u_int32_t *fmts) { return snd_fmtbestfunc(fmt, fmts, 1); } u_int32_t snd_fmtbest(u_int32_t fmt, u_int32_t *fmts) { u_int32_t best1, best2; u_int32_t score, score1, score2; if (snd_fmtvalid(fmt, fmts)) return fmt; best1 = snd_fmtbestchannel(fmt, fmts); best2 = snd_fmtbestbit(fmt, fmts); if (best1 != 0 && best2 != 0 && best1 != best2) { /*if (fmt & AFMT_STEREO)*/ if (AFMT_CHANNEL(fmt) > 1) return best1; else { score = score_val(snd_fmtscore(fmt)); score1 = score_val(snd_fmtscore(best1)); score2 = score_val(snd_fmtscore(best2)); if (score1 == score2 || score1 == score) return best1; else if (score2 == score) return best2; else if (score1 > score2) return best1; return best2; } } else if (best2 == 0) return best1; else return best2; } void feeder_printchain(struct pcm_feeder *head) { struct pcm_feeder *f; printf("feeder chain (head @%p)\n", head); f = head; while (f != NULL) { printf("%s/%d @ %p\n", f->class->name, f->desc->idx, f); f = f->source; } printf("[end]\n\n"); } /*****************************************************************************/ static int feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) { struct snd_dbuf *src = source; int l, offset; KASSERT(count > 0, ("feed_root: count == 0")); if (++ch->feedcount == 0) ch->feedcount = 2; l = min(count, sndbuf_getready(src)); /* When recording only return as much data as available */ if (ch->direction == PCMDIR_REC) { sndbuf_dispose(src, buffer, l); return l; } offset = count - l; if (offset > 0) { if (snd_verbose > 3) printf("%s: (%s) %spending %d bytes " "(count=%d l=%d feed=%d)\n", __func__, (ch->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", (ch->feedcount == 1) ? "pre" : "ap", offset, count, l, ch->feedcount); if (ch->feedcount == 1) { - memset(buffer, - sndbuf_zerodata(sndbuf_getfmt(src)), - offset); + memset(buffer, sndbuf_zerodata(src->fmt), offset); if (l > 0) sndbuf_dispose(src, buffer + offset, l); else ch->feedcount--; } else { if (l > 0) sndbuf_dispose(src, buffer, l); - memset(buffer + l, - sndbuf_zerodata(sndbuf_getfmt(src)), - offset); + memset(buffer + l, sndbuf_zerodata(src->fmt), offset); if (!(ch->flags & CHN_F_CLOSING)) ch->xruns++; } } else if (l > 0) sndbuf_dispose(src, buffer, l); return count; } static kobj_method_t feeder_root_methods[] = { KOBJMETHOD(feeder_feed, feed_root), KOBJMETHOD_END }; static struct feeder_class feeder_root_class = { .name = "feeder_root", .methods = feeder_root_methods, .size = sizeof(struct pcm_feeder), .desc = NULL, .data = NULL, }; /* * Register the root feeder first so that pcm_addchan() and subsequent * functions can use it. */ SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register_root, &feeder_root_class); SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); diff --git a/sys/dev/sound/pcm/feeder_chain.c b/sys/dev/sound/pcm/feeder_chain.c index 56de32441de7..8b5d50760b32 100644 --- a/sys/dev/sound/pcm/feeder_chain.c +++ b/sys/dev/sound/pcm/feeder_chain.c @@ -1,869 +1,869 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008-2009 Ariff Abdullah * All rights reserved. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include "feeder_if.h" /* chain state */ struct feeder_chain_state { uint32_t afmt; /* audio format */ uint32_t rate; /* sampling rate */ struct pcmchan_matrix *matrix; /* matrix map */ }; /* * chain descriptor that will be passed around from the beginning until the * end of chain process. */ struct feeder_chain_desc { struct feeder_chain_state origin; /* original state */ struct feeder_chain_state current; /* current state */ struct feeder_chain_state target; /* target state */ struct pcm_feederdesc desc; /* feeder descriptor */ uint32_t afmt_ne; /* preferred native endian */ int mode; /* chain mode */ int use_eq; /* need EQ? */ int use_matrix; /* need channel matrixing? */ int use_volume; /* need softpcmvol? */ int dummy; /* dummy passthrough */ int expensive; /* possibly expensive */ }; #define FEEDER_CHAIN_LEAN 0 #define FEEDER_CHAIN_16 1 #define FEEDER_CHAIN_32 2 #define FEEDER_CHAIN_MULTI 3 #define FEEDER_CHAIN_FULLMULTI 4 #define FEEDER_CHAIN_LAST 5 #if defined(SND_FEEDER_FULL_MULTIFORMAT) #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_FULLMULTI #elif defined(SND_FEEDER_MULTIFORMAT) #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_MULTI #else #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_LEAN #endif /* * List of preferred formats that might be required during * processing. It will be decided through snd_fmtbest(). */ /* 'Lean' mode, signed 16 or 32 bit native endian. */ static uint32_t feeder_chain_formats_lean[] = { AFMT_S16_NE, AFMT_S32_NE, 0 }; /* Force everything to signed 16 bit native endian. */ static uint32_t feeder_chain_formats_16[] = { AFMT_S16_NE, 0 }; /* Force everything to signed 32 bit native endian. */ static uint32_t feeder_chain_formats_32[] = { AFMT_S32_NE, 0 }; /* Multiple choices, all except 8 bit. */ static uint32_t feeder_chain_formats_multi[] = { AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE, AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE, AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE, AFMT_F32_LE, AFMT_F32_BE, 0 }; /* Everything that is convertible. */ static uint32_t feeder_chain_formats_fullmulti[] = { AFMT_S8, AFMT_U8, AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE, AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE, AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE, AFMT_F32_LE, AFMT_F32_BE, 0 }; static uint32_t *feeder_chain_formats[FEEDER_CHAIN_LAST] = { [FEEDER_CHAIN_LEAN] = feeder_chain_formats_lean, [FEEDER_CHAIN_16] = feeder_chain_formats_16, [FEEDER_CHAIN_32] = feeder_chain_formats_32, [FEEDER_CHAIN_MULTI] = feeder_chain_formats_multi, [FEEDER_CHAIN_FULLMULTI] = feeder_chain_formats_fullmulti }; static int feeder_chain_mode = FEEDER_CHAIN_DEFAULT; #if defined(_KERNEL) && defined(SND_DEBUG) && defined(SND_FEEDER_FULL_MULTIFORMAT) SYSCTL_INT(_hw_snd, OID_AUTO, feeder_chain_mode, CTLFLAG_RWTUN, &feeder_chain_mode, 0, "feeder chain mode " "(0=lean, 1=16bit, 2=32bit, 3=multiformat, 4=fullmultiformat)"); #endif /* * feeder_build_format(): Chain any format converter. */ static int feeder_build_format(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feederdesc *desc; int ret; desc = &(cdesc->desc); desc->type = FEEDER_FORMAT; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_format\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = cdesc->target.afmt; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_format\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_FORMAT; cdesc->current.afmt = cdesc->target.afmt; return (0); } /* * feeder_build_formatne(): Chain format converter that suite best for native * endian format. */ static int feeder_build_formatne(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_chain_state otarget; int ret; if (cdesc->afmt_ne == 0 || AFMT_ENCODING(cdesc->current.afmt) == cdesc->afmt_ne) return (0); otarget = cdesc->target; cdesc->target = cdesc->current; cdesc->target.afmt = SND_FORMAT(cdesc->afmt_ne, cdesc->current.matrix->channels, cdesc->current.matrix->ext); ret = feeder_build_format(c, cdesc); if (ret != 0) return (ret); cdesc->target = otarget; return (0); } /* * feeder_build_rate(): Chain sample rate converter. */ static int feeder_build_rate(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_RATE; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_rate\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_rate\n", __func__); return (ret); } f = c->feeder; /* * If in 'dummy' mode (possibly due to passthrough mode), set the * conversion quality to the lowest possible (should be fastest) since * listener won't be hearing anything. Theoretically we can just * disable it, but that will cause weird runtime behaviour: * application appear to play something that is either too fast or too * slow. */ if (cdesc->dummy != 0) { ret = FEEDER_SET(f, FEEDRATE_QUALITY, 0); if (ret != 0) { device_printf(c->dev, "%s(): can't set resampling quality\n", __func__); return (ret); } } ret = FEEDER_SET(f, FEEDRATE_SRC, cdesc->current.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set source rate\n", __func__); return (ret); } ret = FEEDER_SET(f, FEEDRATE_DST, cdesc->target.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set destination rate\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_RATE; cdesc->current.rate = cdesc->target.rate; return (0); } /* * feeder_build_matrix(): Chain channel matrixing converter. */ static int feeder_build_matrix(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_MATRIX; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_matrix\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = SND_FORMAT(cdesc->current.afmt, cdesc->target.matrix->channels, cdesc->target.matrix->ext); ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_matrix\n", __func__); return (ret); } f = c->feeder; ret = feeder_matrix_setup(f, cdesc->current.matrix, cdesc->target.matrix); if (ret != 0) { device_printf(c->dev, "%s(): feeder_matrix_setup() failed\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_MATRIX; cdesc->current.afmt = desc->out; cdesc->current.matrix = cdesc->target.matrix; cdesc->use_matrix = 0; return (0); } /* * feeder_build_volume(): Chain soft volume. */ static int feeder_build_volume(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_VOLUME; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_volume\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_volume\n", __func__); return (ret); } f = c->feeder; /* * If in 'dummy' mode (possibly due to passthrough mode), set BYPASS * mode since listener won't be hearing anything. Theoretically we can * just disable it, but that will confuse volume per channel mixer. */ if (cdesc->dummy != 0) { ret = FEEDER_SET(f, FEEDVOLUME_STATE, FEEDVOLUME_BYPASS); if (ret != 0) { device_printf(c->dev, "%s(): can't set volume bypass\n", __func__); return (ret); } } ret = feeder_volume_apply_matrix(f, cdesc->current.matrix); if (ret != 0) { device_printf(c->dev, "%s(): feeder_volume_apply_matrix() failed\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_VOLUME; cdesc->use_volume = 0; return (0); } /* * feeder_build_eq(): Chain parametric software equalizer. */ static int feeder_build_eq(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_EQ; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_eq\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_eq\n", __func__); return (ret); } f = c->feeder; ret = FEEDER_SET(f, FEEDEQ_RATE, cdesc->current.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set rate on feeder_eq\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_EQ; cdesc->use_eq = 0; return (0); } /* * feeder_build_root(): Chain root feeder, the top, father of all. */ static int feeder_build_root(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; int ret; fc = feeder_getclass(NULL); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_root\n", __func__); return (ENOTSUP); } ret = feeder_add(c, fc, NULL); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_root\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_ROOT; c->feeder->desc->in = cdesc->current.afmt; c->feeder->desc->out = cdesc->current.afmt; return (0); } /* * feeder_build_mixer(): Chain software mixer for virtual channels. */ static int feeder_build_mixer(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feederdesc *desc; int ret; desc = &(cdesc->desc); desc->type = FEEDER_MIXER; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_mixer\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_mixer\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_MIXER; return (0); } /* Macrosses to ease our job doing stuffs later. */ #define FEEDER_BW(c, t) ((c)->t.matrix->channels * (c)->t.rate) #define FEEDRATE_UP(c) ((c)->target.rate > (c)->current.rate) #define FEEDRATE_DOWN(c) ((c)->target.rate < (c)->current.rate) #define FEEDRATE_REQUIRED(c) (FEEDRATE_UP(c) || FEEDRATE_DOWN(c)) #define FEEDMATRIX_UP(c) ((c)->target.matrix->channels > \ (c)->current.matrix->channels) #define FEEDMATRIX_DOWN(c) ((c)->target.matrix->channels < \ (c)->current.matrix->channels) #define FEEDMATRIX_REQUIRED(c) (FEEDMATRIX_UP(c) || \ FEEDMATRIX_DOWN(c) || (c)->use_matrix != 0) #define FEEDFORMAT_REQUIRED(c) (AFMT_ENCODING((c)->current.afmt) != \ AFMT_ENCODING((c)->target.afmt)) #define FEEDVOLUME_REQUIRED(c) ((c)->use_volume != 0) #define FEEDEQ_VALIDRATE(c, t) (feeder_eq_validrate((c)->t.rate) != 0) #define FEEDEQ_ECONOMY(c) (FEEDER_BW(c, current) < FEEDER_BW(c, target)) #define FEEDEQ_REQUIRED(c) ((c)->use_eq != 0 && \ FEEDEQ_VALIDRATE(c, current)) #define FEEDFORMAT_NE_REQUIRED(c) \ ((c)->afmt_ne != AFMT_S32_NE && \ (((c)->mode == FEEDER_CHAIN_16 && \ AFMT_ENCODING((c)->current.afmt) != AFMT_S16_NE) || \ ((c)->mode == FEEDER_CHAIN_32 && \ AFMT_ENCODING((c)->current.afmt) != AFMT_S32_NE) || \ (c)->mode == FEEDER_CHAIN_FULLMULTI || \ ((c)->mode == FEEDER_CHAIN_MULTI && \ ((c)->current.afmt & AFMT_8BIT)) || \ ((c)->mode == FEEDER_CHAIN_LEAN && \ !((c)->current.afmt & (AFMT_S16_NE | AFMT_S32_NE))))) static void feeder_default_matrix(struct pcmchan_matrix *m, uint32_t fmt, int id) { int x; memset(m, 0, sizeof(*m)); m->id = id; m->channels = AFMT_CHANNEL(fmt); m->ext = AFMT_EXTCHANNEL(fmt); for (x = 0; x != SND_CHN_T_MAX; x++) m->offset[x] = -1; } int feeder_chain(struct pcm_channel *c) { struct snddev_info *d; struct pcmchan_caps *caps; struct feeder_chain_desc cdesc; struct pcmchan_matrix *hwmatrix, *softmatrix; uint32_t hwfmt, softfmt; int ret; CHN_LOCKASSERT(c); /* Remove everything first. */ feeder_remove(c); KASSERT(c->feeder == NULL, ("feeder chain not empty")); /* clear and populate chain descriptor. */ bzero(&cdesc, sizeof(cdesc)); switch (feeder_chain_mode) { case FEEDER_CHAIN_LEAN: case FEEDER_CHAIN_16: case FEEDER_CHAIN_32: #if defined(SND_FEEDER_MULTIFORMAT) || defined(SND_FEEDER_FULL_MULTIFORMAT) case FEEDER_CHAIN_MULTI: #endif #if defined(SND_FEEDER_FULL_MULTIFORMAT) case FEEDER_CHAIN_FULLMULTI: #endif break; default: feeder_chain_mode = FEEDER_CHAIN_DEFAULT; break; } cdesc.mode = feeder_chain_mode; cdesc.expensive = 1; /* XXX faster.. */ #define VCHAN_PASSTHROUGH(c) (((c)->flags & (CHN_F_VIRTUAL | \ CHN_F_PASSTHROUGH)) == \ (CHN_F_VIRTUAL | CHN_F_PASSTHROUGH)) /* Get the best possible hardware format. */ if (VCHAN_PASSTHROUGH(c)) hwfmt = c->parentchannel->format; else { caps = chn_getcaps(c); if (caps == NULL || caps->fmtlist == NULL) { device_printf(c->dev, "%s(): failed to get channel caps\n", __func__); return (ENODEV); } if ((c->format & AFMT_PASSTHROUGH) && !snd_fmtvalid(c->format, caps->fmtlist)) return (ENODEV); hwfmt = snd_fmtbest(c->format, caps->fmtlist); if (hwfmt == 0 || !snd_fmtvalid(hwfmt, caps->fmtlist)) { device_printf(c->dev, "%s(): invalid hardware format 0x%08x\n", __func__, hwfmt); { int i; for (i = 0; caps->fmtlist[i] != 0; i++) printf("0x%08x\n", caps->fmtlist[i]); printf("Req: 0x%08x\n", c->format); } return (ENODEV); } } /* * The 'hardware' possibly have different interpretation of channel * matrixing, so get it first ..... */ hwmatrix = CHANNEL_GETMATRIX(c->methods, c->devinfo, hwfmt); if (hwmatrix == NULL) { /* setup a default matrix */ hwmatrix = &c->matrix_scratch; feeder_default_matrix(hwmatrix, hwfmt, SND_CHN_MATRIX_UNKNOWN); } /* ..... and rebuild hwfmt. */ hwfmt = SND_FORMAT(hwfmt, hwmatrix->channels, hwmatrix->ext); /* Reset and rebuild default channel format/matrix map. */ softfmt = c->format; softmatrix = &c->matrix; if (softmatrix->channels != AFMT_CHANNEL(softfmt) || softmatrix->ext != AFMT_EXTCHANNEL(softfmt)) { softmatrix = feeder_matrix_format_map(softfmt); if (softmatrix == NULL) { /* setup a default matrix */ softmatrix = &c->matrix; feeder_default_matrix(softmatrix, softfmt, SND_CHN_MATRIX_PCMCHANNEL); } else { c->matrix = *softmatrix; c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; } } softfmt = SND_FORMAT(softfmt, softmatrix->channels, softmatrix->ext); if (softfmt != c->format) device_printf(c->dev, "%s(): WARNING: %s Soft format 0x%08x -> 0x%08x\n", __func__, CHN_DIRSTR(c), c->format, softfmt); /* * PLAY and REC are opposite. */ if (c->direction == PCMDIR_PLAY) { cdesc.origin.afmt = softfmt; cdesc.origin.matrix = softmatrix; cdesc.origin.rate = c->speed; cdesc.target.afmt = hwfmt; cdesc.target.matrix = hwmatrix; - cdesc.target.rate = sndbuf_getspd(c->bufhard); + cdesc.target.rate = c->bufhard->spd; } else { cdesc.origin.afmt = hwfmt; cdesc.origin.matrix = hwmatrix; - cdesc.origin.rate = sndbuf_getspd(c->bufhard); + cdesc.origin.rate = c->bufhard->spd; cdesc.target.afmt = softfmt; cdesc.target.matrix = softmatrix; cdesc.target.rate = c->speed; } d = c->parentsnddev; /* * If channel is in bitperfect or passthrough mode, make it appear * that 'origin' and 'target' identical, skipping mostly chain * procedures. */ if (CHN_BITPERFECT(c) || (c->format & AFMT_PASSTHROUGH)) { if (c->direction == PCMDIR_PLAY) cdesc.origin = cdesc.target; else cdesc.target = cdesc.origin; c->format = cdesc.target.afmt; c->speed = cdesc.target.rate; } else { /* * Bail out early if we do not support either of those formats. */ if ((cdesc.origin.afmt & AFMT_CONVERTIBLE) == 0 || (cdesc.target.afmt & AFMT_CONVERTIBLE) == 0) { device_printf(c->dev, "%s(): unsupported formats: in=0x%08x, out=0x%08x\n", __func__, cdesc.origin.afmt, cdesc.target.afmt); return (ENODEV); } /* hwfmt is not convertible, so 'dummy' it. */ if (hwfmt & AFMT_PASSTHROUGH) cdesc.dummy = 1; if ((softfmt & AFMT_CONVERTIBLE) && (((d->flags & SD_F_VPC) && !(c->flags & CHN_F_HAS_VCHAN)) || (!(d->flags & SD_F_VPC) && (d->flags & SD_F_SOFTPCMVOL) && !(c->flags & CHN_F_VIRTUAL)))) cdesc.use_volume = 1; if (feeder_matrix_compare(cdesc.origin.matrix, cdesc.target.matrix) != 0) cdesc.use_matrix = 1; /* Soft EQ only applicable for PLAY. */ if (cdesc.dummy == 0 && c->direction == PCMDIR_PLAY && (d->flags & SD_F_EQ) && (((d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_HAS_VCHAN)) || (!(d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_VIRTUAL)))) cdesc.use_eq = 1; if (FEEDFORMAT_NE_REQUIRED(&cdesc)) { cdesc.afmt_ne = (cdesc.dummy != 0) ? snd_fmtbest(AFMT_ENCODING(softfmt), feeder_chain_formats[cdesc.mode]) : snd_fmtbest(AFMT_ENCODING(cdesc.target.afmt), feeder_chain_formats[cdesc.mode]); if (cdesc.afmt_ne == 0) { device_printf(c->dev, "%s(): snd_fmtbest failed!\n", __func__); cdesc.afmt_ne = (((cdesc.dummy != 0) ? softfmt : cdesc.target.afmt) & (AFMT_24BIT | AFMT_32BIT)) ? AFMT_S32_NE : AFMT_S16_NE; } } } cdesc.current = cdesc.origin; /* Build everything. */ c->feederflags = 0; #define FEEDER_BUILD(t) do { \ ret = feeder_build_##t(c, &cdesc); \ if (ret != 0) \ return (ret); \ } while (0) if (!(c->flags & CHN_F_HAS_VCHAN) || c->direction == PCMDIR_REC) FEEDER_BUILD(root); else if (c->direction == PCMDIR_PLAY && (c->flags & CHN_F_HAS_VCHAN)) FEEDER_BUILD(mixer); else return (ENOTSUP); /* * The basic idea is: The smaller the bandwidth, the cheaper the * conversion process, with following constraints:- * * 1) Almost all feeders work best in 16/32 native endian. * 2) Try to avoid 8bit feeders due to poor dynamic range. * 3) Avoid volume, format, matrix and rate in BITPERFECT or * PASSTHROUGH mode. * 4) Try putting volume before EQ or rate. Should help to * avoid/reduce possible clipping. * 5) EQ require specific, valid rate, unless it allow sloppy * conversion. */ if (FEEDMATRIX_UP(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && (!FEEDEQ_VALIDRATE(&cdesc, target) || (cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc)))) FEEDER_BUILD(eq); if (FEEDRATE_REQUIRED(&cdesc)) FEEDER_BUILD(rate); FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } else if (FEEDMATRIX_DOWN(&cdesc)) { FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDEQ_REQUIRED(&cdesc) && (!FEEDEQ_VALIDRATE(&cdesc, target) || FEEDEQ_ECONOMY(&cdesc))) FEEDER_BUILD(eq); if (FEEDRATE_REQUIRED(&cdesc)) FEEDER_BUILD(rate); if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } else { if (FEEDRATE_DOWN(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && !FEEDEQ_VALIDRATE(&cdesc, target)) { if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); FEEDER_BUILD(eq); } FEEDER_BUILD(rate); } if (FEEDMATRIX_REQUIRED(&cdesc)) FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDRATE_UP(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && !FEEDEQ_VALIDRATE(&cdesc, target)) FEEDER_BUILD(eq); FEEDER_BUILD(rate); } if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } if (FEEDFORMAT_REQUIRED(&cdesc)) FEEDER_BUILD(format); if (c->direction == PCMDIR_REC && (c->flags & CHN_F_HAS_VCHAN)) FEEDER_BUILD(mixer); sndbuf_setfmt(c->bufsoft, c->format); sndbuf_setspd(c->bufsoft, c->speed); sndbuf_setfmt(c->bufhard, hwfmt); chn_syncstate(c); return (0); } diff --git a/sys/dev/sound/pcm/feeder_mixer.c b/sys/dev/sound/pcm/feeder_mixer.c index b6b81ad9a51c..ab67a14711d3 100644 --- a/sys/dev/sound/pcm/feeder_mixer.c +++ b/sys/dev/sound/pcm/feeder_mixer.c @@ -1,374 +1,374 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008-2009 Ariff Abdullah * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef _KERNEL #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include "feeder_if.h" #define SND_USE_FXDIV #include "snd_fxdiv_gen.h" #endif #undef SND_FEEDER_MULTIFORMAT #define SND_FEEDER_MULTIFORMAT 1 struct feed_mixer_info { uint32_t format; uint32_t channels; int bps; }; __always_inline static void feed_mixer_apply(uint8_t *src, uint8_t *dst, uint32_t count, const uint32_t fmt) { intpcm32_t z; intpcm_t x, y; src += count; dst += count; do { src -= AFMT_BPS(fmt); dst -= AFMT_BPS(fmt); count -= AFMT_BPS(fmt); x = pcm_sample_read_calc(src, fmt); y = pcm_sample_read_calc(dst, fmt); z = INTPCM_T(x) + y; x = pcm_clamp_calc(z, fmt); pcm_sample_write(dst, x, fmt); } while (count != 0); } static int feed_mixer_init(struct pcm_feeder *f) { struct feed_mixer_info *info; if (f->desc->in != f->desc->out) return (EINVAL); info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO); if (info == NULL) return (ENOMEM); info->format = AFMT_ENCODING(f->desc->in); info->channels = AFMT_CHANNEL(f->desc->in); info->bps = AFMT_BPS(f->desc->in); f->data = info; return (0); } static int feed_mixer_free(struct pcm_feeder *f) { struct feed_mixer_info *info; info = f->data; if (info != NULL) free(info, M_DEVBUF); f->data = NULL; return (0); } static int feed_mixer_set(struct pcm_feeder *f, int what, int value) { struct feed_mixer_info *info; info = f->data; switch (what) { case FEEDMIXER_CHANNELS: if (value < SND_CHN_MIN || value > SND_CHN_MAX) return (EINVAL); info->channels = (uint32_t)value; break; default: return (EINVAL); } return (0); } static __inline int feed_mixer_rec(struct pcm_channel *c) { struct pcm_channel *ch; struct snd_dbuf *b, *bs; uint32_t cnt, maxfeed; int rdy; /* * Reset ready and moving pointer. We're not using bufsoft * anywhere since its sole purpose is to become the primary * distributor for the recorded buffer and also as an interrupt * threshold progress indicator. */ b = c->bufsoft; b->rp = 0; b->rl = 0; - cnt = sndbuf_getsize(b); - maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(b)); + cnt = b->bufsize; + maxfeed = SND_FXROUND(SND_FXDIV_MAX, b->align); do { cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, min(cnt, maxfeed), c->bufhard); if (cnt != 0) { sndbuf_acquire(b, b->tmpbuf, cnt); cnt = sndbuf_getfree(b); } } while (cnt != 0); /* Not enough data */ - if (b->rl < sndbuf_getalign(b)) { + if (b->rl < b->align) { b->rl = 0; return (0); } /* * Keep track of ready and moving pointer since we will use * bufsoft over and over again, pretending nothing has happened. */ rdy = b->rl; CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (CHN_STOPPED(ch) || (ch->flags & CHN_F_DIRTY)) { CHN_UNLOCK(ch); continue; } #ifdef SND_DEBUG if ((c->flags & CHN_F_DIRTY) && VCHAN_SYNC_REQUIRED(ch)) { if (vchan_sync(ch) != 0) { CHN_UNLOCK(ch); continue; } } #endif bs = ch->bufsoft; if (ch->flags & CHN_F_MMAP) sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); cnt = sndbuf_getfree(bs); - if (cnt < sndbuf_getalign(bs)) { + if (cnt < bs->align) { CHN_UNLOCK(ch); continue; } - maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(bs)); + maxfeed = SND_FXROUND(SND_FXDIV_MAX, bs->align); do { cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, min(cnt, maxfeed), b); if (cnt != 0) { sndbuf_acquire(bs, bs->tmpbuf, cnt); cnt = sndbuf_getfree(bs); } } while (cnt != 0); /* * Not entirely flushed out... */ if (b->rl != 0) ch->xruns++; CHN_UNLOCK(ch); /* * Rewind buffer position for next virtual channel. */ b->rp = 0; b->rl = rdy; } /* * Set ready pointer to indicate that our children are ready * to be woken up, also as an interrupt threshold progress * indicator. */ b->rl = 1; c->flags &= ~CHN_F_DIRTY; /* * Return 0 to bail out early from sndbuf_feed() loop. * No need to increase feedcount counter since part of this * feeder chains already include feed_root(). */ return (0); } static int feed_mixer_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { struct feed_mixer_info *info; struct snd_dbuf *src = source; struct pcm_channel *ch; uint32_t cnt, mcnt, rcnt, sz; int passthrough; uint8_t *tmp; if (c->direction == PCMDIR_REC) return (feed_mixer_rec(c)); - sz = sndbuf_getsize(src); + sz = src->bufsize; if (sz < count) count = sz; info = f->data; sz = info->bps * info->channels; count = SND_FXROUND(count, sz); if (count < sz) return (0); /* * We are going to use our source as a temporary buffer since it's * got no other purpose. We obtain our data by traversing the channel * list of children and calling mixer function to mix count bytes from * each into our destination buffer, b. */ - tmp = sndbuf_getbuf(src); + tmp = src->buf; rcnt = 0; mcnt = 0; passthrough = 0; /* 'passthrough' / 'exclusive' marker */ CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (CHN_STOPPED(ch) || (ch->flags & CHN_F_DIRTY)) { CHN_UNLOCK(ch); continue; } #ifdef SND_DEBUG if ((c->flags & CHN_F_DIRTY) && VCHAN_SYNC_REQUIRED(ch)) { if (vchan_sync(ch) != 0) { CHN_UNLOCK(ch); continue; } } #endif if ((ch->flags & CHN_F_MMAP) && !(ch->flags & CHN_F_CLOSING)) sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); if (c->flags & CHN_F_PASSTHROUGH) { /* * Passthrough. Dump the first digital/passthrough * channel into destination buffer, and the rest into * nothingness (mute effect). */ if (passthrough == 0 && (ch->format & AFMT_PASSTHROUGH)) { rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, b, count, ch->bufsoft), sz); passthrough = 1; } else FEEDER_FEED(ch->feeder, ch, tmp, count, ch->bufsoft); } else if (c->flags & CHN_F_EXCLUSIVE) { /* * Exclusive. Dump the first 'exclusive' channel into * destination buffer, and the rest into nothingness * (mute effect). */ if (passthrough == 0 && (ch->flags & CHN_F_EXCLUSIVE)) { rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, b, count, ch->bufsoft), sz); passthrough = 1; } else FEEDER_FEED(ch->feeder, ch, tmp, count, ch->bufsoft); } else { if (rcnt == 0) { rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, b, count, ch->bufsoft), sz); mcnt = count - rcnt; } else { cnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, tmp, count, ch->bufsoft), sz); if (cnt != 0) { if (mcnt != 0) { memset(b + rcnt, sndbuf_zerodata( f->desc->out), mcnt); mcnt = 0; } switch (info->format) { case AFMT_S16_NE: feed_mixer_apply(tmp, b, cnt, AFMT_S16_NE); break; case AFMT_S24_NE: feed_mixer_apply(tmp, b, cnt, AFMT_S24_NE); break; case AFMT_S32_NE: feed_mixer_apply(tmp, b, cnt, AFMT_S32_NE); break; default: feed_mixer_apply(tmp, b, cnt, info->format); break; } if (cnt > rcnt) rcnt = cnt; } } } CHN_UNLOCK(ch); } if (++c->feedcount == 0) c->feedcount = 2; c->flags &= ~CHN_F_DIRTY; return (rcnt); } static struct pcm_feederdesc feeder_mixer_desc[] = { { FEEDER_MIXER, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } }; static kobj_method_t feeder_mixer_methods[] = { KOBJMETHOD(feeder_init, feed_mixer_init), KOBJMETHOD(feeder_free, feed_mixer_free), KOBJMETHOD(feeder_set, feed_mixer_set), KOBJMETHOD(feeder_feed, feed_mixer_feed), KOBJMETHOD_END }; FEEDER_DECLARE(feeder_mixer, NULL); diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c index 1a5bccd718b5..b5e52aa13a19 100644 --- a/sys/dev/sound/pcm/sndstat.c +++ b/sys/dev/sound/pcm/sndstat.c @@ -1,1459 +1,1456 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 2001 Cameron Grant * Copyright (c) 2020 The FreeBSD Foundation * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Portions of this software were developed by Ka Ho Ng * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include #include "feeder_if.h" #define SS_TYPE_PCM 1 #define SS_TYPE_MIDI 2 static d_open_t sndstat_open; static void sndstat_close(void *); static d_read_t sndstat_read; static d_write_t sndstat_write; static d_ioctl_t sndstat_ioctl; static struct cdevsw sndstat_cdevsw = { .d_version = D_VERSION, .d_open = sndstat_open, .d_read = sndstat_read, .d_write = sndstat_write, .d_ioctl = sndstat_ioctl, .d_name = "sndstat", .d_flags = D_TRACKCLOSE, }; struct sndstat_entry { TAILQ_ENTRY(sndstat_entry) link; device_t dev; char *str; int type, unit; }; struct sndstat_userdev { TAILQ_ENTRY(sndstat_userdev) link; char *provider; char *nameunit; char *devnode; char *desc; unsigned int pchan; unsigned int rchan; struct { uint32_t min_rate; uint32_t max_rate; uint32_t formats; uint32_t min_chn; uint32_t max_chn; } info_play, info_rec; nvlist_t *provider_nvl; }; struct sndstat_file { TAILQ_ENTRY(sndstat_file) entry; struct sbuf sbuf; struct sx lock; void *devs_nvlbuf; /* (l) */ size_t devs_nbytes; /* (l) */ TAILQ_HEAD(, sndstat_userdev) userdev_list; /* (l) */ int out_offset; int in_offset; int fflags; }; static struct sx sndstat_lock; static struct cdev *sndstat_dev; #define SNDSTAT_LOCK() sx_xlock(&sndstat_lock) #define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock) static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist); static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist); int snd_verbose = 0; static int sndstat_prepare(struct sndstat_file *); static struct sndstat_userdev * sndstat_line2userdev(struct sndstat_file *, const char *, int); static int sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) { int error, verbose; verbose = snd_verbose; error = sysctl_handle_int(oidp, &verbose, 0, req); if (error == 0 && req->newptr != NULL) { if (verbose < 0 || verbose > 4) error = EINVAL; else snd_verbose = verbose; } return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level"); static int sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct sndstat_file *pf; pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO); sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND); pf->fflags = flags; TAILQ_INIT(&pf->userdev_list); sx_init(&pf->lock, "sndstat_file"); SNDSTAT_LOCK(); TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry); SNDSTAT_UNLOCK(); devfs_set_cdevpriv(pf, &sndstat_close); return (0); } /* * Should only be called either when: * * Closing * * pf->lock held */ static void sndstat_remove_all_userdevs(struct sndstat_file *pf) { struct sndstat_userdev *ud; KASSERT( sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__)); while ((ud = TAILQ_FIRST(&pf->userdev_list)) != NULL) { TAILQ_REMOVE(&pf->userdev_list, ud, link); free(ud->provider, M_DEVBUF); free(ud->desc, M_DEVBUF); free(ud->devnode, M_DEVBUF); free(ud->nameunit, M_DEVBUF); nvlist_destroy(ud->provider_nvl); free(ud, M_DEVBUF); } } static void sndstat_close(void *sndstat_file) { struct sndstat_file *pf = (struct sndstat_file *)sndstat_file; SNDSTAT_LOCK(); sbuf_delete(&pf->sbuf); TAILQ_REMOVE(&sndstat_filelist, pf, entry); SNDSTAT_UNLOCK(); free(pf->devs_nvlbuf, M_NVLIST); sx_xlock(&pf->lock); sndstat_remove_all_userdevs(pf); sx_xunlock(&pf->lock); sx_destroy(&pf->lock); free(pf, M_DEVBUF); } static int sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) { struct sndstat_file *pf; int err; int len; err = devfs_get_cdevpriv((void **)&pf); if (err != 0) return (err); /* skip zero-length reads */ if (buf->uio_resid == 0) return (0); SNDSTAT_LOCK(); if (pf->out_offset != 0) { /* don't allow both reading and writing */ err = EINVAL; goto done; } else if (pf->in_offset == 0) { err = sndstat_prepare(pf); if (err <= 0) { err = ENOMEM; goto done; } } len = sbuf_len(&pf->sbuf) - pf->in_offset; if (len > buf->uio_resid) len = buf->uio_resid; if (len > 0) err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf); pf->in_offset += len; done: SNDSTAT_UNLOCK(); return (err); } static int sndstat_write(struct cdev *i_dev, struct uio *buf, int flag) { struct sndstat_file *pf; uint8_t temp[64]; int err; int len; err = devfs_get_cdevpriv((void **)&pf); if (err != 0) return (err); /* skip zero-length writes */ if (buf->uio_resid == 0) return (0); /* don't allow writing more than 64Kbytes */ if (buf->uio_resid > 65536) return (ENOMEM); SNDSTAT_LOCK(); if (pf->in_offset != 0) { /* don't allow both reading and writing */ err = EINVAL; } else { /* only remember the last write - allows for updates */ sx_xlock(&pf->lock); sndstat_remove_all_userdevs(pf); sx_xunlock(&pf->lock); while (1) { len = sizeof(temp); if (len > buf->uio_resid) len = buf->uio_resid; if (len > 0) { err = uiomove(temp, len, buf); if (err) break; } else { break; } if (sbuf_bcat(&pf->sbuf, temp, len) < 0) { err = ENOMEM; break; } } sbuf_finish(&pf->sbuf); if (err == 0) { char *line, *str; str = sbuf_data(&pf->sbuf); while ((line = strsep(&str, "\n")) != NULL) { struct sndstat_userdev *ud; ud = sndstat_line2userdev(pf, line, strlen(line)); if (ud == NULL) continue; sx_xlock(&pf->lock); TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link); sx_xunlock(&pf->lock); } pf->out_offset = sbuf_len(&pf->sbuf); } else pf->out_offset = 0; sbuf_clear(&pf->sbuf); } SNDSTAT_UNLOCK(); return (err); } static void sndstat_get_caps(struct snddev_info *d, int dir, uint32_t *min_rate, uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn) { struct pcm_channel *c; struct pcmchan_caps *caps; int i; *fmts = 0; *min_rate = UINT32_MAX; *max_rate = 0; *minchn = UINT32_MAX; *maxchn = 0; CHN_FOREACH(c, d, channels.pcm) { if (c->direction != dir) continue; CHN_LOCK(c); caps = chn_getcaps(c); for (i = 0; caps->fmtlist[i]; i++) { *fmts |= AFMT_ENCODING(caps->fmtlist[i]); *minchn = min(AFMT_CHANNEL(caps->fmtlist[i]), *minchn); *maxchn = max(AFMT_CHANNEL(caps->fmtlist[i]), *maxchn); } if ((c->flags & CHN_F_EXCLUSIVE) || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { *min_rate = min(*min_rate, caps->minspeed); *max_rate = max(*max_rate, caps->maxspeed); } else { *min_rate = min(*min_rate, feeder_rate_min); *max_rate = max(*max_rate, feeder_rate_max); } CHN_UNLOCK(c); } if (*min_rate == UINT32_MAX) *min_rate = 0; if (*minchn == UINT32_MAX) *minchn = 0; } static nvlist_t * sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats, uint32_t min_chn, uint32_t max_chn) { nvlist_t *nv; nv = nvlist_create(0); if (nv == NULL) return (NULL); nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate); nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate); nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats); nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn); nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn); return (nv); } static int sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip) { struct pcm_channel *c; struct pcm_feeder *f; struct sbuf sb; uint32_t maxrate, minrate, fmts, minchn, maxchn, caps; nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL, *cdi = NULL; int err, nchan; char buf[AFMTSTR_LEN]; di = nvlist_create(0); if (di == NULL) { err = ENOMEM; goto done; } sound4di = nvlist_create(0); if (sound4di == NULL) { err = ENOMEM; goto done; } nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false); nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s", device_get_nameunit(d->dev)); nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d", device_get_unit(d->dev)); nvlist_add_string( di, SNDST_DSPS_DESC, device_get_desc(d->dev)); PCM_ACQUIRE_QUICK(d); nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount); nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount); if (d->playcount > 0) { sndstat_get_caps(d, PCMDIR_PLAY, &minrate, &maxrate, &fmts, &minchn, &maxchn); nvlist_add_number(di, "pminrate", minrate); nvlist_add_number(di, "pmaxrate", maxrate); nvlist_add_number(di, "pfmts", fmts); diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts, minchn, maxchn); if (diinfo == NULL) nvlist_set_error(di, ENOMEM); else nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo); } if (d->reccount > 0) { sndstat_get_caps(d, PCMDIR_REC, &minrate, &maxrate, &fmts, &minchn, &maxchn); nvlist_add_number(di, "rminrate", minrate); nvlist_add_number(di, "rmaxrate", maxrate); nvlist_add_number(di, "rfmts", fmts); diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts, minchn, maxchn); if (diinfo == NULL) nvlist_set_error(di, ENOMEM); else nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo); } nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT, device_get_unit(d->dev)); // XXX: I want signed integer here nvlist_add_string(sound4di, SNDST_DSPS_SOUND4_STATUS, d->status); nvlist_add_bool( sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT); nvlist_add_bool(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->flags & SD_F_PVCHANS); nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHANRATE, d->pvchanrate); nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHANFORMAT, d->pvchanformat); nvlist_add_bool(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->flags & SD_F_RVCHANS); nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHANRATE, d->rvchanrate); nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHANFORMAT, d->rvchanformat); nchan = 0; CHN_FOREACH(c, d, channels.pcm) { sbuf_new(&sb, NULL, 4096, SBUF_AUTOEXTEND); cdi = nvlist_create(0); if (cdi == NULL) { sbuf_delete(&sb); PCM_RELEASE_QUICK(d); err = ENOMEM; goto done; } CHN_LOCK(c); caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | ((c->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | ((c->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_NAME, c->name); nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_PARENTCHAN, c->parentchannel != NULL ? c->parentchannel->name : ""); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_UNIT, nchan++); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_CAPS, caps); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LATENCY, c->latency); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RATE, c->speed); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FORMAT, c->format); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_PID, c->pid); nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_COMM, c->comm); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_INTR, c->interrupts); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDCNT, c->feedcount); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_XRUNS, c->xruns); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LEFTVOL, CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL)); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RIGHTVOL, CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR)); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT, - sndbuf_getfmt(c->bufhard)); + c->bufhard->fmt); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_RATE, - sndbuf_getspd(c->bufhard)); + c->bufhard->spd); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE, - sndbuf_getsize(c->bufhard)); + c->bufhard->bufsize); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ, - sndbuf_getblksz(c->bufhard)); + c->bufhard->blksz); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT, - sndbuf_getblkcnt(c->bufhard)); + c->bufhard->blkcnt); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE, sndbuf_getfree(c->bufhard)); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_READY, sndbuf_getready(c->bufhard)); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT, - sndbuf_getfmt(c->bufsoft)); + c->bufsoft->fmt); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_RATE, - sndbuf_getspd(c->bufsoft)); + c->bufsoft->spd); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE, - sndbuf_getsize(c->bufsoft)); + c->bufsoft->bufsize); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ, - sndbuf_getblksz(c->bufsoft)); + c->bufsoft->blksz); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT, - sndbuf_getblkcnt(c->bufsoft)); + c->bufsoft->blkcnt); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE, sndbuf_getfree(c->bufsoft)); nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_READY, sndbuf_getready(c->bufsoft)); if (c->parentchannel != NULL) { sbuf_printf(&sb, "[%s", (c->direction == PCMDIR_REC) ? c->parentchannel->name : "userland"); } else { sbuf_printf(&sb, "[%s", (c->direction == PCMDIR_REC) ? "hardware" : ((d->flags & SD_F_PVCHANS) ? "vchans" : "userland")); } sbuf_printf(&sb, " -> "); f = c->feeder; while (f->source != NULL) f = f->source; while (f != NULL) { sbuf_printf(&sb, "%s", f->class->name); if (f->desc->type == FEEDER_FORMAT) { snd_afmt2str(f->desc->in, buf, sizeof(buf)); sbuf_printf(&sb, "(%s -> ", buf); snd_afmt2str(f->desc->out, buf, sizeof(buf)); sbuf_printf(&sb, "%s)", buf); } else if (f->desc->type == FEEDER_MATRIX) { sbuf_printf(&sb, "(%d.%dch -> %d.%dch)", AFMT_CHANNEL(f->desc->in) - AFMT_EXTCHANNEL(f->desc->in), AFMT_EXTCHANNEL(f->desc->in), AFMT_CHANNEL(f->desc->out) - AFMT_EXTCHANNEL(f->desc->out), AFMT_EXTCHANNEL(f->desc->out)); } else if (f->desc->type == FEEDER_RATE) { sbuf_printf(&sb, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); } else { snd_afmt2str(f->desc->out, buf, sizeof(buf)); sbuf_printf(&sb, "(%s)", buf); } sbuf_printf(&sb, " -> "); f = f->parent; } if (c->parentchannel != NULL) { sbuf_printf(&sb, "%s]", (c->direction == PCMDIR_REC) ? "userland" : c->parentchannel->name); } else { sbuf_printf(&sb, "%s]", (c->direction == PCMDIR_REC) ? ((d->flags & SD_F_RVCHANS) ? "vchans" : "userland") : "hardware"); } CHN_UNLOCK(c); sbuf_finish(&sb); nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDERCHAIN, sbuf_data(&sb)); sbuf_delete(&sb); nvlist_append_nvlist_array(sound4di, SNDST_DSPS_SOUND4_CHAN_INFO, cdi); nvlist_destroy(cdi); err = nvlist_error(sound4di); if (err) { PCM_RELEASE_QUICK(d); goto done; } } nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di); sound4di = NULL; PCM_RELEASE_QUICK(d); nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER); err = nvlist_error(di); if (err) goto done; *dip = di; done: if (err) { nvlist_destroy(sound4di); nvlist_destroy(di); } return (err); } static int sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip) { nvlist_t *di, *diinfo; int err; di = nvlist_create(0); if (di == NULL) { err = ENOMEM; goto done; } nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true); nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan); nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan); nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit); nvlist_add_string( di, SNDST_DSPS_DEVNODE, ud->devnode); nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc); if (ud->pchan != 0) { nvlist_add_number(di, "pminrate", ud->info_play.min_rate); nvlist_add_number(di, "pmaxrate", ud->info_play.max_rate); nvlist_add_number(di, "pfmts", ud->info_play.formats); diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate, ud->info_play.max_rate, ud->info_play.formats, ud->info_play.min_chn, ud->info_play.max_chn); if (diinfo == NULL) nvlist_set_error(di, ENOMEM); else nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo); } if (ud->rchan != 0) { nvlist_add_number(di, "rminrate", ud->info_rec.min_rate); nvlist_add_number(di, "rmaxrate", ud->info_rec.max_rate); nvlist_add_number(di, "rfmts", ud->info_rec.formats); diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate, ud->info_rec.max_rate, ud->info_rec.formats, ud->info_rec.min_chn, ud->info_rec.max_chn); if (diinfo == NULL) nvlist_set_error(di, ENOMEM); else nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo); } nvlist_add_string(di, SNDST_DSPS_PROVIDER, (ud->provider != NULL) ? ud->provider : ""); if (ud->provider_nvl != NULL) nvlist_add_nvlist( di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl); err = nvlist_error(di); if (err) goto done; *dip = di; done: if (err) nvlist_destroy(di); return (err); } /* * Should only be called with the following locks held: * * sndstat_lock */ static int sndstat_create_devs_nvlist(nvlist_t **nvlp) { int err; nvlist_t *nvl; struct sndstat_entry *ent; struct sndstat_file *pf; nvl = nvlist_create(0); if (nvl == NULL) return (ENOMEM); TAILQ_FOREACH(ent, &sndstat_devlist, link) { struct snddev_info *d; nvlist_t *di; d = device_get_softc(ent->dev); if (!PCM_REGISTERED(d)) continue; err = sndstat_build_sound4_nvlist(d, &di); if (err) goto done; nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); nvlist_destroy(di); err = nvlist_error(nvl); if (err) goto done; } TAILQ_FOREACH(pf, &sndstat_filelist, entry) { struct sndstat_userdev *ud; sx_xlock(&pf->lock); TAILQ_FOREACH(ud, &pf->userdev_list, link) { nvlist_t *di; err = sndstat_build_userland_nvlist(ud, &di); if (err != 0) { sx_xunlock(&pf->lock); goto done; } nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); nvlist_destroy(di); err = nvlist_error(nvl); if (err != 0) { sx_xunlock(&pf->lock); goto done; } } sx_xunlock(&pf->lock); } *nvlp = nvl; done: if (err != 0) nvlist_destroy(nvl); return (err); } static int sndstat_refresh_devs(struct sndstat_file *pf) { sx_xlock(&pf->lock); free(pf->devs_nvlbuf, M_NVLIST); pf->devs_nvlbuf = NULL; pf->devs_nbytes = 0; sx_unlock(&pf->lock); return (0); } static int sndstat_get_devs(struct sndstat_file *pf, void *arg_buf, size_t *arg_nbytes) { int err; SNDSTAT_LOCK(); sx_xlock(&pf->lock); if (pf->devs_nvlbuf == NULL) { nvlist_t *nvl; void *nvlbuf; size_t nbytes; int err; sx_xunlock(&pf->lock); err = sndstat_create_devs_nvlist(&nvl); if (err) { SNDSTAT_UNLOCK(); return (err); } sx_xlock(&pf->lock); nvlbuf = nvlist_pack(nvl, &nbytes); err = nvlist_error(nvl); nvlist_destroy(nvl); if (nvlbuf == NULL || err != 0) { SNDSTAT_UNLOCK(); sx_xunlock(&pf->lock); if (err == 0) return (ENOMEM); return (err); } free(pf->devs_nvlbuf, M_NVLIST); pf->devs_nvlbuf = nvlbuf; pf->devs_nbytes = nbytes; } SNDSTAT_UNLOCK(); if (*arg_nbytes == 0) { *arg_nbytes = pf->devs_nbytes; err = 0; goto done; } if (*arg_nbytes < pf->devs_nbytes) { *arg_nbytes = 0; err = 0; goto done; } err = copyout(pf->devs_nvlbuf, arg_buf, pf->devs_nbytes); if (err) goto done; *arg_nbytes = pf->devs_nbytes; free(pf->devs_nvlbuf, M_NVLIST); pf->devs_nvlbuf = NULL; pf->devs_nbytes = 0; done: sx_unlock(&pf->lock); return (err); } static int sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl) { void *nvlbuf; int err; nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK); err = copyin(unvlbuf, nvlbuf, nbytes); if (err != 0) { free(nvlbuf, M_DEVBUF); return (err); } *nvl = nvlist_unpack(nvlbuf, nbytes, 0); free(nvlbuf, M_DEVBUF); if (*nvl == NULL) { return (EINVAL); } return (0); } static bool sndstat_diinfo_is_sane(const nvlist_t *diinfo) { if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) && nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) && nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) && nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) && nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN))) return (false); return (true); } static bool sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist) { if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) && nvlist_exists_string(nvlist, SNDST_DSPS_DESC) && nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) && nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN))) return (false); if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) { if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) { if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist, SNDST_DSPS_INFO_PLAY))) return (false); } else if (!(nvlist_exists_number(nvlist, "pminrate") && nvlist_exists_number(nvlist, "pmaxrate") && nvlist_exists_number(nvlist, "pfmts"))) return (false); } if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) { if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) { if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist, SNDST_DSPS_INFO_REC))) return (false); } else if (!(nvlist_exists_number(nvlist, "rminrate") && nvlist_exists_number(nvlist, "rmaxrate") && nvlist_exists_number(nvlist, "rfmts"))) return (false); } return (true); } static void sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate, uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn, uint32_t *max_chn) { *min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE); *max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE); *formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS); *min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN); *max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN); } static int sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud) { const char *nameunit, *devnode, *desc; unsigned int pchan, rchan; uint32_t pminrate = 0, pmaxrate = 0; uint32_t rminrate = 0, rmaxrate = 0; uint32_t pfmts = 0, rfmts = 0; uint32_t pminchn = 0, pmaxchn = 0; uint32_t rminchn = 0, rmaxchn = 0; nvlist_t *provider_nvl = NULL; const nvlist_t *diinfo; const char *provider; devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE); if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT)) nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT); else nameunit = devnode; desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC); pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN); rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN); if (pchan != 0) { if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) { diinfo = nvlist_get_nvlist(nvlist, SNDST_DSPS_INFO_PLAY); sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate, &pfmts, &pminchn, &pmaxchn); } else { pminrate = nvlist_get_number(nvlist, "pminrate"); pmaxrate = nvlist_get_number(nvlist, "pmaxrate"); pfmts = nvlist_get_number(nvlist, "pfmts"); } } if (rchan != 0) { if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) { diinfo = nvlist_get_nvlist(nvlist, SNDST_DSPS_INFO_REC); sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate, &rfmts, &rminchn, &rmaxchn); } else { rminrate = nvlist_get_number(nvlist, "rminrate"); rmaxrate = nvlist_get_number(nvlist, "rmaxrate"); rfmts = nvlist_get_number(nvlist, "rfmts"); } } provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, ""); if (provider[0] == '\0') provider = NULL; if (provider != NULL && nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) { provider_nvl = nvlist_clone( nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)); if (provider_nvl == NULL) return (ENOMEM); } ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL; ud->devnode = strdup(devnode, M_DEVBUF); ud->nameunit = strdup(nameunit, M_DEVBUF); ud->desc = strdup(desc, M_DEVBUF); ud->pchan = pchan; ud->rchan = rchan; ud->info_play.min_rate = pminrate; ud->info_play.max_rate = pmaxrate; ud->info_play.formats = pfmts; ud->info_play.min_chn = pminchn; ud->info_play.max_chn = pmaxchn; ud->info_rec.min_rate = rminrate; ud->info_rec.max_rate = rmaxrate; ud->info_rec.formats = rfmts; ud->info_rec.min_chn = rminchn; ud->info_rec.max_chn = rmaxchn; ud->provider_nvl = provider_nvl; return (0); } static int sndstat_add_user_devs(struct sndstat_file *pf, void *nvlbuf, size_t nbytes) { int err; nvlist_t *nvl = NULL; const nvlist_t * const *dsps; size_t i, ndsps; if ((pf->fflags & FWRITE) == 0) { err = EPERM; goto done; } if (nbytes > SNDST_UNVLBUF_MAX) { err = ENOMEM; goto done; } err = sndstat_unpack_user_nvlbuf(nvlbuf, nbytes, &nvl); if (err != 0) goto done; if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) { err = EINVAL; goto done; } dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps); for (i = 0; i < ndsps; i++) { if (!sndstat_dsp_nvlist_is_sane(dsps[i])) { err = EINVAL; goto done; } } sx_xlock(&pf->lock); for (i = 0; i < ndsps; i++) { struct sndstat_userdev *ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK); err = sndstat_dsp_unpack_nvlist(dsps[i], ud); if (err) { sx_unlock(&pf->lock); goto done; } TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link); } sx_unlock(&pf->lock); done: nvlist_destroy(nvl); return (err); } static int sndstat_flush_user_devs(struct sndstat_file *pf) { if ((pf->fflags & FWRITE) == 0) return (EPERM); sx_xlock(&pf->lock); sndstat_remove_all_userdevs(pf); sx_xunlock(&pf->lock); return (0); } static int sndstat_ioctl( struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { int err; struct sndstat_file *pf; struct sndstioc_nv_arg *arg; #ifdef COMPAT_FREEBSD32 struct sndstioc_nv_arg32 *arg32; size_t nbytes; #endif err = devfs_get_cdevpriv((void **)&pf); if (err != 0) return (err); switch (cmd) { case SNDSTIOC_GET_DEVS: arg = (struct sndstioc_nv_arg *)data; err = sndstat_get_devs(pf, arg->buf, &arg->nbytes); break; #ifdef COMPAT_FREEBSD32 case SNDSTIOC_GET_DEVS32: arg32 = (struct sndstioc_nv_arg32 *)data; nbytes = arg32->nbytes; err = sndstat_get_devs(pf, (void *)(uintptr_t)arg32->buf, &nbytes); if (err == 0) { KASSERT(nbytes < UINT_MAX, ("impossibly many bytes")); arg32->nbytes = nbytes; } break; #endif case SNDSTIOC_ADD_USER_DEVS: arg = (struct sndstioc_nv_arg *)data; err = sndstat_add_user_devs(pf, arg->buf, arg->nbytes); break; #ifdef COMPAT_FREEBSD32 case SNDSTIOC_ADD_USER_DEVS32: arg32 = (struct sndstioc_nv_arg32 *)data; err = sndstat_add_user_devs(pf, (void *)(uintptr_t)arg32->buf, arg32->nbytes); break; #endif case SNDSTIOC_REFRESH_DEVS: err = sndstat_refresh_devs(pf); break; case SNDSTIOC_FLUSH_USER_DEVS: err = sndstat_flush_user_devs(pf); break; default: err = ENODEV; } return (err); } static struct sndstat_userdev * sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n) { struct sndstat_userdev *ud; const char *e, *m; ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO); ud->provider = NULL; ud->provider_nvl = NULL; e = strchr(line, ':'); if (e == NULL) goto fail; ud->nameunit = strndup(line, e - line, M_DEVBUF); ud->devnode = malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO); strlcat(ud->devnode, ud->nameunit, e - line + 1); line = e + 1; e = strchr(line, '<'); if (e == NULL) goto fail; line = e + 1; e = strrchr(line, '>'); if (e == NULL) goto fail; ud->desc = strndup(line, e - line, M_DEVBUF); line = e + 1; e = strchr(line, '('); if (e == NULL) goto fail; line = e + 1; e = strrchr(line, ')'); if (e == NULL) goto fail; m = strstr(line, "play"); if (m != NULL && m < e) ud->pchan = 1; m = strstr(line, "rec"); if (m != NULL && m < e) ud->rchan = 1; return (ud); fail: free(ud->nameunit, M_DEVBUF); free(ud->devnode, M_DEVBUF); free(ud->desc, M_DEVBUF); free(ud, M_DEVBUF); return (NULL); } /************************************************************************/ int sndstat_register(device_t dev, char *str) { struct sndstat_entry *ent; struct sndstat_entry *pre; const char *devtype; int type, unit; unit = device_get_unit(dev); devtype = device_get_name(dev); if (!strcmp(devtype, "pcm")) type = SS_TYPE_PCM; else if (!strcmp(devtype, "midi")) type = SS_TYPE_MIDI; else return (EINVAL); ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO); ent->dev = dev; ent->str = str; ent->type = type; ent->unit = unit; SNDSTAT_LOCK(); /* sorted list insertion */ TAILQ_FOREACH(pre, &sndstat_devlist, link) { if (pre->unit > unit) break; else if (pre->unit < unit) continue; if (pre->type > type) break; else if (pre->type < unit) continue; } if (pre == NULL) { TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link); } else { TAILQ_INSERT_BEFORE(pre, ent, link); } SNDSTAT_UNLOCK(); return (0); } int sndstat_unregister(device_t dev) { struct sndstat_entry *ent; int error = ENXIO; SNDSTAT_LOCK(); TAILQ_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == dev) { TAILQ_REMOVE(&sndstat_devlist, ent, link); free(ent, M_DEVBUF); error = 0; break; } } SNDSTAT_UNLOCK(); return (error); } /************************************************************************/ static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) { struct snddev_info *d; struct pcm_channel *c; struct pcm_feeder *f; d = device_get_softc(dev); PCM_BUSYASSERT(d); if (CHN_EMPTY(d, channels.pcm)) { sbuf_printf(s, " (mixer only)"); return (0); } if (verbose < 1) { sbuf_printf(s, " (%s%s%s", d->playcount ? "play" : "", (d->playcount && d->reccount) ? "/" : "", d->reccount ? "rec" : ""); } else { sbuf_printf(s, " (%dp:%dv/%dr:%dv", d->playcount, d->pvchancount, d->reccount, d->rvchancount); } sbuf_printf(s, "%s)%s", ((d->playcount != 0 && d->reccount != 0) && (d->flags & SD_F_SIMPLEX)) ? " simplex" : "", (device_get_unit(dev) == snd_unit) ? " default" : ""); if (verbose <= 1) return (0); sbuf_printf(s, "\n\t"); sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS); CHN_FOREACH(c, d, channels.pcm) { KASSERT(c->bufhard != NULL && c->bufsoft != NULL, ("hosed pcm channel setup")); CHN_LOCK(c); sbuf_printf(s, "\n\t"); sbuf_printf(s, "%s[%s]: ", (c->parentchannel != NULL) ? c->parentchannel->name : "", c->name); sbuf_printf(s, "spd %d", c->speed); - if (c->speed != sndbuf_getspd(c->bufhard)) { - sbuf_printf(s, "/%d", - sndbuf_getspd(c->bufhard)); - } + if (c->speed != c->bufhard->spd) + sbuf_printf(s, "/%d", c->bufhard->spd); sbuf_printf(s, ", fmt 0x%08x", c->format); - if (c->format != sndbuf_getfmt(c->bufhard)) { - sbuf_printf(s, "/0x%08x", - sndbuf_getfmt(c->bufhard)); + if (c->format != c->bufhard->fmt) { + sbuf_printf(s, "/0x%08x", c->bufhard->fmt); } sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags); if (c->pid != -1) { sbuf_printf(s, ", pid %d (%s)", c->pid, c->comm); } sbuf_printf(s, "\n\t"); sbuf_printf(s, "\tinterrupts %d, ", c->interrupts); if (c->direction == PCMDIR_REC) { sbuf_printf(s, "overruns %d, feed %u, hfree %d, " "sfree %d\n\t\t[b:%d/%d/%d|bs:%d/%d/%d]", c->xruns, c->feedcount, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), - sndbuf_getsize(c->bufhard), - sndbuf_getblksz(c->bufhard), - sndbuf_getblkcnt(c->bufhard), - sndbuf_getsize(c->bufsoft), - sndbuf_getblksz(c->bufsoft), - sndbuf_getblkcnt(c->bufsoft)); + c->bufhard->bufsize, + c->bufhard->blksz, + c->bufhard->blkcnt, + c->bufsoft->bufsize, + c->bufsoft->blksz, + c->bufsoft->blkcnt); } else { sbuf_printf(s, "underruns %d, feed %u, ready %d " "\n\t\t[b:%d/%d/%d|bs:%d/%d/%d]", c->xruns, c->feedcount, sndbuf_getready(c->bufsoft), - sndbuf_getsize(c->bufhard), - sndbuf_getblksz(c->bufhard), - sndbuf_getblkcnt(c->bufhard), - sndbuf_getsize(c->bufsoft), - sndbuf_getblksz(c->bufsoft), - sndbuf_getblkcnt(c->bufsoft)); + c->bufhard->bufsize, + c->bufhard->blksz, + c->bufhard->blkcnt, + c->bufsoft->bufsize, + c->bufsoft->blksz, + c->bufsoft->blkcnt); } sbuf_printf(s, "\n\t"); sbuf_printf(s, "\tchannel flags=0x%b", c->flags, CHN_F_BITS); sbuf_printf(s, "\n\t"); if (c->parentchannel != NULL) { sbuf_printf(s, "\t{%s}", (c->direction == PCMDIR_REC) ? c->parentchannel->name : "userland"); } else { sbuf_printf(s, "\t{%s}", (c->direction == PCMDIR_REC) ? "hardware" : ((d->flags & SD_F_PVCHANS) ? "vchans" : "userland")); } sbuf_printf(s, " -> "); f = c->feeder; while (f->source != NULL) f = f->source; while (f != NULL) { sbuf_printf(s, "%s", f->class->name); if (f->desc->type == FEEDER_FORMAT) { sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); } else if (f->desc->type == FEEDER_MATRIX) { sbuf_printf(s, "(%d.%d -> %d.%d)", AFMT_CHANNEL(f->desc->in) - AFMT_EXTCHANNEL(f->desc->in), AFMT_EXTCHANNEL(f->desc->in), AFMT_CHANNEL(f->desc->out) - AFMT_EXTCHANNEL(f->desc->out), AFMT_EXTCHANNEL(f->desc->out)); } else if (f->desc->type == FEEDER_RATE) { sbuf_printf(s, "(0x%08x q:%d %d -> %d)", f->desc->out, FEEDER_GET(f, FEEDRATE_QUALITY), FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); } else { sbuf_printf(s, "(0x%08x)", f->desc->out); } sbuf_printf(s, " -> "); f = f->parent; } if (c->parentchannel != NULL) { sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC) ? "userland" : c->parentchannel->name); } else { sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC) ? ((d->flags & SD_F_RVCHANS) ? "vchans" : "userland") : "hardware"); } CHN_UNLOCK(c); } return (0); } static int sndstat_prepare(struct sndstat_file *pf_self) { struct sbuf *s = &pf_self->sbuf; struct sndstat_entry *ent; struct snddev_info *d; struct sndstat_file *pf; int k; /* make sure buffer is reset */ sbuf_clear(s); if (snd_verbose > 0) sbuf_printf(s, "FreeBSD Audio Driver\n"); /* generate list of installed devices */ k = 0; TAILQ_FOREACH(ent, &sndstat_devlist, link) { d = device_get_softc(ent->dev); if (!PCM_REGISTERED(d)) continue; if (!k++) sbuf_printf(s, "Installed devices:\n"); sbuf_printf(s, "%s:", device_get_nameunit(ent->dev)); sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); if (snd_verbose > 0) sbuf_printf(s, " %s", ent->str); /* XXX Need Giant magic entry ??? */ PCM_ACQUIRE_QUICK(d); sndstat_prepare_pcm(s, ent->dev, snd_verbose); PCM_RELEASE_QUICK(d); sbuf_printf(s, "\n"); } if (k == 0) sbuf_printf(s, "No devices installed.\n"); /* append any input from userspace */ k = 0; TAILQ_FOREACH(pf, &sndstat_filelist, entry) { struct sndstat_userdev *ud; if (pf == pf_self) continue; sx_xlock(&pf->lock); if (TAILQ_EMPTY(&pf->userdev_list)) { sx_unlock(&pf->lock); continue; } if (!k++) sbuf_printf(s, "Installed devices from userspace:\n"); TAILQ_FOREACH(ud, &pf->userdev_list, link) { const char *caps = (ud->pchan && ud->rchan) ? "play/rec" : (ud->pchan ? "play" : (ud->rchan ? "rec" : "")); sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc); sbuf_printf(s, " (%s)", caps); sbuf_printf(s, "\n"); } sx_unlock(&pf->lock); } if (k == 0) sbuf_printf(s, "No devices installed from userspace.\n"); sbuf_finish(s); return (sbuf_len(s)); } static void sndstat_sysinit(void *p) { sx_init(&sndstat_lock, "sndstat lock"); sndstat_dev = make_dev(&sndstat_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, "sndstat"); } SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); static void sndstat_sysuninit(void *p) { if (sndstat_dev != NULL) { /* destroy_dev() will wait for all references to go away */ destroy_dev(sndstat_dev); } sx_destroy(&sndstat_lock); } SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index 31a4f7db8d70..b31e28d51453 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -1,854 +1,854 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006-2009 Ariff Abdullah * Copyright (c) 2001 Cameron Grant * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ /* Almost entirely rewritten to add multi-format/channels mixing support. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include /* * [ac3 , dts , linear , 0, linear, 0] */ #define FMTLIST_MAX 6 #define FMTLIST_OFFSET 4 #define DIGFMTS_MAX 2 #ifdef SND_DEBUG static int snd_passthrough_verbose = 0; SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN, &snd_passthrough_verbose, 0, "passthrough verbosity"); #endif struct vchan_info { struct pcm_channel *channel; struct pcmchan_caps caps; uint32_t fmtlist[FMTLIST_MAX]; int trigger; }; bool snd_vchans_enable = true; static void * vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct vchan_info *info; struct pcm_channel *p; uint32_t i, j, *fmtlist; KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, ("vchan_init: bad direction")); KASSERT(c != NULL && c->parentchannel != NULL, ("vchan_init: bad channels")); info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO); info->channel = c; info->trigger = PCMTRIG_STOP; p = c->parentchannel; CHN_LOCK(p); fmtlist = chn_getcaps(p)->fmtlist; for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) { if (fmtlist[i] & AFMT_PASSTHROUGH) info->fmtlist[j++] = fmtlist[i]; } if (p->format & AFMT_VCHAN) info->fmtlist[j] = p->format; else info->fmtlist[j] = VCHAN_DEFAULT_FORMAT; info->caps.fmtlist = info->fmtlist + ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET); CHN_UNLOCK(p); c->flags |= CHN_F_VIRTUAL; return (info); } static int vchan_free(kobj_t obj, void *data) { free(data, M_DEVBUF); return (0); } static int vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchan_info *info; info = data; CHN_LOCKASSERT(info->channel); if (!snd_fmtvalid(format, info->caps.fmtlist)) return (-1); return (0); } static uint32_t vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchan_info *info; info = data; CHN_LOCKASSERT(info->channel); return (info->caps.maxspeed); } static int vchan_trigger(kobj_t obj, void *data, int go) { struct vchan_info *info; struct pcm_channel *c, *p; int ret, otrigger; info = data; c = info->channel; p = c->parentchannel; CHN_LOCKASSERT(c); if (!PCMTRIG_COMMON(go) || go == info->trigger) return (0); CHN_UNLOCK(c); CHN_LOCK(p); otrigger = info->trigger; info->trigger = go; switch (go) { case PCMTRIG_START: if (otrigger != PCMTRIG_START) CHN_INSERT_HEAD(p, c, children.busy); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (otrigger == PCMTRIG_START) CHN_REMOVE(p, c, children.busy); break; default: break; } ret = chn_notify(p, CHN_N_TRIGGER); CHN_LOCK(c); if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c)) ret = vchan_sync(c); CHN_UNLOCK(c); CHN_UNLOCK(p); CHN_LOCK(c); return (ret); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchan_info *info; struct pcm_channel *c; uint32_t pformat, pspeed, pflags, i; info = data; c = info->channel; pformat = c->parentchannel->format; pspeed = c->parentchannel->speed; pflags = c->parentchannel->flags; CHN_LOCKASSERT(c); if (pflags & CHN_F_VCHAN_DYNAMIC) { info->caps.fmtlist = info->fmtlist; if (pformat & AFMT_VCHAN) { for (i = 0; info->caps.fmtlist[i] != 0; i++) { if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH) continue; break; } info->caps.fmtlist[i] = pformat; } if (c->format & AFMT_PASSTHROUGH) info->caps.minspeed = c->speed; else info->caps.minspeed = pspeed; info->caps.maxspeed = info->caps.minspeed; } else { info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET; if (pformat & AFMT_VCHAN) info->caps.fmtlist[0] = pformat; else { device_printf(c->dev, "%s(): invalid vchan format 0x%08x", __func__, pformat); info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT; } info->caps.minspeed = pspeed; info->caps.maxspeed = info->caps.minspeed; } return (&info->caps); } static struct pcmchan_matrix * vchan_getmatrix(kobj_t obj, void *data, uint32_t format) { return (feeder_matrix_format_map(format)); } static kobj_method_t vchan_methods[] = { KOBJMETHOD(channel_init, vchan_init), KOBJMETHOD(channel_free, vchan_free), KOBJMETHOD(channel_setformat, vchan_setformat), KOBJMETHOD(channel_setspeed, vchan_setspeed), KOBJMETHOD(channel_trigger, vchan_trigger), KOBJMETHOD(channel_getcaps, vchan_getcaps), KOBJMETHOD(channel_getmatrix, vchan_getmatrix), KOBJMETHOD_END }; CHANNEL_DECLARE(vchan); static int sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int err, enabled, flag; bus_topo_lock(); d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d)) { bus_topo_unlock(); return (EINVAL); } bus_topo_unlock(); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: /* Exit if we do not support this direction. */ if (d->playcount < 1) { PCM_UNLOCK(d); return (ENODEV); } flag = SD_F_PVCHANS; break; case VCHAN_REC: if (d->reccount < 1) { PCM_UNLOCK(d); return (ENODEV); } flag = SD_F_RVCHANS; break; default: PCM_UNLOCK(d); return (EINVAL); } enabled = (d->flags & flag) != 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); err = sysctl_handle_int(oidp, &enabled, 0, req); if (err != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (err); } if (enabled <= 0) d->flags &= ~flag; else d->flags |= flag; PCM_RELEASE_QUICK(d); return (0); } static int sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c; uint32_t dflags; int *vchanmode, direction, ret; char dtype[16]; bus_topo_lock(); d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d)) { bus_topo_unlock(); return (EINVAL); } bus_topo_unlock(); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: if ((d->flags & SD_F_PVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_PLAY; vchanmode = &d->pvchanmode; break; case VCHAN_REC: if ((d->flags & SD_F_RVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_REC; vchanmode = &d->rvchanmode; break; default: PCM_UNLOCK(d); return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (*vchanmode & CHN_F_VCHAN_PASSTHROUGH) strlcpy(dtype, "passthrough", sizeof(dtype)); else if (*vchanmode & CHN_F_VCHAN_ADAPTIVE) strlcpy(dtype, "adaptive", sizeof(dtype)); else strlcpy(dtype, "fixed", sizeof(dtype)); ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } if (strcasecmp(dtype, "passthrough") == 0 || strcmp(dtype, "1") == 0) dflags = CHN_F_VCHAN_PASSTHROUGH; else if (strcasecmp(dtype, "adaptive") == 0 || strcmp(dtype, "2") == 0) dflags = CHN_F_VCHAN_ADAPTIVE; else if (strcasecmp(dtype, "fixed") == 0 || strcmp(dtype, "0") == 0) dflags = 0; else { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_FOREACH(c, d, channels.pcm.primary) { CHN_LOCK(c); if (c->direction != direction || dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) || (c->flags & CHN_F_PASSTHROUGH)) { CHN_UNLOCK(c); continue; } c->flags &= ~CHN_F_VCHAN_DYNAMIC; c->flags |= dflags; CHN_UNLOCK(c); *vchanmode = dflags; } PCM_RELEASE_QUICK(d); return (ret); } /* * On the fly vchan rate/format settings */ #define VCHAN_ACCESSIBLE(c) (!((c)->flags & (CHN_F_PASSTHROUGH | \ CHN_F_EXCLUSIVE)) && \ (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \ CHN_STOPPED(c))) static int sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch; int *vchanrate, direction, ret, newspd, restart; bus_topo_lock(); d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d)) { bus_topo_unlock(); return (EINVAL); } bus_topo_unlock(); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: if ((d->flags & SD_F_PVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_PLAY; vchanrate = &d->pvchanrate; break; case VCHAN_REC: if ((d->flags & SD_F_RVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_REC; vchanrate = &d->rvchanrate; break; default: PCM_UNLOCK(d); return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); newspd = *vchanrate; ret = sysctl_handle_int(oidp, &newspd, 0, req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } if (newspd < feeder_rate_min || newspd > feeder_rate_max) { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_FOREACH(c, d, channels.pcm.primary) { CHN_LOCK(c); if (c->direction != direction) { CHN_UNLOCK(c); continue; } if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) { if (CHN_STARTED(c)) { chn_abort(c); restart = 1; } else restart = 0; ret = chn_reset(c, c->format, newspd); if (ret == 0) { if (restart != 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } c->flags |= CHN_F_DIRTY; ret = chn_start(c, 1); } } } - *vchanrate = sndbuf_getspd(c->bufsoft); + *vchanrate = c->bufsoft->spd; CHN_UNLOCK(c); } PCM_RELEASE_QUICK(d); return (ret); } static int sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch; uint32_t newfmt; int *vchanformat, direction, ret, restart; char fmtstr[AFMTSTR_LEN]; bus_topo_lock(); d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d)) { bus_topo_unlock(); return (EINVAL); } bus_topo_unlock(); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: if ((d->flags & SD_F_PVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_PLAY; vchanformat = &d->pvchanformat; break; case VCHAN_REC: if ((d->flags & SD_F_RVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_REC; vchanformat = &d->rvchanformat; break; default: PCM_UNLOCK(d); return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); bzero(fmtstr, sizeof(fmtstr)); if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat) strlcpy(fmtstr, "", sizeof(fmtstr)); ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } newfmt = snd_str2afmt(fmtstr); if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_FOREACH(c, d, channels.pcm.primary) { CHN_LOCK(c); if (c->direction != direction) { CHN_UNLOCK(c); continue; } if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) { if (CHN_STARTED(c)) { chn_abort(c); restart = 1; } else restart = 0; ret = chn_reset(c, newfmt, c->speed); if (ret == 0) { if (restart != 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } c->flags |= CHN_F_DIRTY; ret = chn_start(c, 1); } } } - *vchanformat = sndbuf_getfmt(c->bufsoft); + *vchanformat = c->bufsoft->fmt; CHN_UNLOCK(c); } PCM_RELEASE_QUICK(d); return (ret); } /* virtual channel interface */ #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanformat" : "rec.vchanformat" #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanrate" : "rec.vchanrate" int vchan_create(struct pcm_channel *parent, struct pcm_channel **child) { struct snddev_info *d; struct pcm_channel *ch; struct pcmchan_caps *parent_caps; uint32_t vchanfmt, vchanspd; int ret, direction; ret = 0; d = parent->parentsnddev; PCM_BUSYASSERT(d); CHN_LOCKASSERT(parent); if (!(parent->direction == PCMDIR_PLAY || parent->direction == PCMDIR_REC)) return (EINVAL); CHN_UNLOCK(parent); PCM_LOCK(d); if (parent->direction == PCMDIR_PLAY) { direction = PCMDIR_PLAY_VIRTUAL; vchanfmt = d->pvchanformat; vchanspd = d->pvchanrate; } else { direction = PCMDIR_REC_VIRTUAL; vchanfmt = d->rvchanformat; vchanspd = d->rvchanrate; } /* create a new playback channel */ ch = chn_init(d, parent, &vchan_class, direction, parent); if (ch == NULL) { PCM_UNLOCK(d); CHN_LOCK(parent); return (ENODEV); } PCM_UNLOCK(d); CHN_LOCK(parent); CHN_INSERT_SORT_ASCEND(parent, ch, children); *child = ch; if (parent->flags & CHN_F_HAS_VCHAN) return (0); parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY; parent_caps = chn_getcaps(parent); if (parent_caps == NULL) { ret = EINVAL; goto fail; } if ((ret = chn_reset(parent, vchanfmt, vchanspd)) != 0) goto fail; /* * If the parent channel supports digital format, * enable passthrough mode. */ if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) { parent->flags &= ~CHN_F_VCHAN_DYNAMIC; parent->flags |= CHN_F_VCHAN_PASSTHROUGH; } return (ret); fail: CHN_LOCK(ch); vchan_destroy(ch); *child = NULL; return (ret); } int vchan_destroy(struct pcm_channel *c) { struct pcm_channel *parent; KASSERT(c != NULL && c->parentchannel != NULL && c->parentsnddev != NULL, ("%s(): invalid channel=%p", __func__, c)); CHN_LOCKASSERT(c); parent = c->parentchannel; PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(parent); CHN_UNLOCK(c); /* remove us from our parent's children list */ CHN_REMOVE(parent, c, children); if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); chn_reset(parent, parent->format, parent->speed); } CHN_UNLOCK(parent); /* destroy ourselves */ chn_kill(c); CHN_LOCK(parent); return (0); } int #ifdef SND_DEBUG vchan_passthrough(struct pcm_channel *c, const char *caller) #else vchan_sync(struct pcm_channel *c) #endif { int ret; KASSERT(c != NULL && c->parentchannel != NULL && (c->flags & CHN_F_VIRTUAL), ("%s(): invalid passthrough", __func__)); CHN_LOCKASSERT(c); CHN_LOCKASSERT(c->parentchannel); sndbuf_setspd(c->bufhard, c->parentchannel->speed); c->flags |= CHN_F_PASSTHROUGH; ret = feeder_chain(c); c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH); if (ret != 0) c->flags |= CHN_F_DIRTY; #ifdef SND_DEBUG if (snd_passthrough_verbose) { device_printf(c->dev, "%s(%s/%s) %s() -> re-sync err=%d\n", __func__, c->name, c->comm, caller, ret); } #endif return (ret); } static int sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, v, error; v = snd_vchans_enable; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); bus_topo_lock(); snd_vchans_enable = v >= 1; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; PCM_ACQUIRE_QUICK(d); if (snd_vchans_enable) { if (d->playcount > 0) d->flags |= SD_F_PVCHANS; if (d->reccount > 0) d->flags |= SD_F_RVCHANS; } else d->flags &= ~(SD_F_PVCHANS | SD_F_RVCHANS); PCM_RELEASE_QUICK(d); } bus_topo_unlock(); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch"); void vchan_initsys(device_t dev) { struct snddev_info *d; int unit; unit = device_get_unit(dev); d = device_get_softc(dev); /* Play */ SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanmode, "A", "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); /* Rec */ SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanmode, "A", "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); } diff --git a/sys/dev/sound/pcm/vchan.h b/sys/dev/sound/pcm/vchan.h index 8c1de9496ef3..65b0218781fb 100644 --- a/sys/dev/sound/pcm/vchan.h +++ b/sys/dev/sound/pcm/vchan.h @@ -1,76 +1,76 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 2001 Cameron Grant * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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 AUTHOR 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 AUTHOR 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. */ #ifndef _SND_VCHAN_H_ #define _SND_VCHAN_H_ extern bool snd_vchans_enable; int vchan_create(struct pcm_channel *, struct pcm_channel **); int vchan_destroy(struct pcm_channel *); #ifdef SND_DEBUG int vchan_passthrough(struct pcm_channel *, const char *); #define vchan_sync(c) vchan_passthrough(c, __func__) #else int vchan_sync(struct pcm_channel *); #endif #define VCHAN_SYNC_REQUIRED(c) \ (((c)->flags & CHN_F_VIRTUAL) && (((c)->flags & CHN_F_DIRTY) || \ - sndbuf_getfmt((c)->bufhard) != (c)->parentchannel->format || \ - sndbuf_getspd((c)->bufhard) != (c)->parentchannel->speed)) + (c)->bufhard->fmt != (c)->parentchannel->format || \ + (c)->bufhard->spd != (c)->parentchannel->speed)) void vchan_initsys(device_t); /* * Default format / rate */ #define VCHAN_DEFAULT_FORMAT SND_FORMAT(AFMT_S16_LE, 2, 0) #define VCHAN_DEFAULT_RATE 48000 #define VCHAN_PLAY 0 #define VCHAN_REC 1 /* * Offset by +/- 1 so we can distinguish bogus pointer. */ #define VCHAN_SYSCTL_DATA(x, y) \ ((void *)((intptr_t)(((((x) + 1) & 0xfff) << 2) | \ (((VCHAN_##y) + 1) & 0x3)))) #define VCHAN_SYSCTL_DATA_SIZE sizeof(void *) #define VCHAN_SYSCTL_UNIT(x) ((int)(((intptr_t)(x) >> 2) & 0xfff) - 1) #define VCHAN_SYSCTL_DIR(x) ((int)((intptr_t)(x) & 0x3) - 1) #endif /* _SND_VCHAN_H_ */