--- sys/conf/files.orig Mon Nov 21 21:23:22 2005 +++ sys/conf/files Sat Dec 31 01:39:10 2005 @@ -704,6 +704,7 @@ dev/sound/isa/sbc.c optional snd_sbc isa dev/sound/isa/sndbuf_dma.c optional sound isa dev/sound/pci/als4000.c optional snd_als4000 pci +dev/sound/pci/atiixp.c optional snd_atiixp pci #dev/sound/pci/au88x0.c optional snd_au88x0 pci dev/sound/pci/cmi.c optional snd_cmi pci dev/sound/pci/cs4281.c optional snd_cs4281 pci @@ -738,6 +739,7 @@ dev/sound/pcm/feeder_if.m optional sound dev/sound/pcm/feeder_fmt.c optional sound dev/sound/pcm/feeder_rate.c optional sound +dev/sound/pcm/feeder_volume.c optional sound dev/sound/pcm/mixer.c optional sound dev/sound/pcm/mixer_if.m optional sound dev/sound/pcm/sndstat.c optional sound --- sys/dev/sound/driver.c.orig Tue Jan 24 09:30:46 2006 +++ sys/dev/sound/driver.c Fri Jan 27 05:07:48 2006 @@ -54,6 +54,7 @@ MODULE_DEPEND(snd_driver, snd_ad1816, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_als4000, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_atiixp, 1, 1, 1); /* MODULE_DEPEND(snd_driver, snd_aureal, 1, 1, 1); */ MODULE_DEPEND(snd_driver, snd_cmi, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_cs4281, 1, 1, 1); @@ -71,8 +72,8 @@ MODULE_DEPEND(snd_driver, snd_maestro3, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_mss, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_neomagic, 1, 1, 1); -MODULE_DEPEND(snd_driver, snd_sb8, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_sb16, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_sb8, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_sbc, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_solo, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_t4dwave, 1, 1, 1); --- sys/sys/soundcard.h.orig Fri Sep 16 06:21:09 2005 +++ sys/sys/soundcard.h Fri Sep 16 06:32:00 2005 @@ -180,6 +180,10 @@ #define AFMT_S32_BE 0x00002000 /* Big endian signed 32-bit */ #define AFMT_U32_LE 0x00004000 /* Little endian unsigned 32-bit */ #define AFMT_U32_BE 0x00008000 /* Big endian unsigned 32-bit */ +#define AFMT_S24_LE 0x00010000 /* Little endian signed 24-bit */ +#define AFMT_S24_BE 0x00020000 /* Big endian signed 24-bit */ +#define AFMT_U24_LE 0x00040000 /* Little endian unsigned 24-bit */ +#define AFMT_U24_BE 0x00080000 /* Big endian unsigned 24-bit */ #define AFMT_STEREO 0x10000000 /* can do/want stereo */ --- sys/modules/sound/sound/Makefile.orig Sat Dec 31 01:52:33 2005 +++ sys/modules/sound/sound/Makefile Sat Dec 31 01:40:11 2005 @@ -8,7 +8,7 @@ SRCS+= ac97_if.h channel_if.h feeder_if.h mixer_if.h SRCS+= ac97_if.c channel_if.c feeder_if.c mixer_if.c SRCS+= ac97.c ac97_patch.c buffer.c channel.c dsp.c -SRCS+= fake.c feeder.c feeder_fmt.c feeder_rate.c +SRCS+= fake.c feeder.c feeder_fmt.c feeder_rate.c feeder_volume.c SRCS+= mixer.c sndstat.c sound.c vchan.c SRCS+= sndbuf_dma.c --- sys/modules/sound/driver/Makefile.orig Mon Nov 21 21:19:20 2005 +++ sys/modules/sound/driver/Makefile Mon Nov 21 21:19:32 2005 @@ -3,7 +3,7 @@ .if ${MACHINE_ARCH} == "sparc64" SUBDIR = audiocs .else -SUBDIR = als4000 ad1816 cmi cs4281 csa ds1 emu10k1 es137x ess +SUBDIR = als4000 ad1816 atiixp cmi cs4281 csa ds1 emu10k1 es137x ess SUBDIR += fm801 ich maestro maestro3 mss neomagic sb16 sb8 sbc solo SUBDIR += t4dwave via8233 via82c686 vibes SUBDIR += driver uaudio --- sys/dev/sound/pci/maestro.c.orig Tue Jan 17 14:18:22 2006 +++ sys/dev/sound/pci/maestro.c Tue Jan 17 14:18:45 2006 @@ -785,7 +785,7 @@ AFMT_STEREO | AFMT_S16_LE, 0 }; - static struct pcmchan_caps playcaps = {2000, 96000, playfmt, 0}; + static struct pcmchan_caps playcaps = {8000, 48000, playfmt, 0}; static u_int32_t recfmt[] = { AFMT_S8, @@ -794,7 +794,7 @@ AFMT_STEREO | AFMT_S16_LE, 0 }; - static struct pcmchan_caps reccaps = {4000, 48000, recfmt, 0}; + static struct pcmchan_caps reccaps = {8000, 48000, recfmt, 0}; return (((struct agg_chinfo*)data)->dir == PCMDIR_PLAY)? &playcaps : &reccaps; --- sys/dev/sound/pci/atiixp.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/atiixp.c Sun Dec 25 08:43:03 2005 @@ -0,0 +1,1202 @@ +/*- + * 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. + */ + +#include +#include + +#include +#include +#include +#include + +#include + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/atiixp.c,v 1.6 2006/03/22 21:29:47 ariff Exp $"); + +#define ATI_IXP_DMA_RETRY_MAX 100 +#define ATI_IXP_DMA_CHSEGS_DEFAULT 2 + +#define ATI_IXP_BUFSZ_MIN 4096 +#define ATI_IXP_BUFSZ_MAX 65536 +#define ATI_IXP_BUFSZ_DEFAULT 16384 + +#ifdef ATI_IXP_DEBUG_VERBOSE +#undef ATI_IXP_DEBUG +#define ATI_IXP_DEBUG 1 +#endif + +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, dma_dt_cur_bit; + uint32_t dma_segs, dma_blksz; +#ifdef ATI_IXP_DEBUG + uint32_t dma_ptr, dma_prevptr; +#endif + uint32_t fmt; + int caps_32bit, dir, active; +}; + +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 dma_segs; + int registered_channels; + + struct mtx *lock; +}; + +#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[] = { + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; + +static uint32_t atiixp_fmt[] = { + AFMT_STEREO | AFMT_S16_LE, + 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" }, +}; + +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 int atiixp_chan_setspeed(kobj_t, void *, uint32_t); +static int 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 int 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), + { 0, 0 } +}; +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->dma_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->dma_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->dma_segs = sc->dma_segs; + ch->dma_blksz = sc->bufsz / sc->dma_segs; + + atiixp_unlock(sc); + + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) == -1) + return NULL; + + atiixp_lock(sc); + num = sc->registered_channels++; + ch->sgd_table = &sc->sgd_table[num * ch->dma_segs]; + ch->sgd_addr = sc->sgd_addr + + (num * ch->dma_segs * 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 int +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_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + + /* XXX Force static blocksize */ + sndbuf_resize(ch->buffer, ch->dma_segs, ch->dma_blksz); + + if (ch->dma_blksz != sndbuf_getblksz(ch->buffer)) { + device_printf(sc->dev, + "%s: WARNING - dma_blksz=%u != blksz=%u !!!\n", + __func__, ch->dma_blksz, sndbuf_getblksz(ch->buffer)); + ch->dma_blksz = sndbuf_getblksz(ch->buffer); + } + + return ch->dma_blksz; +} + +static void +atiixp_buildsgdt(struct atiixp_chinfo *ch) +{ + uint32_t addr; + int i; + + addr = sndbuf_getbufaddr(ch->buffer); + + for (i = 0; i < ch->dma_segs; i++) { + ch->sgd_table[i].addr = htole32(addr + (i * ch->dma_blksz)); + ch->sgd_table[i].status = htole16(0); + ch->sgd_table[i].size = htole16(ch->dma_blksz >> 2); + ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + + (((i + 1) % ch->dma_segs) * + sizeof(struct atiixp_dma_op))); + } + +#ifdef ATI_IXP_DEBUG + ch->dma_ptr = 0; + ch->dma_prevptr = 0; +#endif +} + +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; + + 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); + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + atiixp_disable_dma(ch); + atiixp_flush_dma(ch); + 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 __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->dma_dt_cur_bit; + addr = sndbuf_getbufaddr(ch->buffer); + sz = ch->dma_segs * ch->dma_blksz; + retry = ATI_IXP_DMA_RETRY_MAX; + + do { + ptr = atiixp_rd(sc, reg); + if (ptr < addr) + continue; + ptr -= addr; + if (ptr < sz) { +#ifdef ATI_IXP_DEBUG + if ((ptr & ~(ch->dma_blksz - 1)) != ch->dma_ptr) { + uint32_t delta; + + delta = (sz + ptr - ch->dma_prevptr) % sz; +#ifndef ATI_IXP_DEBUG_VERBOSE + if (delta < ch->dma_blksz) +#endif + device_printf(sc->dev, + "PCMDIR_%s: incoherent DMA " + "dma_prevptr=%u ptr=%u " + "dma_ptr=%u dma_segs=%u " + "[delta=%u != dma_blksz=%u] " + "(%s)\n", + (ch->dir == PCMDIR_PLAY) ? + "PLAY" : "REC", + ch->dma_prevptr, ptr, + ch->dma_ptr, ch->dma_segs, + delta, ch->dma_blksz, + (delta < ch->dma_blksz) ? + "OVERLAPPED!" : "Ok"); + ch->dma_ptr = ptr & ~(ch->dma_blksz - 1); + } + ch->dma_prevptr = ptr; +#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 int +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); + 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_trigger, atiixp_chan_trigger), + KOBJMETHOD(channel_getptr, atiixp_chan_getptr), + KOBJMETHOD(channel_getcaps, atiixp_chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(atiixp_chan); + +/* + * PCI driver interface + */ +static void +atiixp_intr(void *p) +{ + struct atiixp_info *sc = p; + struct atiixp_chinfo *ch; + uint32_t status, enable, detected_codecs; + + atiixp_lock(sc); + status = atiixp_rd(sc, ATI_REG_ISR); + + if (status == 0) { + atiixp_unlock(sc); + return; + } + + if ((status & ATI_REG_ISR_IN_STATUS) && sc->rch.channel) { + ch = &sc->rch; +#ifdef ATI_IXP_DEBUG + ch->dma_ptr = (ch->dma_ptr + ch->dma_blksz) % + (ch->dma_segs * ch->dma_blksz); +#endif + atiixp_unlock(sc); + chn_intr(ch->channel); + atiixp_lock(sc); + } + if ((status & ATI_REG_ISR_OUT_STATUS) && sc->pch.channel) { + ch = &sc->pch; +#ifdef ATI_IXP_DEBUG + ch->dma_ptr = (ch->dma_ptr + ch->dma_blksz) % + (ch->dma_segs * ch->dma_blksz); +#endif + atiixp_unlock(sc); + chn_intr(ch->channel); + atiixp_lock(sc); + } + +#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 interupt sources */ + enable = atiixp_rd(sc, ATI_REG_IER); + enable &= ~detected_codecs; + atiixp_wr(sc, ATI_REG_IER, enable); + } + + /* acknowledge */ + atiixp_wr(sc, ATI_REG_ISR, status); + atiixp_unlock(sc); +} + +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 void +atiixp_chip_post_init(void *arg) +{ + struct atiixp_info *sc = (struct atiixp_info *)arg; + uint32_t subdev; + int i, timeout, found; + 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; + } + + /* wait for the interrupts to happen */ + timeout = 100; + do { + msleep(sc, sc->lock, PWAIT, "ixpslp", 1); + if (sc->codec_not_ready_bits) + break; + } while (--timeout); + + atiixp_disable_interrupts(sc); + + if (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 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); + + if (pcm_register(sc->dev, sc, ATI_IXP_NPCHAN, ATI_IXP_NRCHAN)) + goto postinitbad; + + 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); + + snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s", + rman_get_start(sc->reg), rman_get_start(sc->irq), + PCM_KLDSTRING(snd_atiixp)); + + pcm_setstatus(sc->dev, status); + + atiixp_lock(sc); + 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->codec) { + ac97_destroy(sc->codec); + sc->codec = NULL; + } + if (sc->ih) { + bus_teardown_intr(sc->dev, sc->irq, sc->ih); + sc->ih = 0; + } + if (sc->reg) { + bus_release_resource(sc->dev, sc->regtype, sc->regid, sc->reg); + sc->reg = 0; + } + if (sc->irq) { + bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irqid, sc->irq); + sc->irq = 0; + } + if (sc->parent_dmat) { + bus_dma_tag_destroy(sc->parent_dmat); + sc->parent_dmat = 0; + } + if (sc->sgd_dmamap) { + bus_dmamap_unload(sc->sgd_dmat, sc->sgd_dmamap); + sc->sgd_dmamap = 0; + } + if (sc->sgd_dmat) { + bus_dma_tag_destroy(sc->sgd_dmat); + sc->sgd_dmat = 0; + } + if (sc->lock) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } +} + +static int +atiixp_pci_probe(device_t dev) +{ + int i; + uint16_t devid, vendor; + + vendor = pci_get_vendor(dev); + devid = pci_get_device(dev); + for (i = 0; i < sizeof(atiixp_hw) / sizeof(atiixp_hw[0]); i++) { + if (vendor == atiixp_hw[i].vendor && + devid == atiixp_hw[i].devid) { + device_set_desc(dev, atiixp_hw[i].desc); + return 0; + } + } + + return ENXIO; +} + +static int +atiixp_pci_attach(device_t dev) +{ + struct atiixp_info *sc; + int i; + + if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc->dev = dev; + /* + * Default DMA segments per playback / recording channel + */ + sc->dma_segs = ATI_IXP_DMA_CHSEGS_DEFAULT; + + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + 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), "dma_segs", + &i) == 0) { + if (i < ATI_IXP_DMA_CHSEGS_MIN) + i = ATI_IXP_DMA_CHSEGS_MIN; + if (i > ATI_IXP_DMA_CHSEGS_MAX) + i = ATI_IXP_DMA_CHSEGS_MAX; + sc->dma_segs = i; + } + + /* + * round the value to the nearest ^2 + */ + i = 0; + while (sc->dma_segs >> i) + i++; + sc->dma_segs = 1 << (i - 1); + if (sc->dma_segs < ATI_IXP_DMA_CHSEGS_MIN) + sc->dma_segs = ATI_IXP_DMA_CHSEGS_MIN; + else if (sc->dma_segs > ATI_IXP_DMA_CHSEGS_MAX) + sc->dma_segs = ATI_IXP_DMA_CHSEGS_MAX; + + /* + * DMA tag for scatter-gather buffers and link pointers + */ + if (bus_dma_tag_create(/*parent*/NULL, /*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*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/sc->dma_segs * 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, + sc->dma_segs * 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); + free(sc, M_DEVBUF); + } + return 0; +} + +static int +atiixp_pci_suspend(device_t dev) +{ + struct atiixp_info *sc = pcm_getdevinfo(dev); + uint32_t value; + + /* quickly disable interrupts and save channels active state */ + atiixp_lock(sc); + atiixp_disable_interrupts(sc); + value = atiixp_rd(sc, ATI_REG_CMD); + sc->pch.active = (value & ATI_REG_CMD_SEND_EN) ? 1 : 0; + sc->rch.active = (value & ATI_REG_CMD_RECEIVE_EN) ? 1 : 0; + atiixp_unlock(sc); + + /* stop everything */ + if (sc->pch.channel && sc->pch.active) + atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); + if (sc->rch.channel && sc->rch.active) + atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); + + /* power down aclink and pci bus */ + atiixp_lock(sc); + value = atiixp_rd(sc, ATI_REG_CMD); + value |= ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET; + atiixp_wr(sc, ATI_REG_CMD, ATI_REG_CMD_POWERDOWN); + pci_set_powerstate(dev, PCI_POWERSTATE_D3); + atiixp_unlock(sc); + + return 0; +} + +static int +atiixp_pci_resume(device_t dev) +{ + struct atiixp_info *sc = pcm_getdevinfo(dev); + + atiixp_lock(sc); + /* power up pci bus */ + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + pci_enable_io(dev, SYS_RES_MEMORY); + pci_enable_busmaster(dev); + /* 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) { + if (sc->pch.fmt) + atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); + if (sc->pch.active) + atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); + } + if (sc->rch.channel) { + if (sc->rch.fmt) + atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); + if (sc->rch.active) + atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); + } + + /* enable interrupts */ + atiixp_lock(sc); + 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, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_atiixp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_atiixp, 1); --- sys/dev/sound/pci/atiixp.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/atiixp.h Sun Nov 27 11:29:59 2005 @@ -0,0 +1,202 @@ +/*- + * 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, 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. + * + * $FreeBSD: src/sys/dev/sound/pci/atiixp.h,v 1.2 2006/02/18 10:23:37 ariff Exp $ + */ + +#ifndef _ATIIXP_H_ +#define _ATIIXP_H_ + +/* + * Constants, pretty much FreeBSD specific. + */ + +/* Number of playback / recording channel */ +#define ATI_IXP_NPCHAN 1 +#define ATI_IXP_NRCHAN 1 +#define ATI_IXP_NCHANS (ATI_IXP_NPCHAN + ATI_IXP_NRCHAN) + +/* + * Maximum segments/descriptors is 256, but 2 for + * each channel should be more than enough for us. + */ +#define ATI_IXP_DMA_CHSEGS 2 +#define ATI_IXP_DMA_CHSEGS_MIN 2 +#define ATI_IXP_DMA_CHSEGS_MAX 256 + +#define ATI_VENDOR_ID 0x1002 /* ATI Technologies */ + +#define ATI_IXP_200_ID 0x4341 +#define ATI_IXP_300_ID 0x4361 +#define ATI_IXP_400_ID 0x4370 + +#define ATI_IXP_BASE_RATE 48000 + +/* + * Register definitions for ATI IXP + * + * References: ALSA snd-atiixp.c , OpenBSD/NetBSD auixp-*.h + */ + +#define ATI_IXP_CODECS 3 + +#define ATI_REG_ISR 0x00 /* interrupt source */ +#define ATI_REG_ISR_IN_XRUN (1U<<0) +#define ATI_REG_ISR_IN_STATUS (1U<<1) +#define ATI_REG_ISR_OUT_XRUN (1U<<2) +#define ATI_REG_ISR_OUT_STATUS (1U<<3) +#define ATI_REG_ISR_SPDF_XRUN (1U<<4) +#define ATI_REG_ISR_SPDF_STATUS (1U<<5) +#define ATI_REG_ISR_PHYS_INTR (1U<<8) +#define ATI_REG_ISR_PHYS_MISMATCH (1U<<9) +#define ATI_REG_ISR_CODEC0_NOT_READY (1U<<10) +#define ATI_REG_ISR_CODEC1_NOT_READY (1U<<11) +#define ATI_REG_ISR_CODEC2_NOT_READY (1U<<12) +#define ATI_REG_ISR_NEW_FRAME (1U<<13) + +#define ATI_REG_IER 0x04 /* interrupt enable */ +#define ATI_REG_IER_IN_XRUN_EN (1U<<0) +#define ATI_REG_IER_IO_STATUS_EN (1U<<1) +#define ATI_REG_IER_OUT_XRUN_EN (1U<<2) +#define ATI_REG_IER_OUT_XRUN_COND (1U<<3) +#define ATI_REG_IER_SPDF_XRUN_EN (1U<<4) +#define ATI_REG_IER_SPDF_STATUS_EN (1U<<5) +#define ATI_REG_IER_PHYS_INTR_EN (1U<<8) +#define ATI_REG_IER_PHYS_MISMATCH_EN (1U<<9) +#define ATI_REG_IER_CODEC0_INTR_EN (1U<<10) +#define ATI_REG_IER_CODEC1_INTR_EN (1U<<11) +#define ATI_REG_IER_CODEC2_INTR_EN (1U<<12) +#define ATI_REG_IER_NEW_FRAME_EN (1U<<13) /* (RO) */ +#define ATI_REG_IER_SET_BUS_BUSY (1U<<14) /* (WO) audio is running */ + +#define ATI_REG_CMD 0x08 /* command */ +#define ATI_REG_CMD_POWERDOWN (1U<<0) +#define ATI_REG_CMD_RECEIVE_EN (1U<<1) +#define ATI_REG_CMD_SEND_EN (1U<<2) +#define ATI_REG_CMD_STATUS_MEM (1U<<3) +#define ATI_REG_CMD_SPDF_OUT_EN (1U<<4) +#define ATI_REG_CMD_SPDF_STATUS_MEM (1U<<5) +#define ATI_REG_CMD_SPDF_THRESHOLD (3U<<6) +#define ATI_REG_CMD_SPDF_THRESHOLD_SHIFT 6 +#define ATI_REG_CMD_IN_DMA_EN (1U<<8) +#define ATI_REG_CMD_OUT_DMA_EN (1U<<9) +#define ATI_REG_CMD_SPDF_DMA_EN (1U<<10) +#define ATI_REG_CMD_SPDF_OUT_STOPPED (1U<<11) +#define ATI_REG_CMD_SPDF_CONFIG_MASK (7U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_34 (1U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_78 (2U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_69 (3U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_01 (4U<<12) +#define ATI_REG_CMD_INTERLEAVE_SPDF (1U<<16) +#define ATI_REG_CMD_AUDIO_PRESENT (1U<<20) +#define ATI_REG_CMD_INTERLEAVE_IN (1U<<21) +#define ATI_REG_CMD_INTERLEAVE_OUT (1U<<22) +#define ATI_REG_CMD_LOOPBACK_EN (1U<<23) +#define ATI_REG_CMD_PACKED_DIS (1U<<24) +#define ATI_REG_CMD_BURST_EN (1U<<25) +#define ATI_REG_CMD_PANIC_EN (1U<<26) +#define ATI_REG_CMD_MODEM_PRESENT (1U<<27) +#define ATI_REG_CMD_ACLINK_ACTIVE (1U<<28) +#define ATI_REG_CMD_AC_SOFT_RESET (1U<<29) +#define ATI_REG_CMD_AC_SYNC (1U<<30) +#define ATI_REG_CMD_AC_RESET (1U<<31) + +#define ATI_REG_PHYS_OUT_ADDR 0x0c +#define ATI_REG_PHYS_OUT_CODEC_MASK (3U<<0) +#define ATI_REG_PHYS_OUT_RW (1U<<2) +#define ATI_REG_PHYS_OUT_ADDR_EN (1U<<8) +#define ATI_REG_PHYS_OUT_ADDR_SHIFT 9 +#define ATI_REG_PHYS_OUT_DATA_SHIFT 16 + +#define ATI_REG_PHYS_IN_ADDR 0x10 +#define ATI_REG_PHYS_IN_READ_FLAG (1U<<8) +#define ATI_REG_PHYS_IN_ADDR_SHIFT 9 +#define ATI_REG_PHYS_IN_DATA_SHIFT 16 + +#define ATI_REG_SLOTREQ 0x14 + +#define ATI_REG_COUNTER 0x18 +#define ATI_REG_COUNTER_SLOT (3U<<0) /* slot # */ +#define ATI_REG_COUNTER_BITCLOCK (31U<<8) + +#define ATI_REG_IN_FIFO_THRESHOLD 0x1c + +#define ATI_REG_IN_DMA_LINKPTR 0x20 +#define ATI_REG_IN_DMA_DT_START 0x24 /* RO */ +#define ATI_REG_IN_DMA_DT_NEXT 0x28 /* RO */ +#define ATI_REG_IN_DMA_DT_CUR 0x2c /* RO */ +#define ATI_REG_IN_DMA_DT_SIZE 0x30 + +#define ATI_REG_OUT_DMA_SLOT 0x34 +#define ATI_REG_OUT_DMA_SLOT_BIT(x) (1U << ((x) - 3)) +#define ATI_REG_OUT_DMA_SLOT_MASK 0x1ff +#define ATI_REG_OUT_DMA_THRESHOLD_MASK 0xf800 +#define ATI_REG_OUT_DMA_THRESHOLD_SHIFT 11 + +#define ATI_REG_OUT_DMA_LINKPTR 0x38 +#define ATI_REG_OUT_DMA_DT_START 0x3c /* RO */ +#define ATI_REG_OUT_DMA_DT_NEXT 0x40 /* RO */ +#define ATI_REG_OUT_DMA_DT_CUR 0x44 /* RO */ +#define ATI_REG_OUT_DMA_DT_SIZE 0x48 + +#define ATI_REG_SPDF_CMD 0x4c +#define ATI_REG_SPDF_CMD_LFSR (1U<<4) +#define ATI_REG_SPDF_CMD_SINGLE_CH (1U<<5) +#define ATI_REG_SPDF_CMD_LFSR_ACC (0xff<<8) /* RO */ + +#define ATI_REG_SPDF_DMA_LINKPTR 0x50 +#define ATI_REG_SPDF_DMA_DT_START 0x54 /* RO */ +#define ATI_REG_SPDF_DMA_DT_NEXT 0x58 /* RO */ +#define ATI_REG_SPDF_DMA_DT_CUR 0x5c /* RO */ +#define ATI_REG_SPDF_DMA_DT_SIZE 0x60 + +#define ATI_REG_MODEM_MIRROR 0x7c +#define ATI_REG_AUDIO_MIRROR 0x80 + +#define ATI_REG_6CH_REORDER 0x84 /* reorder slots for 6ch */ +#define ATI_REG_6CH_REORDER_EN (1U<<0) /* 3,4,7,8,6,9 -> 3,4,6,9,7,8 */ + +#define ATI_REG_FIFO_FLUSH 0x88 +#define ATI_REG_FIFO_OUT_FLUSH (1U<<0) +#define ATI_REG_FIFO_IN_FLUSH (1U<<1) + +/* LINKPTR */ +#define ATI_REG_LINKPTR_EN (1U<<0) + +/* [INT|OUT|SPDIF]_DMA_DT_SIZE */ +#define ATI_REG_DMA_DT_SIZE (0xffffU<<0) +#define ATI_REG_DMA_FIFO_USED (0x1fU<<16) +#define ATI_REG_DMA_FIFO_FREE (0x1fU<<21) +#define ATI_REG_DMA_STATE (7U<<26) + +#define ATI_MAX_DESCRIPTORS 256 /* max number of descriptor packets */ + +/* codec detection constant indicating the interrupt flags */ +#define ALL_CODECS_NOT_READY \ + (ATI_REG_ISR_CODEC0_NOT_READY | ATI_REG_ISR_CODEC1_NOT_READY |\ + ATI_REG_ISR_CODEC2_NOT_READY) +#define CODEC_CHECK_BITS (ALL_CODECS_NOT_READY|ATI_REG_ISR_NEW_FRAME) + +#endif --- sys/modules/sound/driver/atiixp/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/atiixp/Makefile Mon Nov 21 20:50:28 2005 @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci + +KMOD= snd_atiixp +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= atiixp.c + +.include --- sys/dev/sound/isa/ad1816.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/ad1816.c Fri Sep 16 06:32:00 2005 @@ -138,12 +138,16 @@ } /* check for capture interupt */ if (sndbuf_runsz(ad1816->rch.buffer) && (c & AD1816_INTRCI)) { + ad1816_unlock(ad1816); chn_intr(ad1816->rch.channel); + ad1816_lock(ad1816); served |= AD1816_INTRCI; /* cp served */ } /* check for playback interupt */ if (sndbuf_runsz(ad1816->pch.buffer) && (c & AD1816_INTRPI)) { + ad1816_unlock(ad1816); chn_intr(ad1816->pch.channel); + ad1816_lock(ad1816); served |= AD1816_INTRPI; /* pb served */ } if (served == 0) { @@ -575,6 +579,9 @@ switch (logical_id) { case 0x80719304: /* ADS7180 */ s = "AD1816"; + break; + case 0x50719304: /* ADS7150 */ + s = "AD1815"; break; } --- sys/dev/sound/isa/ess.c.orig Tue Jan 17 06:04:40 2006 +++ sys/dev/sound/isa/ess.c Tue Jan 17 06:06:28 2006 @@ -61,7 +61,7 @@ 0 }; -static struct pcmchan_caps ess_playcaps = {5000, 49000, ess_pfmt, 0}; +static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_pfmt, 0}; static u_int32_t ess_rfmt[] = { AFMT_U8, @@ -75,7 +75,7 @@ 0 }; -static struct pcmchan_caps ess_reccaps = {5000, 49000, ess_rfmt, 0}; +static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_rfmt, 0}; struct ess_info; @@ -361,8 +361,11 @@ rirq = (src & sc->rch.hwch)? 1 : 0; if (pirq) { - if (sc->pch.run) + if (sc->pch.run) { + ess_unlock(sc); chn_intr(sc->pch.channel); + ess_lock(sc); + } if (sc->pch.stopping) { sc->pch.run = 0; sndbuf_dma(sc->pch.buffer, PCMTRIG_STOP); @@ -375,8 +378,11 @@ } if (rirq) { - if (sc->rch.run) + if (sc->rch.run) { + ess_unlock(sc); chn_intr(sc->rch.channel); + ess_lock(sc); + } if (sc->rch.stopping) { sc->rch.run = 0; sndbuf_dma(sc->rch.buffer, PCMTRIG_STOP); --- sys/dev/sound/isa/mss.c.orig Mon Nov 7 09:03:25 2005 +++ sys/dev/sound/isa/mss.c Mon Nov 7 09:03:41 2005 @@ -795,11 +795,15 @@ c &= ~served; if (sndbuf_runsz(mss->pch.buffer) && (c & 0x10)) { served |= 0x10; + mss_unlock(mss); chn_intr(mss->pch.channel); + mss_lock(mss); } if (sndbuf_runsz(mss->rch.buffer) && (c & 0x20)) { served |= 0x20; + mss_unlock(mss); chn_intr(mss->rch.channel); + mss_lock(mss); } /* now ack the interrupt */ if (FULL_DUPLEX(mss)) ad_write(mss, 24, ~c); /* ack selectively */ @@ -1111,8 +1115,16 @@ return; } - if (sndbuf_runsz(mss->rch.buffer) && (mc11 & 8)) chn_intr(mss->rch.channel); - if (sndbuf_runsz(mss->pch.buffer) && (mc11 & 4)) chn_intr(mss->pch.channel); + if (sndbuf_runsz(mss->rch.buffer) && (mc11 & 8)) { + mss_unlock(mss); + chn_intr(mss->rch.channel); + mss_lock(mss); + } + if (sndbuf_runsz(mss->pch.buffer) && (mc11 & 4)) { + mss_unlock(mss); + chn_intr(mss->pch.channel); + mss_lock(mss); + } opti_wr(mss, 11, ~mc11); /* ack */ if (--loops) goto again; mss_unlock(mss); --- sys/dev/sound/isa/sb16.c.orig Sun Nov 13 01:22:14 2005 +++ sys/dev/sound/isa/sb16.c Sun Nov 13 01:29:11 2005 @@ -370,23 +370,32 @@ sb16mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); - u_char recdev; + u_char recdev_l, recdev_r; - recdev = 0; - if (src & SOUND_MASK_MIC) - recdev |= 0x01; /* mono mic */ + recdev_l = 0; + recdev_r = 0; + if (src & SOUND_MASK_MIC) { + recdev_l |= 0x01; /* mono mic */ + recdev_r |= 0x01; + } - if (src & SOUND_MASK_CD) - recdev |= 0x06; /* l+r cd */ + if (src & SOUND_MASK_CD) { + recdev_l |= 0x04; /* l cd */ + recdev_r |= 0x02; /* r cd */ + } - if (src & SOUND_MASK_LINE) - recdev |= 0x18; /* l+r line */ + if (src & SOUND_MASK_LINE) { + recdev_l |= 0x10; /* l line */ + recdev_r |= 0x08; /* r line */ + } - if (src & SOUND_MASK_SYNTH) - recdev |= 0x60; /* l+r midi */ + if (src & SOUND_MASK_SYNTH) { + recdev_l |= 0x40; /* l midi */ + recdev_r |= 0x20; /* r midi */ + } - sb_setmixer(sb, SB16_IMASK_L, recdev); - sb_setmixer(sb, SB16_IMASK_R, recdev); + sb_setmixer(sb, SB16_IMASK_L, recdev_l); + sb_setmixer(sb, SB16_IMASK_R, recdev_r); /* Switch on/off FM tuner source */ if (src & SOUND_MASK_LINE1) @@ -494,7 +503,7 @@ sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; - int reason = 3, c; + int reason, c; /* * The Vibra16X has separate flags for 8 and 16 bit transfers, but @@ -570,8 +579,9 @@ sb_reset_dsp(sb); if (sb->bd_flags & BD_F_SB16X) { + /* full-duplex doesn't work! */ pprio = sb->pch.run? 1 : 0; - sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq1 : NULL); + sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq1 : sb->drq2); sb->pch.dch = pprio? 1 : 0; sndbuf_dmasetup(sb->rch.buffer, pprio? sb->drq2 : sb->drq1); sb->rch.dch = pprio? 2 : 1; @@ -848,7 +858,7 @@ else status2[0] = '\0'; - snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %ud %s", + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u %s", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1), status2, sb->bufsize, PCM_KLDSTRING(snd_sb16)); --- sys/dev/sound/isa/sb8.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/sb8.c Fri Sep 16 06:32:00 2005 @@ -475,11 +475,17 @@ struct sb_info *sb = (struct sb_info *)arg; sb_lock(sb); - if (sndbuf_runsz(sb->pch.buffer) > 0) + if (sndbuf_runsz(sb->pch.buffer) > 0) { + sb_unlock(sb); chn_intr(sb->pch.channel); + sb_lock(sb); + } - if (sndbuf_runsz(sb->rch.buffer) > 0) + if (sndbuf_runsz(sb->rch.buffer) > 0) { + sb_unlock(sb); chn_intr(sb->rch.channel); + sb_lock(sb); + } sb_rd(sb, DSP_DATA_AVAIL); /* int ack */ sb_unlock(sb); @@ -564,8 +570,16 @@ sb_lock(sb); if (sb->bd_flags & BD_F_HISPEED) sb_reset_dsp(sb); - else + else { +#if 0 + /* + * NOTE: DSP_CMD_DMAEXIT_8 does not work with old + * soundblaster. + */ sb_cmd(sb, DSP_CMD_DMAEXIT_8); +#endif + sb_reset_dsp(sb); + } if (play) sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ --- sys/dev/sound/isa/sbc.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/sbc.c Fri Sep 16 06:32:00 2005 @@ -259,6 +259,7 @@ {0x81167316, "ESS ES1681"}, /* ESS1681 */ {0x02017316, "ESS ES1688"}, /* ESS1688 */ + {0x68097316, "ESS ES1688"}, /* ESS1688 */ {0x68187316, "ESS ES1868"}, /* ESS1868 */ {0x03007316, "ESS ES1869"}, /* ESS1869 */ {0x69187316, "ESS ES1869"}, /* ESS1869 */ --- sys/dev/sound/pci/als4000.c.orig Mon Nov 7 12:01:29 2005 +++ sys/dev/sound/pci/als4000.c Mon Nov 7 12:06:55 2005 @@ -75,6 +75,7 @@ struct resource *reg, *irq; int regid, irqid; void *ih; + struct mtx *lock; unsigned int bufsz; struct sc_chinfo pch, rch; @@ -90,7 +91,11 @@ 0 }; -static struct pcmchan_caps als_caps = { 4000, 48000, als_format, 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 */ @@ -199,6 +204,7 @@ 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; @@ -213,9 +219,11 @@ ch->format = AFMT_U8; ch->speed = DSP_DEFAULT_SPEED; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + snd_mtxunlock(sc->lock); + + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) return NULL; - } + return ch; } @@ -263,9 +271,12 @@ 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); return (2 * sz - pos - 1) % sz; } @@ -378,7 +389,9 @@ alspchan_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_playback_start(ch); @@ -387,6 +400,7 @@ als_playback_stop(ch); break; } + snd_mtxunlock(sc->lock); return 0; } @@ -468,7 +482,9 @@ 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); @@ -477,6 +493,7 @@ als_capture_stop(ch); break; } + snd_mtxunlock(sc->lock); return 0; } @@ -578,8 +595,13 @@ for (i = l = r = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (src & (1 << i)) { - l |= amt[i].iselect; - r |= amt[i].iselect << 1; + if (amt[i].iselect == 1) { /* microphone */ + l |= amt[i].iselect; + r |= amt[i].iselect; + } else { + l |= amt[i].iselect; + r |= amt[i].iselect >> 1; + } } } @@ -605,13 +627,20 @@ 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) + if (intr & 0x80) { + snd_mtxunlock(sc->lock); chn_intr(sc->pch.channel); + snd_mtxlock(sc->lock); + } - if (intr & 0x40) + 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); @@ -627,6 +656,8 @@ 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; } @@ -708,6 +739,10 @@ bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } + if (sc->lock) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } } static int @@ -730,7 +765,7 @@ goto bad; } - if (bus_setup_intr(dev, sc->irq, INTR_TYPE_AV, als_intr, + if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, als_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; @@ -745,8 +780,8 @@ /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &sc->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -768,6 +803,7 @@ return ENXIO; } + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -851,9 +887,11 @@ { 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; } @@ -862,13 +900,17 @@ { 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; } @@ -879,6 +921,8 @@ if (sc->rch.dma_was_active) { als_capture_start(&sc->rch); } + snd_mtxunlock(sc->lock); + return 0; } --- sys/dev/sound/pci/cmi.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/cmi.c Fri Sep 16 06:32:00 2005 @@ -876,8 +876,8 @@ /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, - /*lockfunc*/busdma_lock_mutex, - /*lockfunc*/&Giant, + /*lockfunc*/NULL, + /*lockfunc*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "cmi_attach: Unable to create dma tag\n"); goto bad; --- sys/dev/sound/pci/es137x.c.orig Sat Oct 29 04:24:45 2005 +++ sys/dev/sound/pci/es137x.c Thu Jan 12 22:28:01 2006 @@ -61,9 +61,6 @@ SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/es137x.c,v 1.51.2.2 2005/01/30 01:00:04 imp Exp $"); -static int debug = 0; -SYSCTL_INT(_debug, OID_AUTO, es_debug, CTLFLAG_RW, &debug, 0, ""); - #define MEM_MAP_REG 0x14 /* PCI IDs of supported chips */ @@ -90,6 +87,15 @@ #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 ES1370_DAC1_MINSPEED 5512 +#define ES1370_DAC1_MAXSPEED 44100 + /* device private data */ struct es_info; @@ -97,10 +103,73 @@ struct es_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; - int dir, num; + struct pcmchan_caps caps; + int dir, num, index; u_int32_t fmt, blksz, bufsz; }; +/* + * 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; @@ -115,41 +184,35 @@ unsigned int bufsz; /* Contents of board's registers */ - u_long ctrl; - u_long sctrl; - struct es_chinfo pch, rch; + uint32_t ctrl; + uint32_t sctrl; + uint32_t escfg; + struct es_chinfo ch[ES_NCHANS]; + struct mtx *lock; }; -/* -------------------------------------------------------------------- */ +#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 u_int es1371_wait_src_ready(struct es_info *); +static uint32_t es1371_wait_src_ready(struct es_info *); static void es1371_src_write(struct es_info *, u_short, unsigned short); static u_int es1371_adc_rate(struct es_info *, u_int, int); static u_int es1371_dac_rate(struct es_info *, u_int, int); -static int es1371_init(struct es_info *, device_t); +static int es1371_init(struct es_info *); static int es1370_init(struct es_info *); static int es1370_wrcodec(struct es_info *, u_char, u_char); -static u_int32_t es_playfmt[] = { +static u_int32_t es_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; -static struct pcmchan_caps es_playcaps = {4000, 48000, es_playfmt, 0}; - -static u_int32_t es_recfmt[] = { - AFMT_U8, - AFMT_STEREO | AFMT_U8, - AFMT_S16_LE, - AFMT_STEREO | AFMT_S16_LE, - 0 -}; -static struct pcmchan_caps es_reccaps = {4000, 48000, es_recfmt, 0}; +static struct pcmchan_caps es_caps = {4000, 48000, es_fmt, 0}; static const struct { unsigned volidx:4; @@ -159,7 +222,7 @@ unsigned recmask:13; unsigned avail:1; } mixtable[SOUND_MIXER_NRDEVICES] = { - [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x0000, 1 }, + [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 }, @@ -171,22 +234,67 @@ [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; +static __inline u_int32_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, u_int32_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; u_int32_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; } @@ -194,7 +302,8 @@ static int es1370_mixset(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { - int l, r, rl, rr; + struct es_info *es; + int l, r, rl, rr, set_dac1; if (!mixtable[dev].avail) return -1; l = left; @@ -204,30 +313,53 @@ } else { rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; } + 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 < 10)? 0x80 : 15 - (r - 10) / 6; - es1370_wrcodec(mix_getdevinfo(m), mixtable[dev].right, rr); + 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(mix_getdevinfo(m), mixtable[dev].left, rl); + 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 int es1370_mixsetrecsrc(struct snd_mixer *m, u_int32_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; - es1370_wrcodec(mix_getdevinfo(m), CODEC_LIMIX1, j & 0x55); - es1370_wrcodec(mix_getdevinfo(m), CODEC_RIMIX1, j & 0xaa); - es1370_wrcodec(mix_getdevinfo(m), CODEC_LIMIX2, (j >> 8) & 0x17); - es1370_wrcodec(mix_getdevinfo(m), CODEC_RIMIX2, (j >> 8) & 0x0f); - es1370_wrcodec(mix_getdevinfo(m), CODEC_OMIX1, 0x7f); - es1370_wrcodec(mix_getdevinfo(m), CODEC_OMIX2, 0x3f); + 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; } @@ -244,18 +376,20 @@ static int es1370_wrcodec(struct es_info *es, u_char i, u_char data) { - int wait = 100; /* 100 msec timeout */ + u_int t; + + ES_LOCK_ASSERT(es); - do { - if ((bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS) & + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1370_REG_STATUS, 4) & STAT_CSTAT) == 0) { - bus_space_write_2(es->st, es->sh, ES1370_REG_CODEC, - ((u_short)i << CODEC_INDEX_SHIFT) | data); + es_wr(es, ES1370_REG_CODEC, + ((u_short)i << CODEC_INDEX_SHIFT) | data, 2); return 0; } - DELAY(1000); - } while (--wait); - printf("pcm: es1370_wrcodec timed out\n"); + DELAY(1); + } + device_printf(es->dev, "%s: timed out\n", __func__); return -1; } @@ -266,36 +400,82 @@ 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 = (dir == PCMDIR_PLAY)? &es->pch : &es->rch; + 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->blksz = ch->bufsz / 2; - ch->num = ch->parent->num++; + ch->dir = dir; + ES_UNLOCK(es); if (sndbuf_alloc(ch->buffer, es->parent_dmat, ch->bufsz) != 0) return NULL; - return ch; -} - -static int -eschan_setdir(kobj_t obj, void *data, int dir) -{ - struct es_chinfo *ch = data; - struct es_info *es = ch->parent; - + ES_LOCK(es); if (dir == PCMDIR_PLAY) { - bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer)); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); - } else { - bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer)); - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); + 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); + 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); + 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); + es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); } - ch->dir = dir; - return 0; + ES_UNLOCK(es); + return ch; } static int @@ -304,16 +484,24 @@ struct es_chinfo *ch = data; struct es_info *es = ch->parent; + ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) { - es->sctrl &= ~SCTRL_P2FMT; - if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; - if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + if (ch->index == ES_DAC1) { + es->sctrl &= ~SCTRL_P1FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P1SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_P1SMB; + } else { + es->sctrl &= ~SCTRL_P2FMT; + if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; + if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + } } else { es->sctrl &= ~SCTRL_R1FMT; if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; } - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); + ES_UNLOCK(es); ch->fmt = format; return 0; } @@ -324,11 +512,41 @@ struct es_chinfo *ch = data; struct es_info *es = ch->parent; - es->ctrl &= ~CTRL_PCLKDIV; - es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); - /* rec/play speeds locked together - should indicate in flags */ - return speed; /* XXX calc real speed */ + /* Fixed rate , do nothing. */ + if (ch->caps.minspeed == ch->caps.maxspeed) + return ch->caps.maxspeed; + if (speed < ch->caps.minspeed) + speed = ch->caps.minspeed; + if (speed > ch->caps.maxspeed) + speed = ch->caps.maxspeed; + ES_LOCK(es); + if (ch->index == ES_DAC1) { + /* + * DAC1 does not support continuous rate settings. + * Pick the nearest and use it since FEEDER_RATE will + * do the 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 int @@ -336,23 +554,41 @@ { struct es_chinfo *ch = data; struct es_info *es = ch->parent; + uint32_t i; + int delta; - if (ch->dir == PCMDIR_PLAY) { - return es1371_dac_rate(es, speed, 3 - ch->num); /* play */ - } else { - return es1371_adc_rate(es, speed, 1); /* record */ - } + 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_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { + struct es_info *es; struct es_chinfo *ch = data; + uint32_t oblksz, obufsz; + int error; + oblksz = ch->blksz; + obufsz = ch->bufsz; ch->blksz = blocksize; ch->bufsz = ch->blksz * 2; - sndbuf_resize(ch->buffer, 2, ch->blksz); - + error = sndbuf_resize(ch->buffer, 2, ch->blksz); + if (error != 0) { + ch->blksz = oblksz; + ch->bufsz = obufsz; + es = ch->parent; + device_printf(es->dev, "unable to set block size, blksz = %d, " + "error = %d", blocksize, error); + } return ch->blksz; } @@ -361,37 +597,52 @@ { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - unsigned cnt; + uint32_t cnt, b = 0; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; cnt = (ch->blksz / sndbuf_getbps(ch->buffer)) - 1; - + if (ch->fmt & AFMT_16BIT) + b |= 0x02; + if (ch->fmt & AFMT_STEREO) + b |= 0x01; + ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { - int b = (ch->fmt & AFMT_S16_LE)? 2 : 1; - es->ctrl |= CTRL_DAC2_EN; - es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN); - es->sctrl |= SCTRL_P2INTEN | (b << SCTRL_SH_P2ENDINC); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_SCOUNT, cnt); - /* start at beginning of buffer */ - bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); - } else es->ctrl &= ~CTRL_DAC2_EN; + if (ch->index == ES_DAC1) { + es->ctrl |= CTRL_DAC1_EN; + es->sctrl &= ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD); + es->sctrl |= SCTRL_P1INTEN | 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); + es->sctrl |= SCTRL_P2INTEN | (b << 2) | + (((b & 2) ? : 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; - es->sctrl |= SCTRL_R1INTEN; - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_SCOUNT, cnt); + es->sctrl |= SCTRL_R1INTEN | (b << 4); + es_wr(es, ES1370_REG_ADC_SCOUNT, cnt, 4); /* start at beginning of buffer */ - bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); + 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; } - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + ES_UNLOCK(es); return 0; } @@ -402,13 +653,17 @@ struct es_info *es = ch->parent; u_int32_t reg, cnt; - if (ch->dir == PCMDIR_PLAY) - reg = ES1370_REG_DAC2_FRAMECNT; - 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; - - bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, reg >> 8); - cnt = bus_space_read_4(es->st, es->sh, reg & 0x000000ff) >> 16; + ES_LOCK(es); + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; + ES_UNLOCK(es); /* cnt is longwords */ return cnt << 2; } @@ -417,12 +672,12 @@ eschan_getcaps(kobj_t obj, void *data) { struct es_chinfo *ch = data; - return (ch->dir == PCMDIR_PLAY)? &es_playcaps : &es_reccaps; + + return &ch->caps; } static kobj_method_t eschan1370_methods[] = { KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setdir, eschan_setdir), KOBJMETHOD(channel_setformat, eschan_setformat), KOBJMETHOD(channel_setspeed, eschan1370_setspeed), KOBJMETHOD(channel_setblocksize, eschan_setblocksize), @@ -435,7 +690,6 @@ static kobj_method_t eschan1371_methods[] = { KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setdir, eschan_setdir), KOBJMETHOD(channel_setformat, eschan_setformat), KOBJMETHOD(channel_setspeed, eschan1371_setspeed), KOBJMETHOD(channel_setblocksize, eschan_setblocksize), @@ -452,34 +706,78 @@ es_intr(void *p) { struct es_info *es = p; - unsigned intsrc, sctrl; + uint32_t intsrc, sctrl; - intsrc = bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS); - if ((intsrc & STAT_INTR) == 0) return; + ES_LOCK(es); + 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; - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, sctrl); - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - - if (intsrc & STAT_ADC) chn_intr(es->rch.channel); - if (intsrc & STAT_DAC1); - if (intsrc & STAT_DAC2) chn_intr(es->pch.channel); + 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) { - es->ctrl = CTRL_CDC_EN | CTRL_SERR_DIS | - (DAC2_SRTODIV(DSP_DEFAULT_SPEED) << CTRL_SH_PCLKDIV); - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + 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) ? 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; - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es1370_wrcodec(es, CODEC_RES_PD, 3);/* No RST, PD */ es1370_wrcodec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use @@ -487,45 +785,65 @@ * PLL; program DAC_SYNC=0! */ es1370_wrcodec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ es1370_wrcodec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ + ES_UNLOCK(es); return 0; } /* ES1371 specific */ int -es1371_init(struct es_info *es, device_t dev) +es1371_init(struct es_info *es) { + uint32_t cssr, devid, revid, subdev; int idx; - int devid = pci_get_devid(dev); - int revid = pci_get_revid(dev); - - if (debug > 0) printf("es_init\n"); + ES_LOCK(es); + /* This is NOT ES1370 */ + es->escfg = ES_SET_IS_ES1370(es->escfg, 0); es->num = 0; - es->ctrl = 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) || - (devid == CT4730_PCI_ID)) { - bus_space_write_4(es->st, es->sh, ES1370_REG_STATUS, 0x20000000); + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { + cssr = 1 << 29; + es_wr(es, ES1370_REG_STATUS, cssr, 4); DELAY(20000); - if (debug > 0) device_printf(dev, "ac97 2.1 enabled\n"); - } else { /* pre ac97 2.1 card */ - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); - if (debug > 0) device_printf(dev, "ac97 pre-2.1 enabled\n"); } - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - bus_space_write_4(es->st, es->sh, ES1371_REG_LEGACY, 0); /* AC'97 warm reset to start the bitclk */ - bus_space_write_4(es->st, es->sh, ES1371_REG_LEGACY, es->ctrl | ES1371_SYNC_RES); + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + es_wr(es, ES1371_REG_LEGACY, ES1371_SYNC_RES, 4); DELAY(2000); - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->ctrl); + es_wr(es, ES1370_REG_CONTROL, es->sctrl, 4); + es1371_wait_src_ready(es); /* Init the sample rate converter */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, ES1371_DIS_SRC); + 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); @@ -538,16 +856,21 @@ 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, 1); - es1371_dac_rate (es, 22050, 1); - es1371_dac_rate (es, 22050, 2); + 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) */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, 0); + 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); } @@ -557,43 +880,34 @@ static int es1371_wrcd(kobj_t obj, void *s, int addr, u_int32_t data) { - int sl; - unsigned t, x; + uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; - if (debug > 0) printf("wrcodec addr 0x%x data 0x%x\n", addr, data); - for (t = 0; t < 0x1000; t++) - if (!(bus_space_read_4(es->st, es->sh,(ES1371_REG_CODEC & CODEC_WIP)))) + if (!es_rd(es, ES1371_REG_CODEC & CODEC_WIP, 4)) break; - sl = spltty(); /* save the current state for later */ - x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE); + x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, - (es1371_wait_src_ready(s) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1))); + 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 ((bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE) & 0x00070000) == 0x00010000) + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; - if (debug > 2) - printf("one b_s_w: 0x%lx 0x%x 0x%x\n", - rman_get_start(es->reg), ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK)); - - bus_space_write_4(es->st, es->sh,ES1371_REG_CODEC, + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK)); + ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); - if (debug > 2) - printf("two b_s_w: 0x%lx 0x%x 0x%x\n", - rman_get_start(es->reg), ES1371_REG_SMPRATE, x); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, x); - splx(sl); + es_wr(es, ES1371_REG_SMPRATE, orig, 4); return 0; } @@ -601,44 +915,42 @@ static int es1371_rdcd(kobj_t obj, void *s, int addr) { - int sl; - unsigned t, x = 0; + uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; - if (debug > 0) printf("rdcodec addr 0x%x ... ", addr); - for (t = 0; t < 0x1000; t++) - if (!(x = bus_space_read_4(es->st, es->sh, ES1371_REG_CODEC) & CODEC_WIP)) + if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; - if (debug > 0) printf("loop 1 t 0x%x x 0x%x ", t, x); - - sl = spltty(); /* save the current state for later */ - x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE); + x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, - (es1371_wait_src_ready(s) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1))); + 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 < 0x5000; t++) - if ((x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE) & 0x00070000) == 0x00010000) + for (t = 0; t < 0x1000; t++) + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) break; - if (debug > 0) printf("loop 2 t 0x%x x 0x%x ", t, x); - bus_space_write_4(es->st, es->sh, ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD); + + es_wr(es, ES1371_REG_CODEC, + ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | + CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, x); - - splx(sl); + es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ for (t = 0; t < 0x1000; t++) - if ((x = bus_space_read_4(es->st, es->sh, ES1371_REG_CODEC)) & CODEC_RDY) + if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; - if (debug > 0) printf("loop 3 t 0x%x 0x%x ret 0x%x\n", t, x, ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT)); + return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } @@ -654,24 +966,24 @@ static u_int es1371_src_read(struct es_info *es, u_short reg) { - unsigned int r; + 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); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE,r); + 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, u_short reg, u_short data){ - u_int r; +es1371_src_write(struct es_info *es, u_short reg, u_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); - /* printf("es1371_src_write 0x%x 0x%x\n",ES1371_REG_SMPRATE,r | ES1371_SRC_RAM_WE); */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE); + es_wr(es, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE, 4); } static u_int @@ -679,6 +991,8 @@ { u_int n, truncm, freq, result; + ES_LOCK_ASSERT(es); + if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; n = rate / 3000; @@ -712,36 +1026,37 @@ { u_int freq, r, result, dac, dis; + ES_LOCK_ASSERT(es); + if (rate > 48000) rate = 48000; if (rate < 4000) rate = 4000; - freq = (rate << 15) / 3000; + freq = ((rate << 15) + 1500) / 3000; result = (freq * 3000) >> 15; - if (set) { - dac = (set == 1)? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; - dis = (set == 1)? ES1371_DIS_P2 : ES1371_DIS_P1; - - r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r); - 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)); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r); - } + + 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 u_int +static uint32_t es1371_wait_src_ready(struct es_info *es) { - u_int t, r; + uint32_t t, r; - for (t = 0; t < 500; t++) { - if (!((r = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE)) & ES1371_SRC_RAM_BUSY)) + for (t = 0; t < 0x1000; t++) { + if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & ES1371_SRC_RAM_BUSY)) return r; - DELAY(1000); + DELAY(1); } - printf("es1371: wait src ready timeout 0x%x [0x%x]\n", ES1371_REG_SMPRATE, r); + device_printf(es->dev, "%s: timed out 0x%x [0x%x]\n", __func__, + ES1371_REG_SMPRATE, r); return 0; } @@ -799,7 +1114,7 @@ case CT4730_PCI_ID: switch(pci_get_revid(dev)) { case CT4730REV_CT4730_A: - device_set_desc(dev, "Creative SB AudioPCI CT4730"); + device_set_desc(dev, "Creative SB AudioPCI CT4730/EV1938"); return 0; default: device_set_desc(dev, "Creative SB AudioPCI CT4730-?"); @@ -832,25 +1147,285 @@ } } +#ifdef SND_DYNSYSCTL +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, sizeof(new_en), 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, sizeof(val), 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, sizeof(val), 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; + struct cdev *i_dev; + device_t dev; + uint32_t val, set; + int recsrc, level, err; + + dev = oidp->oid_arg1; + d = device_get_softc(dev); + if (d == NULL || 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, sizeof(val), req); + + if (err || req->newptr == NULL) + return (err); + if (!(val == 0 || val == 1)) + return (EINVAL); + if (val == set) + return (0); + i_dev = d->mixer_dev; + if (mixer_ioctl(i_dev, 0, (caddr_t)&recsrc, 0, NULL) != EBADF) + return (EBUSY); + err = mixer_ioctl(i_dev, MIXER_READ(SOUND_MIXER_PCM), + (caddr_t)&level, -1, NULL); + if (!err) + err = mixer_ioctl(i_dev, MIXER_READ(SOUND_MIXER_RECSRC), + (caddr_t)&recsrc, -1, NULL); + if (err) + return (err); + if (level < 0) + return (EINVAL); + + ES_LOCK(es); + if (es->ctrl & (CTRL_ADC_EN | CTRL_DAC1_EN | CTRL_DAC2_EN)) { + ES_UNLOCK(es); + 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); + m = i_dev->si_drv1; + if (!val) { + mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) | + (1 << SOUND_MIXER_SYNTH)); + mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) | + (1 << SOUND_MIXER_SYNTH)); + err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), + (caddr_t)&level, -1, NULL); + } else { + err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), + (caddr_t)&level, -1, NULL); + mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) & + ~(1 << SOUND_MIXER_SYNTH)); + mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) & + ~(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 = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_RECSRC), + (caddr_t)&recsrc, -1, NULL); + } + return (err); +} +#endif /* SND_DYNSYSCTL */ + +static void +es_init_sysctls(device_t dev) +{ +#ifdef SND_DYNSYSCTL + 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)) { + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), + SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "spdif_enabled", + CTLTYPE_INT | CTLFLAG_RW, 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) { + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), + SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "fixed_rate", + CTLTYPE_INT | CTLFLAG_RW, 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) { + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), + SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "single_pcm_mixer", + CTLTYPE_INT | CTLFLAG_RW, 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); + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), + SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "latency_timer", + CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_latency_timer, "I", + "PCI Latency Timer configuration"); +#endif /* SND_DYNSYSCTL */ +} + static int es_pci_attach(device_t dev) { u_int32_t data; - struct es_info *es = 0; - int mapped; + struct es_info *es = NULL; + int mapped, i, numplay, dac_cfg; char status[SND_STATUSLEN]; - struct ac97_info *codec = 0; + struct ac97_info *codec = NULL; kobj_class_t ct = NULL; + uint32_t devid; if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } - + es->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); es->dev = dev; + es->escfg = 0; mapped = 0; + + pci_enable_busmaster(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); - data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); + data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { @@ -858,58 +1433,97 @@ es->regtype = SYS_RES_MEMORY; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, RF_ACTIVE); - if (es->reg) { - es->st = rman_get_bustag(es->reg); - es->sh = rman_get_bushandle(es->reg); + if (es->reg) mapped++; - } } if (mapped == 0 && (data & PCIM_CMD_PORTEN)) { 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) { - es->st = rman_get_bustag(es->reg); - es->sh = rman_get_bushandle(es->reg); + 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); es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); - if (pci_get_devid(dev) == ES1371_PCI_ID || - pci_get_devid(dev) == ES1371_PCI_ID2 || - pci_get_devid(dev) == CT5880_PCI_ID || - pci_get_devid(dev) == CT4730_PCI_ID) { - if(-1 == es1371_init(es, dev)) { - device_printf(dev, "unable to initialize the card\n"); - goto bad; - } + 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; + 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; + if (mixer_init(dev, ac97_getmixerclass(), codec)) + goto bad; ct = &eschan1371_class; - } else if (pci_get_devid(dev) == ES1370_PCI_ID) { - if (-1 == es1370_init(es)) { - device_printf(dev, "unable to initialize the card\n"); + 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; - } - if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; - } else goto bad; + 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, 0, es_intr, es, &es->ih)) { + 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; } @@ -919,8 +1533,8 @@ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/es->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &es->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &es->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -929,19 +1543,31 @@ (es->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(es->reg), rman_get_start(es->irq),PCM_KLDSTRING(snd_es137x)); - if (pcm_register(dev, es, 1, 1)) goto bad; + if (pcm_register(dev, es, numplay, 1)) + goto bad; + for (i = 0; i < numplay; i++) + pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_addchan(dev, PCMDIR_REC, ct, es); - pcm_addchan(dev, PCMDIR_PLAY, ct, es); + es_init_sysctls(dev); pcm_setstatus(dev, status); - + 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 (codec) ac97_destroy(codec); - if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); + 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 (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); + 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; } @@ -953,14 +1579,14 @@ struct es_info *es; r = pcm_unregister(dev); - if (r) - return r; + if (r) return r; es = pcm_getdevinfo(dev); - bus_release_resource(dev, es->regtype, es->regid, es->reg); 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; --- sys/dev/sound/pci/es137x.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/es137x.h Fri Sep 16 06:32:00 2005 @@ -167,6 +167,17 @@ #define ES1371_SRC_RAM_DATAI(i) (((i)>>0)&0xffff) /* current value of the sample rate converter */ /* + * S/PDIF specific + */ + +/* Use ES1370_REG_CONTROL */ +#define RECEN_B 0x08000000 /* Used to control mixing of analog with digital data */ +#define SPDIFEN_B 0x04000000 /* Reset to switch digital output mux to "THRU" mode */ +/* Use ES1370_REG_STATUS */ +#define ENABLE_SPDIF 0x00040000 /* Used to enable the S/PDIF circuitry */ +#define TEST_SPDIF 0x00020000 /* Used to put the S/PDIF module in "test mode" */ + +/* * Sample rate converter addresses */ --- sys/dev/sound/pci/ds1.c.orig Tue Jan 10 13:57:25 2006 +++ sys/dev/sound/pci/ds1.c Wed Jan 18 09:01:32 2006 @@ -743,13 +743,17 @@ for (i = 0; i < DS1_CHANS; i++) { if (sc->pch[i].run) { x = 1; + snd_mtxunlock(sc->lock); chn_intr(sc->pch[i].channel); + snd_mtxlock(sc->lock); } } for (i = 0; i < 2; i++) { if (sc->rch[i].run) { x = 1; + snd_mtxunlock(sc->lock); chn_intr(sc->rch[i].channel); + snd_mtxlock(sc->lock); } } i = ds_rd(sc, YDSXGR_MODE, 4); @@ -830,8 +834,8 @@ if (sc->regbase == NULL) { if (bus_dma_tag_create(NULL, 2, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, - NULL, NULL, memsz, 1, memsz, 0, busdma_lock_mutex, - &Giant, &sc->control_dmat)) + NULL, NULL, memsz, 1, memsz, 0, NULL, + NULL, &sc->control_dmat)) return -1; if (bus_dmamem_alloc(sc->control_dmat, &buf, BUS_DMA_NOWAIT, &sc->map)) return -1; @@ -971,8 +975,8 @@ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &sc->buffer_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &sc->buffer_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -986,12 +990,23 @@ codec = AC97_CREATE(dev, sc, ds_ac97); if (codec == NULL) goto bad; + /* + * Turn on inverted external amplifier sense flags for few + * 'special' boards. + */ + switch (subdev) { + case 0x81171033: /* NEC ValueStar (VT550/0) */ + ac97_setflags(codec, ac97_getflags(codec) | AC97_F_EAPD_INV); + break; + default: + break; + } mixer_init(dev, ac97_getmixerclass(), codec); 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, ds_intr, sc, &sc->ih)) { + if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ds_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } --- sys/dev/sound/pci/ich.c.orig Fri Nov 4 15:37:02 2005 +++ sys/dev/sound/pci/ich.c Sat Mar 4 14:42:42 2006 @@ -41,11 +41,84 @@ #define ICH_DEFAULT_BUFSZ 16384 #define ICH_MAX_BUFSZ 65536 -#define SIS7012ID 0x70121039 /* SiS 7012 needs special handling */ -#define ICH4ID 0x24c58086 /* ICH4 needs special handling too */ -#define ICH5ID 0x24d58086 /* ICH5 needs to be treated as ICH4 */ -#define I6300ESBID 0x25a68086 /* 6300ESB needs to be treated as ICH4 */ -#define ICH6ID 0x266e8086 /* ICH6 needs to be treated as ICH4 */ +#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 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) + +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" }, + { AMD_VENDORID, AMD_768, 0, + "AMD-768" }, + { AMD_VENDORID, AMD_8111, 0, + "AMD-8111" } +}; /* buffer descriptor */ struct ich_desc { @@ -93,6 +166,11 @@ bus_addr_t desc_addr; struct intr_config_hook intrhook; int use_intrhook; + uint16_t vendor; + uint16_t devid; + uint32_t flags; +#define IGNORE_PCR 0x01 + struct mtx *ich_lock; }; /* -------------------------------------------------------------------- */ @@ -106,7 +184,7 @@ /* -------------------------------------------------------------------- */ /* Hardware */ -static u_int32_t +static __inline u_int32_t ich_rd(struct sc_info *sc, int regno, int size) { switch (size) { @@ -121,7 +199,7 @@ } } -static void +static __inline void ich_wr(struct sc_info *sc, int regno, u_int32_t data, int size) { switch (size) { @@ -149,7 +227,10 @@ data = ich_rd(sc, ICH_REG_ACC_SEMA, 1); if ((data & 0x01) == 0) return 0; + DELAY(1); } + if ((sc->flags & IGNORE_PCR) != 0) + return (0); device_printf(sc->dev, "CODEC semaphore timeout\n"); return ETIMEDOUT; } @@ -190,15 +271,15 @@ static void ich_filldtbl(struct sc_chinfo *ch) { + struct sc_info *sc = ch->parent; u_int32_t base; int i; base = sndbuf_getbufaddr(ch->buffer); - ch->blkcnt = sndbuf_getsize(ch->buffer) / ch->blksz; - if (ch->blkcnt != 2 && ch->blkcnt != 4 && ch->blkcnt != 8 && ch->blkcnt != 16 && ch->blkcnt != 32) { - ch->blkcnt = 2; - ch->blksz = sndbuf_getsize(ch->buffer) / ch->blkcnt; - } + if (ch->blksz > sc->bufsz / ch->blkcnt) + ch->blksz = sc->bufsz / ch->blkcnt; + sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz); + ch->blksz = sndbuf_getblksz(ch->buffer); for (i = 0; i < ICH_DTBL_LENGTH; i++) { ch->dtbl[i].buffer = base + (ch->blksz * (i % ch->blkcnt)); @@ -222,7 +303,12 @@ 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); @@ -244,6 +330,7 @@ struct sc_chinfo *ch; unsigned int num; + ICH_LOCK(sc); num = sc->chnum++; ch = &sc->ch[num]; ch->num = num; @@ -283,10 +370,13 @@ return NULL; } + ICH_UNLOCK(sc); if (sndbuf_alloc(ch->buffer, sc->dmat, sc->bufsz) != 0) return NULL; + ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_t)(ch->desc_addr), 4); + ICH_UNLOCK(sc); return ch; } @@ -304,16 +394,20 @@ struct sc_info *sc = ch->parent; if (ch->spdreg) { - int r; + int r, ac97rate; + + ICH_LOCK(sc); if (sc->ac97rate <= 32000 || sc->ac97rate >= 64000) sc->ac97rate = 48000; - r = (speed * 48000) / sc->ac97rate; + ac97rate = sc->ac97rate; + ICH_UNLOCK(sc); + r = (speed * 48000) / ac97rate; /* * Cast the return value of ac97_setrate() to u_int so that * the math don't overflow into the negative range. */ ch->spd = ((u_int)ac97_setrate(sc->codec, ch->spdreg, r) * - sc->ac97rate) / 48000; + ac97rate) / 48000; } else { ch->spd = 48000; } @@ -328,7 +422,9 @@ 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; } @@ -342,12 +438,16 @@ switch (go) { case PCMTRIG_START: ch->run = 1; + ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_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_ABORT: + ICH_LOCK(sc); ich_resetchan(sc, ch->num); + ICH_UNLOCK(sc); ch->run = 0; break; } @@ -361,7 +461,9 @@ struct sc_info *sc = ch->parent; u_int32_t pos; + 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; @@ -399,6 +501,7 @@ u_int32_t cbi, lbi, lvi, st, gs; int i; + ICH_LOCK(sc); 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 */ @@ -417,8 +520,11 @@ 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) + 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) @@ -439,6 +545,7 @@ (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); @@ -573,7 +680,6 @@ ich_init(struct sc_info *sc) { u_int32_t stat; - int sz; ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); DELAY(600000); @@ -581,134 +687,75 @@ if ((stat & ICH_GLOB_STA_PCR) == 0) { /* ICH4/ICH5 may fail when busmastering is enabled. Continue */ - if ((pci_get_devid(sc->dev) != ICH4ID) && - (pci_get_devid(sc->dev) != ICH5ID) && - (pci_get_devid(sc->dev) != I6300ESBID) && - (pci_get_devid(sc->dev) != ICH6ID)) { - return ENXIO; + 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 |= 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; - if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT, &sc->dtmap)) - return ENOSPC; - - sz = sizeof(struct ich_desc) * ICH_DTBL_LENGTH * 3; - if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sz, ich_setmap, sc, 0)) { - bus_dmamem_free(sc->dmat, (void **)&sc->dtbl, sc->dtmap); - return ENOSPC; - } - return 0; } static int ich_pci_probe(device_t dev) { - switch(pci_get_devid(dev)) { - case 0x71958086: - device_set_desc(dev, "Intel 443MX"); - return 0; - - case 0x24158086: - device_set_desc(dev, "Intel ICH (82801AA)"); - return 0; - - case 0x24258086: - device_set_desc(dev, "Intel ICH (82801AB)"); - return 0; - - case 0x24458086: - device_set_desc(dev, "Intel ICH2 (82801BA)"); - return 0; - - case 0x24858086: - device_set_desc(dev, "Intel ICH3 (82801CA)"); - return 0; - - case ICH4ID: - device_set_desc(dev, "Intel ICH4 (82801DB)"); - return -1000; /* allow a better driver to override us */ - - case ICH5ID: - device_set_desc(dev, "Intel ICH5 (82801EB)"); - return -1000; /* allow a better driver to override us */ - - case I6300ESBID: - device_set_desc(dev, "Intel 6300ESB"); - return -1000; /* allow a better driver to override us */ - - case ICH6ID: - device_set_desc(dev, "Intel ICH6 (82801FB)"); - return -1000; /* allow a better driver to override us */ - - case SIS7012ID: - device_set_desc(dev, "SiS 7012"); - return 0; - - case 0x01b110de: - device_set_desc(dev, "nVidia nForce"); - return 0; - - case 0x006a10de: - device_set_desc(dev, "nVidia nForce2"); - return 0; - - case 0x008a10de: - device_set_desc(dev, "nVidia nForce2 400"); - return 0; - - case 0x00da10de: - device_set_desc(dev, "nVidia nForce3"); - return 0; - - case 0x00ea10de: - device_set_desc(dev, "nVidia nForce3 250"); - return 0; - - case 0x005910de: - device_set_desc(dev, "nVidia nForce4"); - return 0; - - case 0x74451022: - device_set_desc(dev, "AMD-768"); - return 0; - - case 0x746d1022: - device_set_desc(dev, "AMD-8111"); - return 0; + int i; + uint16_t devid, vendor; - default: - return ENXIO; + vendor = pci_get_vendor(dev); + devid = pci_get_device(dev); + for (i = 0; i < sizeof(ich_devs)/sizeof(ich_devs[0]); 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 (-1000); + return (0); + } } + return (ENXIO); } static int ich_pci_attach(device_t dev) { + uint32_t subdev; u_int16_t extcaps; + uint16_t devid, vendor; struct sc_info *sc; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { + if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } - bzero(sc, sizeof(*sc)); + sc->ich_lock = snd_mtxcreate(device_get_nameunit(dev), "sound 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 (pci_get_devid(dev) == SIS7012ID) { + if (vendor == SIS_VENDORID && devid == SIS_7012) { sc->swap_reg = 1; sc->sample_size = 1; } else { @@ -717,28 +764,25 @@ } /* + * 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 (pci_get_devid(dev) == ICH4ID) { - pci_write_config(dev, PCIR_ICH_LEGACY, ICH_LEGACY_ENABLE, 1); - } - - /* - * Enable bus master. On ich4/5 this may prevent the detection of - * the primary codec becoming ready in ich_init(). - */ - pci_enable_busmaster(dev); - - if (pci_get_devid(dev) == ICH5ID || - pci_get_devid(dev) == I6300ESBID || - pci_get_devid(dev) == ICH6ID) { + 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; @@ -763,7 +807,7 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); if (bus_dma_tag_create(NULL, 8, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->bufsz, 1, 0x3ffff, 0, - busdma_lock_mutex, &Giant, &sc->dmat) != 0) { + NULL, NULL, &sc->dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -771,7 +815,7 @@ 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, ich_intr, sc, &sc->ih)) { + 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; } @@ -781,9 +825,36 @@ goto bad; } + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, + BUS_DMA_NOWAIT, &sc->dtmap)) + goto bad; + + if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, + sizeof(struct ich_desc) * ICH_DTBL_LENGTH * 3, + ich_setmap, sc, 0)) + 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 0x204c161f: /* Kvazar-Micro Senator 3592XT */ + case 0x8144104d: /* Sony VAIO PCG-TR* */ + case 0x81c0104d: /* Sony VAIO type T */ + case 0x8197104d: /* Sony S1XP */ + 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 */ @@ -832,6 +903,12 @@ 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->dmat) + bus_dma_tag_destroy(sc->dmat); + if (sc->ich_lock) + snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return ENXIO; } @@ -851,7 +928,9 @@ 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_dma_tag_destroy(sc->dmat); + snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); return 0; } @@ -885,12 +964,16 @@ 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; } @@ -908,13 +991,16 @@ pci_enable_io(dev, SYS_RES_MEMORY); pci_enable_busmaster(dev); + ICH_LOCK(sc); /* Reinit audio device */ if (ich_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); + ICH_UNLOCK(sc); return ENXIO; } /* 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"); --- sys/dev/sound/pci/fm801.c.orig Mon Jan 9 14:39:08 2006 +++ sys/dev/sound/pci/fm801.c Mon Jan 9 14:39:23 2006 @@ -114,7 +114,7 @@ }; static struct pcmchan_caps fm801ch_caps = { - 4000, 48000, + 5500, 48000, fmts, 0 }; --- sys/dev/sound/pci/solo.c.orig Thu Jan 5 01:14:16 2006 +++ sys/dev/sound/pci/solo.c Fri Jan 27 11:57:24 2006 @@ -39,11 +39,14 @@ #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ -#undef ESS18XX_DUPLEX +#define ESS18XX_DUPLEX 1 /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED +/* 1 = INTR_MPSAFE, 0 = GIANT */ +#define ESS18XX_MPSAFE 1 + static u_int32_t ess_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, @@ -55,7 +58,7 @@ AFMT_STEREO | AFMT_U16_LE, 0 }; -static struct pcmchan_caps ess_playcaps = {5000, 49000, ess_playfmt, 0}; +static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_playfmt, 0}; /* * Recording output is byte-swapped @@ -71,7 +74,7 @@ AFMT_STEREO | AFMT_U16_BE, 0 }; -static struct pcmchan_caps ess_reccaps = {5000, 49000, ess_recfmt, 0}; +static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_recfmt, 0}; struct ess_info; @@ -93,8 +96,21 @@ unsigned int bufsz; struct ess_chinfo pch, rch; +#if ESS18XX_MPSAFE == 1 + struct mtx *lock; +#endif }; +#if ESS18XX_MPSAFE == 1 +#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) +#else +#define ess_lock(_ess) +#define ess_unlock(_ess) +#define ess_lock_assert(_ess) +#endif + 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); @@ -219,29 +235,22 @@ static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { - u_long flags; - DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) - flags = spltty(); 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); - splx(flags); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; - u_long flags; - flags = spltty(); ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); - splx(flags); return val; } @@ -296,6 +305,7 @@ 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; @@ -328,7 +338,9 @@ else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } + ess_unlock(sc); chn_intr(sc->pch.channel); + ess_lock(sc); } if (rirq) { @@ -338,13 +350,17 @@ /* 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 */ @@ -570,6 +586,7 @@ if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; + ess_lock(sc); switch (go) { case PCMTRIG_START: ess_dmasetup(sc, ch->hwch, sndbuf_getbufaddr(ch->buffer), sndbuf_getsize(ch->buffer), ch->dir); @@ -583,6 +600,7 @@ ess_stop(ch); break; } + ess_unlock(sc); return 0; } @@ -591,8 +609,12 @@ { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; + int ret; - return ess_dmapos(sc, ch->hwch); + ess_lock(sc); + ret = ess_dmapos(sc, ch->hwch); + ess_unlock(sc); + return ret; } static struct pcmchan_caps * @@ -760,10 +782,8 @@ ess_dmapos(struct ess_info *sc, int ch) { int p = 0, i = 0, j = 0; - u_long flags; KASSERT(ch == 1 || ch == 2, ("bad ch")); - flags = spltty(); if (ch == 1) { /* @@ -787,7 +807,6 @@ } else if (ch == 2) p = port_rd(sc->io, 0x4, 2); - splx(flags); return sc->dmasz[ch - 1] - p; } @@ -841,6 +860,13 @@ sc->parent_dmat = 0; } +#if ESS18XX_MPSAFE == 1 + if (sc->lock) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } +#endif + free(sc, M_DEVBUF); } @@ -868,7 +894,14 @@ sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); +#if ESS18XX_MPSAFE == 1 + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + + return (sc->irq && sc->io && sc->sb && sc->vc && + sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; +#else return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp)? 0 : ENXIO; +#endif } static int @@ -911,6 +944,7 @@ uint32_t data; struct ess_info *sc = pcm_getdevinfo(dev); + ess_lock(sc); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -921,14 +955,19 @@ pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); - if (ess_reset_dsp(sc)) + 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: @@ -962,11 +1001,6 @@ pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); - if (ess_reset_dsp(sc)) - goto no; - if (mixer_init(dev, &solomixer_class, sc)) - goto no; - port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ #ifdef ESS18XX_DUPLEX sc->duplex = 1; @@ -979,24 +1013,48 @@ #else sc->newspeed = 0; #endif - if (sc->newspeed) - ess_setmixer(sc, 0x71, 0x2a); + if (snd_setup_intr(dev, sc->irq, +#if ESS18XX_MPSAFE == 1 + INTR_MPSAFE +#else + 0 +#endif + , ess_intr, sc, &sc->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto no; + } - snd_setup_intr(dev, sc->irq, 0, ess_intr, sc, &sc->ih); if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); +#if 0 if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/65536, /*boundary*/0, +#endif + if (bus_dma_tag_create(/*parent*/NULL, /*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*/busdma_lock_mutex, - /*lockarg*/&Giant, &sc->parent_dmat) != 0) { + /*flags*/0, +#if ESS18XX_MPSAFE == 1 + /*lockfunc*/NULL, /*lockarg*/NULL, +#else + /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, +#endif + &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, "at io 0x%lx,0x%lx,0x%lx irq %ld %s", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), --- sys/dev/sound/pci/via8233.c.orig Wed Nov 9 11:44:28 2005 +++ sys/dev/sound/pci/via8233.c Wed Nov 9 11:48:29 2005 @@ -54,6 +54,7 @@ #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 */ @@ -100,12 +101,14 @@ struct ac97_info *codec; unsigned int bufsz; + int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; u_int16_t codec_caps; u_int16_t n_dxs_registered; + struct mtx *lock; }; static u_int32_t via_fmt[] = { @@ -119,7 +122,86 @@ static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; -static u_int32_t +#ifdef SND_DYNSYSCTL +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, sizeof(new_en), 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, sizeof(val), 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; +} +#endif /* SND_DYNSYSCTL */ + +static void +via_init_sysctls(device_t dev) +{ +#ifdef SND_DYNSYSCTL + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), + SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "spdif_enabled", + CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_spdif_enable, "I", + "Enable S/PDIF output on primary playback channel"); + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), + SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "via_dxs_src", + CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_dxs_src, "I", + "Enable VIA DXS Sample Rate Converter"); +#endif +} + +static __inline u_int32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { @@ -134,7 +216,7 @@ } } -static void +static __inline void via_wr(struct via_info *via, int regno, u_int32_t data, int size) { @@ -253,14 +335,16 @@ { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - + u_int32_t f = WR_FORMAT_STOP_INDEX; if (format & AFMT_STEREO) 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; } @@ -270,9 +354,11 @@ { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + u_int32_t r, v; - u_int32_t r = ch->rbase + VIA8233_RP_DXS_RATEFMT; - u_int32_t v = via_rd(via, r, 4); + 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 (format & AFMT_STEREO) @@ -280,6 +366,7 @@ if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); + snd_mtxunlock(via->lock); return 0; } @@ -301,8 +388,10 @@ 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; } @@ -316,11 +405,10 @@ struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t spd = 48000; - if (via->codec_caps & AC97_EXTCAP_VRA) { - spd = ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed); - } - return spd; + if (via->codec_caps & AC97_EXTCAP_VRA) + return ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed); + + return 48000; } static int @@ -328,14 +416,17 @@ { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + u_int32_t r, v; - u_int32_t r = ch->rbase + VIA8233_RP_DXS_RATEFMT; - u_int32_t v = via_rd(via, r, 4) & ~VIA8233_DXS_RATEFMT_48K; + 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; } @@ -362,7 +453,7 @@ struct via_info *via = ch->parent; /* Controlled by ac97 registers */ - if (via->codec_caps & AC97_EXTCAP_VRA) + if (via->codec_caps & AC97_EXTCAP_VRA) return &via_vracaps; return &via_caps; } @@ -370,7 +461,17 @@ static struct pcmchan_caps * via8233dxs_getcaps(kobj_t obj, void *data) { - /* Controlled by onboard registers */ + 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; } @@ -381,7 +482,7 @@ struct via_info *via = ch->parent; /* Controlled by ac97 registers */ - if (via->codec_caps & AC97_EXTCAP_VRA) + if (via->codec_caps & AC97_EXTCAP_VRA) return &via_vracaps; return &via_caps; } @@ -393,6 +494,7 @@ via8233chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct via_chinfo *ch = data; + sndbuf_resize(ch->buffer, SEGS_PER_CHAN, blocksize); ch->blksz = sndbuf_getblksz(ch->buffer); return ch->blksz; @@ -403,11 +505,15 @@ { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + u_int32_t v, index, count; + int ptr; - u_int32_t v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); - u_int32_t index = v >> 24; /* Last completed buffer */ - u_int32_t count = v & 0x00ffffff; /* Bytes remaining */ - int ptr = (index + 1) * ch->blksz - count; + snd_mtxlock(via->lock); + 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 %= SEGS_PER_CHAN * ch->blksz; /* Wrap to available space */ return ptr; @@ -445,12 +551,17 @@ ch->dir = dir; ch->rbase = VIA_WR_BASE(c->num); + snd_mtxlock(via->lock); 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, via->bufsz) != 0) return NULL; + + snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, c->num); via8233chan_reset(via, ch); + snd_mtxunlock(via->lock); return ch; } @@ -472,13 +583,18 @@ * channels. We therefore want to align first DXS channel to * DXS3. */ + snd_mtxlock(via->lock); 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, via->bufsz) != 0) return NULL; + + snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + c->num); via8233chan_reset(via, ch); + snd_mtxunlock(via->lock); return ch; } @@ -498,8 +614,11 @@ if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) return NULL; + + snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + c->num); via8233chan_reset(via, ch); + snd_mtxunlock(via->lock); return ch; } @@ -526,6 +645,7 @@ struct via_chinfo *ch = data; struct via_info *via = ch->parent; + snd_mtxlock(via->lock); switch(go) { case PCMTRIG_START: via_buildsgdt(ch); @@ -542,6 +662,7 @@ via8233chan_reset(via, ch); break; } + snd_mtxunlock(via->lock); return 0; } @@ -587,29 +708,55 @@ via_intr(void *p) { struct via_info *via = p; - int i, stat; + int i, reg, stat; /* Poll playback channels */ + snd_mtxlock(via->lock); for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { - if (via->pch[i].rbase == 0) + if (via->pch[i].channel == NULL) continue; - stat = via->pch[i].rbase + VIA_RP_STATUS; - if (via_rd(via, stat, 1) & SGD_STATUS_INTR) { - via_wr(via, stat, SGD_STATUS_INTR, 1); + 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); + snd_mtxunlock(via->lock); chn_intr(via->pch[i].channel); + snd_mtxlock(via->lock); } } - + /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { - if (via->rch[i].rbase == 0) + if (via->rch[i].channel == NULL) continue; - stat = via->rch[i].rbase + VIA_RP_STATUS; - if (via_rd(via, stat, 1) & SGD_STATUS_INTR) { - via_wr(via, stat, SGD_STATUS_INTR, 1); + 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); + snd_mtxunlock(via->lock); chn_intr(via->rch[i].channel); + snd_mtxlock(via->lock); } } + snd_mtxunlock(via->lock); } /* @@ -639,6 +786,9 @@ case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); return 0; + case VIA8233_REV_ID_8251: + device_set_desc(dev, "VIA VT8251"); + return 0; default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ return 0; @@ -710,65 +860,23 @@ return ENXIO; } -#ifdef SND_DYNSYSCTL -static int via8233_spdif_en; - -static int -sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) -{ - device_t dev; - int err, new_en, r; - - new_en = via8233_spdif_en; - err = sysctl_handle_int(oidp, &new_en, sizeof(new_en), req); - if (err || req->newptr == NULL) - return err; - - if (new_en < 0 || new_en > 1) - return EINVAL; - via8233_spdif_en = new_en; - - dev = oidp->oid_arg1; - r = pci_read_config(dev, VIA_PCI_SPDIF, 1) & ~VIA_SPDIF_EN; - if (new_en) - r |= VIA_SPDIF_EN; - pci_write_config(dev, VIA_PCI_SPDIF, r, 1); - return 0; -} -#endif /* SND_DYNSYSCTL */ - -static void -via_init_sysctls(device_t dev) -{ -#ifdef SND_DYNSYSCTL - int r; - - r = pci_read_config(dev, VIA_PCI_SPDIF, 1); - via8233_spdif_en = (r & VIA_SPDIF_EN) ? 1 : 0; - - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "spdif_enabled", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_via8233_spdif_enable, "I", - "Enable S/PDIF output on primary playback channel"); -#endif -} - static int via_attach(device_t dev) { struct via_info *via = 0; char status[SND_STATUSLEN]; + int i, via_dxs_disabled, via_dxs_src, via_dxs_chnum, via_sgd_chnum; + uint32_t revid; if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } + via->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); 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); @@ -785,7 +893,7 @@ 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, 0, via_intr, via, &via->ih)) { + snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } @@ -796,8 +904,8 @@ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -813,8 +921,8 @@ /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/NSEGS * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->sgd_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -850,28 +958,82 @@ snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", rman_get_start(via->reg), rman_get_start(via->irq),PCM_KLDSTRING(snd_via8233)); - /* Register */ - if (pci_get_revid(dev) == VIA8233_REV_ID_8233A) { - if (pcm_register(dev, via, NMSGDCHANS, 1)) goto bad; + 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). - pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); */ - pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); - pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); + 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 { - int i; - if (pcm_register(dev, via, NMSGDCHANS + NDXSCHANS, NWRCHANS)) goto bad; - for (i = 0; i < NDXSCHANS; i++) - pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); - pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); - for (i = 0; i < NWRCHANS; i++) - pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); + 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; + /* Register */ + if (pcm_register(dev, via, via_dxs_chnum + via_sgd_chnum, NWRCHANS)) + goto bad; + 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); pcm_setstatus(dev, status); @@ -884,6 +1046,7 @@ if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, 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; } @@ -904,6 +1067,7 @@ bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); + snd_mtxfree(via->lock); free(via, M_DEVBUF); return 0; } --- sys/dev/sound/pci/via82c686.c.orig Mon Aug 1 06:15:52 2005 +++ sys/dev/sound/pci/via82c686.c Sat Oct 1 04:48:46 2005 @@ -86,6 +86,7 @@ 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[] = { @@ -98,7 +99,7 @@ static struct pcmchan_caps via_vracaps = {4000, 48000, via_fmt, 0}; static struct pcmchan_caps via_caps = {48000, 48000, via_fmt, 0}; -static u_int32_t +static __inline u_int32_t via_rd(struct via_info *via, int regno, int size) { @@ -115,7 +116,7 @@ } -static void +static __inline void via_wr(struct via_info *via, int regno, u_int32_t data, int size) { @@ -244,6 +245,7 @@ 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; @@ -266,9 +268,11 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; + snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) return NULL; + return ch; } @@ -286,10 +290,12 @@ 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; } @@ -342,12 +348,14 @@ ado = ch->sgd_table; DEB(printf("ado located at va=%p pa=%x\n", ado, 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; @@ -363,11 +371,13 @@ int ptr, base, base1, len, seg; ado = ch->sgd_table; + 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)); @@ -417,22 +427,25 @@ via_intr(void *p) { struct via_info *via = p; - int st; /* DEB(printf("viachan_intr\n")); */ /* Read channel */ - st = via_rd(via, VIA_PLAY_STAT, 1); - if (st & VIA_RPSTAT_INTR) { + 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 */ - st = via_rd(via, VIA_RECORD_STAT, 1); - if (st & VIA_RPSTAT_INTR) { + 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); } /* @@ -442,8 +455,8 @@ via_probe(device_t dev) { if (pci_get_devid(dev) == VIA_PCI_ID) { - device_set_desc(dev, "VIA VT82C686A"); - return 0; + device_set_desc(dev, "VIA VT82C686A"); + return 0; } return ENXIO; } @@ -468,6 +481,7 @@ device_printf(dev, "cannot allocate softc\n"); return ENXIO; } + via->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); /* Get resources */ data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -521,7 +535,7 @@ 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, 0, via_intr, via, &via->ih)) { + 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; } @@ -546,8 +560,8 @@ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -563,8 +577,8 @@ /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/NSEGS * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->sgd_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -594,6 +608,7 @@ if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, 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; } @@ -615,6 +630,7 @@ bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); + snd_mtxfree(via->lock); free(via, M_DEVBUF); return 0; } --- sys/dev/sound/pcm/ac97.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/ac97.c Sun Mar 19 22:56:06 2006 @@ -84,10 +84,8 @@ [SOUND_MIXER_LINE] = { AC97_MIX_LINE, 5, 0, 1, 1, 5, 0, 1 }, [SOUND_MIXER_PHONEIN] = { AC97_MIX_PHONE, 5, 0, 0, 1, 8, 0, 0 }, [SOUND_MIXER_MIC] = { AC97_MIX_MIC, 5, 0, 0, 1, 1, 1, 1 }, -#if 0 /* use igain for the mic 20dB boost */ [SOUND_MIXER_IGAIN] = { -AC97_MIX_MIC, 1, 6, 0, 0, 0, 1, 1 }, -#endif [SOUND_MIXER_CD] = { AC97_MIX_CD, 5, 0, 1, 1, 2, 0, 1 }, [SOUND_MIXER_LINE1] = { AC97_MIX_AUX, 5, 0, 1, 1, 4, 0, 0 }, [SOUND_MIXER_VIDEO] = { AC97_MIX_VIDEO, 5, 0, 1, 1, 3, 0, 0 }, @@ -118,6 +116,11 @@ { 0x57454300, "Winbond" }, { 0x574d4c00, "Wolfson" }, { 0x594d4800, "Yamaha" }, + /* + * XXX This is a fluke, really! The real vendor + * should be SigmaTel, not this! This should be + * removed someday! + */ { 0x01408300, "Creative" }, { 0x00000000, NULL } }; @@ -135,6 +138,7 @@ { 0x41445372, 0x00, 0, "AD1981A", 0 }, { 0x41445374, 0x00, 0, "AD1981B", 0 }, { 0x41445375, 0x00, 0, "AD1985", ad198x_patch }, + { 0x41445378, 0x00, 0, "AD1986", ad198x_patch }, { 0x414b4d00, 0x00, 1, "AK4540", 0 }, { 0x414b4d01, 0x00, 1, "AK4542", 0 }, { 0x414b4d02, 0x00, 1, "AK4543", 0 }, @@ -145,7 +149,9 @@ { 0x414c4710, 0x0f, 0, "ALC200", 0 }, { 0x414c4740, 0x0f, 0, "ALC202", 0 }, { 0x414c4720, 0x0f, 0, "ALC650", 0 }, + { 0x414c4752, 0x0f, 0, "ALC250", 0 }, { 0x414c4760, 0x0f, 0, "ALC655", 0 }, + { 0x414c4770, 0x0f, 0, "ALC203", 0 }, { 0x414c4780, 0x0f, 0, "ALC658", 0 }, { 0x414c4790, 0x0f, 0, "ALC850", 0 }, { 0x43525900, 0x07, 0, "CS4297", 0 }, @@ -156,10 +162,14 @@ { 0x43525940, 0x07, 0, "CS4201", 0 }, { 0x43525958, 0x07, 0, "CS4205", 0 }, { 0x43525960, 0x07, 0, "CS4291A", 0 }, - { 0x434d4961, 0x00, 0, "CMI9739", 0 }, + { 0x434d4961, 0x00, 0, "CMI9739", cmi9739_patch }, { 0x434d4941, 0x00, 0, "CMI9738", 0 }, + { 0x434d4978, 0x00, 0, "CMI9761", 0 }, + { 0x434d4982, 0x00, 0, "CMI9761", 0 }, + { 0x434d4983, 0x00, 0, "CMI9761", 0 }, { 0x43585421, 0x00, 0, "HSD11246", 0 }, { 0x43585428, 0x07, 0, "CX20468", 0 }, + { 0x43585430, 0x00, 0, "CX20468-21", 0 }, { 0x44543000, 0x00, 0, "DT0398", 0 }, { 0x454d4323, 0x00, 0, "EM28023", 0 }, { 0x454d4328, 0x00, 0, "EM28028", 0 }, @@ -192,6 +202,7 @@ { 0x83847658, 0x00, 0, "STAC9758/59", 0 }, { 0x83847660, 0x00, 0, "STAC9760/61", 0 }, /* Extrapolated */ { 0x83847662, 0x00, 0, "STAC9762/63", 0 }, /* Extrapolated */ + { 0x83847666, 0x00, 0, "STAC9766/67", 0 }, { 0x53494c22, 0x00, 0, "Si3036", 0 }, { 0x53494c23, 0x00, 0, "Si3038", 0 }, { 0x54524103, 0x00, 0, "TR28023", 0 }, /* Extrapolated */ @@ -201,6 +212,7 @@ { 0x54524e03, 0x07, 0, "TLV320AIC27", 0 }, { 0x54584e20, 0x00, 0, "TLC320AD90", 0 }, { 0x56494161, 0x00, 0, "VIA1612A", 0 }, + { 0x56494170, 0x00, 0, "VIA1617A", 0 }, { 0x574d4c00, 0x00, 0, "WM9701A", 0 }, { 0x574d4c03, 0x00, 0, "WM9703/4/7/8", 0 }, { 0x574d4c04, 0x00, 0, "WM9704Q", 0 }, @@ -211,6 +223,11 @@ { 0x594d4800, 0x00, 0, "YMF743", 0 }, { 0x594d4802, 0x00, 0, "YMF752", 0 }, { 0x594d4803, 0x00, 0, "YMF753", 0 }, + /* + * XXX This is a fluke, really! The real codec + * should be STAC9704, not this! This should be + * removed someday! + */ { 0x01408384, 0x00, 0, "EV1938", 0 }, { 0, 0, 0, NULL, 0 } }; @@ -283,6 +300,21 @@ u_int16_t ac97_rdcd(struct ac97_info *codec, int reg) { + if (codec->flags & AC97_F_RDCD_BUG) { + u_int16_t i[2], j = 100; + + i[0] = AC97_READ(codec->methods, codec->devinfo, reg); + i[1] = AC97_READ(codec->methods, codec->devinfo, reg); + while (i[0] != i[1] && j) + i[j-- & 1] = AC97_READ(codec->methods, codec->devinfo, reg); +#if 0 + if (j < 100) { + device_printf(codec->dev, "%s(): Inconsistent register value at" + " 0x%08x (retry: %d)\n", __func__, reg, 100 - j); + } +#endif + return i[!(j & 1)]; + } return AC97_READ(codec->methods, codec->devinfo, reg); } @@ -452,14 +484,16 @@ */ snd_mtxlock(codec->lock); if (e->mask) { - int cur = ac97_rdcd(codec, e->reg); + int cur = ac97_rdcd(codec, reg); val |= cur & ~(mask); } ac97_wrcd(codec, reg, val); snd_mtxunlock(codec->lock); return left | (right << 8); } else { - /* printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); */ +#if 0 + printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); +#endif return -1; } } @@ -511,6 +545,40 @@ } } +static void +ac97_fix_volume(struct ac97_info *codec) +{ + struct snddev_info *d = device_get_softc(codec->dev); + +#if 0 + /* XXX For the sake of debugging purposes */ + ac97_wrcd(codec, AC97_MIX_PCM, 0); + bzero(&codec->mix[SOUND_MIXER_PCM], + sizeof(codec->mix[SOUND_MIXER_PCM])); + codec->flags |= AC97_F_SOFTVOL; + if (d) + d->flags |= SD_F_SOFTVOL; + return; +#endif + switch (codec->id) { + case 0x434d4941: /* CMI9738 */ + case 0x434d4961: /* CMI9739 */ + case 0x434d4978: /* CMI9761 */ + case 0x434d4982: /* CMI9761 */ + case 0x434d4983: /* CMI9761 */ + ac97_wrcd(codec, AC97_MIX_PCM, 0); + break; + default: + return; + break; + } + bzero(&codec->mix[SOUND_MIXER_PCM], + sizeof(codec->mix[SOUND_MIXER_PCM])); + codec->flags |= AC97_F_SOFTVOL; + if (d) + d->flags |= SD_F_SOFTVOL; +} + static const char* ac97_hw_desc(u_int32_t id, const char* vname, const char* cname, char* buf) { @@ -536,8 +604,9 @@ const char *cname, *vname; char desc[80]; u_int8_t model, step; - unsigned i, j, k, old; + unsigned i, j, k, bit, old; u_int32_t id; + int reg; snd_mtxlock(codec->lock); codec->count = AC97_INIT(codec->methods, codec->devinfo); @@ -552,6 +621,16 @@ ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); i = ac97_rdcd(codec, AC97_REG_RESET); + j = ac97_rdcd(codec, AC97_REG_RESET); + /* + * Let see if this codec can return consistent value. + * If not, turn on aggressive read workaround + * (STAC9704 comes in mind). + */ + if (i != j) { + codec->flags |= AC97_F_RDCD_BUG; + i = ac97_rdcd(codec, AC97_REG_RESET); + } codec->caps = i & 0x03ff; codec->se = (i & 0x7c00) >> 10; @@ -605,27 +684,82 @@ } ac97_fix_auxout(codec); ac97_fix_tone(codec); + ac97_fix_volume(codec); if (codec_patch) codec_patch(codec); for (i = 0; i < 32; i++) { k = codec->noext? codec->mix[i].enable : 1; - if (k && (codec->mix[i].reg > 0)) { - old = ac97_rdcd(codec, codec->mix[i].reg); - ac97_wrcd(codec, codec->mix[i].reg, 0x3f); - j = ac97_rdcd(codec, codec->mix[i].reg); - ac97_wrcd(codec, codec->mix[i].reg, old); - codec->mix[i].enable = (j != 0 && j != old)? 1 : 0; - for (k = 1; j & (1 << k); k++); - codec->mix[i].bits = j? k - codec->mix[i].ofs : 0; + reg = codec->mix[i].reg; + if (reg < 0) + reg = -reg; + if (k && reg) { + j = old = ac97_rdcd(codec, reg); + /* + * Test for mute bit (except for AC97_MIX_TONE, + * where we simply assume it as available). + */ + if (codec->mix[i].mute) { + ac97_wrcd(codec, reg, j | 0x8000); + j = ac97_rdcd(codec, reg); + } else + j |= 0x8000; + if ((j & 0x8000)) { + /* + * Test whether the control width should be + * 4, 5 or 6 bit. For 5bit register, we should + * test it whether it's really 5 or 6bit. Leave + * 4bit register alone, because sometimes an + * attempt to write past 4th bit may cause + * incorrect result especially for AC97_MIX_BEEP + * (ac97 2.3). + */ + bit = codec->mix[i].bits; + if (bit == 5) + bit++; + j = ((1 << bit) - 1) << codec->mix[i].ofs; + ac97_wrcd(codec, reg, + j | (codec->mix[i].mute ? 0x8000 : 0)); + k = ac97_rdcd(codec, reg) & j; + k >>= codec->mix[i].ofs; + if (reg == AC97_MIX_TONE && + ((k & 0x0001) == 0x0000)) + k >>= 1; + for (j = 0; k >> j; j++) + ; + if (j != 0) { +#if 0 + device_printf(codec->dev, "%2d: [ac97_rdcd() = %d] [Testbit = %d] %d -> %d\n", + i, k, bit, codec->mix[i].bits, j); +#endif + codec->mix[i].enable = 1; + codec->mix[i].bits = j; + } else if (reg == AC97_MIX_BEEP) { + /* + * Few codec such as CX20468-21 does + * have this control register, although + * the only usable part is the mute bit. + */ + codec->mix[i].enable = 1; + } else + codec->mix[i].enable = 0; + } else + codec->mix[i].enable = 0; + ac97_wrcd(codec, reg, old); } - /* printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); */ +#if 0 + printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); +#endif } device_printf(codec->dev, "<%s>\n", ac97_hw_desc(codec->id, vname, cname, desc)); if (bootverbose) { + if (codec->flags & AC97_F_RDCD_BUG) + device_printf(codec->dev, "Buggy AC97 Codec: aggressive ac97_rdcd() workaround enabled\n"); + if (codec->flags & AC97_F_SOFTVOL) + device_printf(codec->dev, "Soft PCM volume\n"); device_printf(codec->dev, "Codec features "); for (i = j = 0; i < 10; i++) if (codec->caps & (1 << i)) @@ -645,8 +779,16 @@ } } - if ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) - device_printf(codec->dev, "ac97 codec reports dac not ready\n"); + i = 0; + while ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) { + if (++i == 100) { + device_printf(codec->dev, "ac97 codec reports dac not ready\n"); + break; + } + DELAY(1000); + } + if (bootverbose) + device_printf(codec->dev, "ac97 codec dac ready count: %d\n", i); snd_mtxunlock(codec->lock); return 0; } @@ -685,15 +827,17 @@ struct ac97_info * ac97_create(device_t dev, void *devinfo, kobj_class_t cls) { + struct snddev_info *d = device_get_softc(dev); struct ac97_info *codec; + int eapd_inv; - codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT); + codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT | M_ZERO); if (codec == NULL) return NULL; snprintf(codec->name, AC97_NAMELEN, "%s:ac97", device_get_nameunit(dev)); codec->lock = snd_mtxcreate(codec->name, "ac97 codec"); - codec->methods = kobj_create(cls, M_AC97, M_WAITOK); + codec->methods = kobj_create(cls, M_AC97, M_WAITOK | M_ZERO); if (codec->methods == NULL) { snd_mtxlock(codec->lock); snd_mtxfree(codec->lock); @@ -704,6 +848,13 @@ codec->dev = dev; codec->devinfo = devinfo; codec->flags = 0; + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "ac97_eapd_inv", &eapd_inv) == 0) { + if (eapd_inv != 0) + codec->flags |= AC97_F_EAPD_INV; + } + if (d != NULL) + d->flags |= SD_F_AC97; return codec; } @@ -822,4 +973,65 @@ return &ac97mixer_class; } +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_ac97_external_amplifier(SYSCTL_HANDLER_ARGS) +{ + struct ac97_info *codec; + int ea, inv, err = 0; + u_int16_t val; + codec = oidp->oid_arg1; + if (codec == NULL || codec->id == 0 || codec->lock == NULL) + return EINVAL; + snd_mtxlock(codec->lock); + val = ac97_rdcd(codec, AC97_REG_POWER); + inv = (codec->flags & AC97_F_EAPD_INV) ? 0 : 1; + ea = (val >> 15) ^ inv; + snd_mtxunlock(codec->lock); + err = sysctl_handle_int(oidp, &ea, sizeof(ea), req); + if (err == 0 && req->newptr != NULL) { + if (ea != 0 && ea != 1) + return EINVAL; + if (ea != ((val >> 15) ^ inv)) { + snd_mtxlock(codec->lock); + ac97_wrcd(codec, AC97_REG_POWER, val ^ 0x8000); + snd_mtxunlock(codec->lock); + } + } + return err; +} +#endif + +int +ac97_initsys(device_t dev) +{ +#ifdef SND_DYNSYSCTL + struct ac97_info *codec; + struct snddev_info *d; + u_int16_t orig, val; + + d = device_get_softc(dev); + if (d == NULL || d->mixer_dev == NULL || d->mixer_dev->si_drv1 == NULL) + return ENODEV; + if (!(d->flags & SD_F_AC97)) + return ENODEV; + codec = mix_getdevinfo(d->mixer_dev->si_drv1); + if (codec == NULL || codec->id == 0 || codec->lock == NULL) + return ENODEV; + + snd_mtxlock(codec->lock); + orig = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig ^ 0x8000); + val = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig); + snd_mtxunlock(codec->lock); + if ((val & 0x8000) == (orig & 0x8000)) + return EINVAL; + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "ac97_external_amplifier", CTLTYPE_INT | CTLFLAG_RW, + codec, sizeof(codec), sysctl_hw_snd_ac97_external_amplifier, + "I", "AC97 External Amplifier"); +#endif + return 0; +} --- sys/dev/sound/pcm/ac97.h.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/ac97.h Sat Mar 18 07:03:31 2006 @@ -81,6 +81,8 @@ #define AC97_REG_ID2 0x7e #define AC97_F_EAPD_INV 0x00000001 +#define AC97_F_RDCD_BUG 0x00000002 +#define AC97_F_SOFTVOL 0x00000004 #define AC97_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) #define AC97_CREATE(dev, devinfo, cls) ac97_create(dev, devinfo, &cls ## _class) @@ -103,4 +105,4 @@ u_int16_t ac97_rdcd(struct ac97_info *codec, int reg); void ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val); - +int ac97_initsys(device_t dev); --- sys/dev/sound/pcm/ac97_patch.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/ac97_patch.c Sun Mar 19 22:56:06 2006 @@ -46,3 +46,13 @@ ac97_wrcd(codec, 0x76, ac97_rdcd(codec, 0x76) | 0x0420); } +void cmi9739_patch(struct ac97_info* codec) +{ + /* + * Few laptops (notably ASUS W1000N) need extra register + * initialization to power up the internal speakers. + */ + ac97_wrcd(codec, AC97_REG_POWER, 0x000f); + ac97_wrcd(codec, AC97_MIXEXT_CLFE, 0x0000); + ac97_wrcd(codec, 0x64, 0x7110); +} --- sys/dev/sound/pcm/ac97_patch.h.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/ac97_patch.h Sat Mar 18 07:03:31 2006 @@ -29,3 +29,4 @@ void ad1886_patch(struct ac97_info*); void ad198x_patch(struct ac97_info*); +void cmi9739_patch(struct ac97_info*); --- sys/dev/sound/pcm/buffer.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/buffer.c Sun Mar 19 22:56:06 2006 @@ -286,8 +286,12 @@ b->fmt = fmt; b->bps = 1; b->bps <<= (b->fmt & AFMT_STEREO)? 1 : 0; - b->bps <<= (b->fmt & AFMT_16BIT)? 1 : 0; - b->bps <<= (b->fmt & AFMT_32BIT)? 2 : 0; + if (b->fmt & AFMT_16BIT) + b->bps <<= 1; + else if (b->fmt & AFMT_24BIT) + b->bps *= 3; + else if (b->fmt & AFMT_32BIT) + b->bps <<= 2; return 0; } --- sys/dev/sound/pcm/channel.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/channel.c Sun Mar 19 22:56:06 2006 @@ -32,8 +32,10 @@ SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/channel.c,v 1.97.2.1 2005/01/30 01:00:04 imp Exp $"); #define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ +#if 0 #define DMA_ALIGN_THRESHOLD 4 #define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) +#endif #define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) @@ -59,7 +61,7 @@ return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); + 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", "irq rate for buffering"); static int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 1, "report software-emulated formats"); @@ -105,7 +107,9 @@ return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0; } else { amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); +#if 0 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; +#endif lim = 1; return (amt >= lim)? 1 : 0; } @@ -161,7 +165,7 @@ /* * chn_dmaupdate() tracks the status of a dma transfer, - * updating pointers. It must be called at spltty(). + * updating pointers. */ static unsigned int @@ -187,13 +191,23 @@ if (c->direction == PCMDIR_PLAY) { amt = MIN(delta, sndbuf_getready(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = MIN(delta, sndbuf_getfree(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } + if ((c->flags & CHN_F_TRIGGERED) && delta == 0) { + device_printf(c->dev, "WARNING: PCMDIR_%s DMA completion " + "too fast/slow ! hwptr=%u, old=%u " + "delta=%u amt=%u ready=%u free=%u\n", + (c->direction == PCMDIR_PLAY) ? "PLAY" : "REC", + hwptr, old, delta, amt, + sndbuf_getready(b), sndbuf_getfree(b)); + } return delta; } @@ -225,23 +239,31 @@ unsigned int ret, amt; CHN_LOCKASSERT(c); -/* DEB( +#if 0 + DEB( if (c->flags & CHN_F_CLOSING) { sndbuf_dump(b, "b", 0x02); sndbuf_dump(bs, "bs", 0x02); - }) */ + }) +#endif if (c->flags & CHN_F_MAPPED) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); amt = sndbuf_getfree(b); - KASSERT(amt <= sndbuf_getsize(bs), - ("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, - amt, sndbuf_getsize(bs), c->flags)); - if (sndbuf_getready(bs) < amt) + DEB(if (amt > sndbuf_getsize(bs) && + sndbuf_getbps(bs) >= sndbuf_getbps(b)) { + printf("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, + amt, sndbuf_getsize(bs), c->flags); + }); + + ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; + /* + * Possible xruns. There should be no empty space left in buffer. + */ + if (sndbuf_getfree(b) > 0) c->xruns++; - ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; if (ret == 0 && sndbuf_getfree(b) < amt) chn_wakeup(c); @@ -351,19 +373,25 @@ return ret; } +#if 0 static int chn_rddump(struct pcm_channel *c, unsigned int cnt) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); +#if 0 + static uint32_t kk = 0; + printf("%u: dumping %d bytes\n", ++kk, cnt); +#endif + c->xruns++; sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); return sndbuf_dispose(b, NULL, cnt); } +#endif /* * Feed new data from the read buffer. Can be called in the bottom half. - * Hence must be called at spltty. */ int chn_rdfeed(struct pcm_channel *c) @@ -379,16 +407,21 @@ sndbuf_dump(bs, "bs", 0x02); }) +#if 0 amt = sndbuf_getready(b); if (sndbuf_getfree(bs) < amt) { c->xruns++; amt = sndbuf_getfree(bs); } +#endif + amt = sndbuf_getfree(bs); ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; amt = sndbuf_getready(b); - if (amt > 0) - chn_rddump(c, amt); + if (amt > 0) { + c->xruns++; + sndbuf_dispose(b, NULL, amt); + } chn_wakeup(c); @@ -408,8 +441,8 @@ chn_trigger(c, PCMTRIG_EMLDMARD); chn_dmaupdate(c); ret = chn_rdfeed(c); - if (ret) - printf("chn_rdfeed: %d\n", ret); + DEB(if (ret) + printf("chn_rdfeed: %d\n", ret);) } @@ -533,10 +566,12 @@ * fed at the first irq. */ if (c->direction == PCMDIR_PLAY) { + /* + * Reduce pops during playback startup. + */ + sndbuf_fillsilence(b); if (SLIST_EMPTY(&c->children)) chn_wrfeed(c); - else - sndbuf_fillsilence(b); } sndbuf_setrun(b, 1); c->xruns = 0; @@ -721,6 +756,131 @@ return 0; } +static struct afmtstr_table default_afmtstr_table[] = { + { "alaw", AFMT_A_LAW }, { "mulaw", AFMT_MU_LAW }, + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +int +afmtstr_swap_sign(char *s) +{ + if (s == NULL || strlen(s) < 2) /* full length of "s8" */ + return 0; + if (*s == 's') + *s = 'u'; + else if (*s == 'u') + *s = 's'; + else + return 0; + return 1; +} + +int +afmtstr_swap_endian(char *s) +{ + if (s == NULL || strlen(s) < 5) /* full length of "s16le" */ + return 0; + if (s[3] == 'l') + s[3] = 'b'; + else if (s[3] == 'b') + s[3] = 'l'; + else + return 0; + return 1; +} + +u_int32_t +afmtstr2afmt(struct afmtstr_table *tbl, const char *s, int stereo) +{ + size_t fsz, sz; + + sz = (s == NULL) ? 0 : strlen(s); + + if (sz > 1) { + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->fmtstr != NULL; tbl++) { + fsz = strlen(tbl->fmtstr); + if (sz < fsz) + continue; + if (strncmp(s, tbl->fmtstr, fsz) != 0) + continue; + if (fsz == sz) + return tbl->format | + ((stereo) ? AFMT_STEREO : 0); + if ((sz - fsz) < 2 || s[fsz] != ':') + break; + /* + * For now, just handle mono/stereo. + */ + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 'm' || + s[fsz + 1] == '1')) || + strcmp(s + fsz + 1, "mono") == 0) + return tbl->format; + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 's' || + s[fsz + 1] == '2')) || + strcmp(s + fsz + 1, "stereo") == 0) + return tbl->format | AFMT_STEREO; + break; + } + } + + return 0; +} + +u_int32_t +afmt2afmtstr(struct afmtstr_table *tbl, u_int32_t afmt, char *dst, + size_t len, int type, int stereo) +{ + u_int32_t fmt = 0; + char *fmtstr = NULL, *tag = ""; + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->format != 0; tbl++) { + if (tbl->format == 0) + break; + if ((afmt & ~AFMT_STEREO) != tbl->format) + continue; + fmt = afmt; + fmtstr = tbl->fmtstr; + break; + } + + if (fmt != 0 && fmtstr != NULL && dst != NULL && len > 0) { + strlcpy(dst, fmtstr, len); + switch (type) { + case AFMTSTR_SIMPLE: + tag = (fmt & AFMT_STEREO) ? ":s" : ":m"; + break; + case AFMTSTR_NUM: + tag = (fmt & AFMT_STEREO) ? ":2" : ":1"; + break; + case AFMTSTR_FULL: + tag = (fmt & AFMT_STEREO) ? ":stereo" : ":mono"; + break; + case AFMTSTR_NONE: + default: + break; + } + if (strlen(tag) > 0 && ((stereo && !(fmt & AFMT_STEREO)) || \ + (!stereo && (fmt & AFMT_STEREO)))) + strlcat(dst, tag, len); + } + + return fmt; +} + int chn_reset(struct pcm_channel *c, u_int32_t fmt) { @@ -733,18 +893,24 @@ r = CHANNEL_RESET(c->methods, c->devinfo); if (fmt != 0) { +#if 0 hwspd = DSP_DEFAULT_SPEED; /* only do this on a record channel until feederbuilder works */ if (c->direction == PCMDIR_REC) RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); c->speed = hwspd; +#endif + hwspd = chn_getcaps(c)->minspeed; + c->speed = hwspd; if (r == 0) r = chn_setformat(c, fmt); if (r == 0) r = chn_setspeed(c, hwspd); +#if 0 if (r == 0) r = chn_setvolume(c, 100, 100); +#endif } if (r == 0) r = chn_setblocksize(c, 0, 0); @@ -880,7 +1046,15 @@ { CHN_LOCKASSERT(c); /* should add a feeder for volume changing if channel returns -1 */ - c->volume = (left << 8) | right; + if (left > 100) + left = 100; + if (left < 0) + left = 0; + if (right > 100) + right = 100; + if (right < 0) + right = 0; + c->volume = left | (right << 8); return 0; } @@ -912,7 +1086,10 @@ delta = -delta; c->feederflags &= ~(1 << FEEDER_RATE); - if (delta > 500) + /* + * Used to be 500. It was too big! + */ + if (delta > 25) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); @@ -945,6 +1122,11 @@ r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r)); out: + if (!r) + r = CHANNEL_SETFORMAT(c->methods, c->devinfo, + sndbuf_getfmt(b)); + if (!r) + sndbuf_setfmt(bs, c->format); DEB(printf("setspeed done, r = %d\n", r)); return r; } else @@ -1002,46 +1184,89 @@ return r; } +/* + * given a bufsz value, round it to a power of 2 in the min-max range + * XXX only works if min and max are powers of 2 + */ +static int +round_bufsz(int bufsz, int min, int max) +{ + int tmp = min * 2; + + KASSERT((min & (min-1)) == 0, ("min %d must be power of 2\n", min)); + KASSERT((max & (max-1)) == 0, ("max %d must be power of 2\n", max)); + while (tmp <= bufsz) + tmp <<= 1; + tmp >>= 1; + if (tmp > max) + tmp = max; + return tmp; +} + +/* + * set the channel's blocksize both for soft and hard buffers. + * + * blksz should be a power of 2 between 2**4 and 2**16 -- it is useful + * that it has the same value for both bufsoft and bufhard. + * blksz == -1 computes values according to a target irq rate. + * blksz == 0 reuses previous values if available, otherwise + * behaves as for -1 + * + * blkcnt is set by the user, between 2 and (2**17)/blksz for bufsoft, + * but should be a power of 2 for bufhard to simplify life to low + * level drivers. + * Note, for the rec channel a large blkcnt is ok, + * but for the play channel we want blksz as small as possible to keep + * the delay small, because routines in the write path always try to + * keep bufhard full. + * + * Unless we have good reason to, use the values suggested by the caller. + */ int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; - int irqhz, tmp, ret, maxsize, reqblksz, tmpblksz; + int irqhz, ret, maxsz, maxsize, reqblksz; CHN_LOCKASSERT(c); if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) { - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d", __func__, - c->name, sndbuf_getsize(bs), sndbuf_getsize(b))); + DEB(if (!(sndbuf_getsize(bs) == 0 || + sndbuf_getsize(bs) >= sndbuf_getsize(b) || + sndbuf_getbps(bs) < sndbuf_getbps(b))) { + printf("%s(%s): bufsoft size %d < bufhard size %d", __func__, + c->name, sndbuf_getsize(bs), sndbuf_getsize(b)) + }); return EINVAL; } c->flags |= CHN_F_SETBLOCKSIZE; ret = 0; DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); - if (blksz == 0 || blksz == -1) { - if (blksz == -1) + if (blksz == 0 || blksz == -1) { /* let the driver choose values */ + if (blksz == -1) /* delete previous values */ c->flags &= ~CHN_F_HAS_SIZE; - if (!(c->flags & CHN_F_HAS_SIZE)) { - blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / chn_targetirqrate; - tmp = 32; - while (tmp <= blksz) - tmp <<= 1; - tmp >>= 1; - blksz = tmp; + if (!(c->flags & CHN_F_HAS_SIZE)) { /* no previous value */ + /* + * compute a base blksz according to the target irq + * rate, then round to a suitable power of 2 + * in the range 16.. 2^17/2. + * Finally compute a suitable blkcnt. + */ + blksz = round_bufsz( (sndbuf_getbps(bs) * + sndbuf_getspd(bs)) / chn_targetirqrate, + 16, CHN_2NDBUFMAXSIZE / 2); blkcnt = CHN_2NDBUFMAXSIZE / blksz; - - RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); - RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz); - DEB(printf("%s: defaulting to (%d, %d)\n", __func__, blkcnt, blksz)); - } else { + } else { /* use previously defined value */ blkcnt = sndbuf_getblkcnt(bs); blksz = sndbuf_getblksz(bs); - DEB(printf("%s: updating (%d, %d)\n", __func__, blkcnt, blksz)); } } else { + /* + * use supplied values if reasonable. Note that here we + * might have blksz which is not a power of 2 if the + * ioctl() to compute it allows such values. + */ ret = EINVAL; if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) goto out; @@ -1050,27 +1275,29 @@ } reqblksz = blksz; + if (reqblksz < sndbuf_getbps(bs)) + reqblksz = sndbuf_getbps(bs); + if (reqblksz % sndbuf_getbps(bs)) + reqblksz -= reqblksz % sndbuf_getbps(bs); /* adjust for different hw format/speed */ + /* + * Now compute the approx irq rate for the given (soft) blksz, + * reduce to the acceptable range and compute a corresponding blksz + * for the hard buffer. Then set the channel's blocksize and + * corresponding hardbuf value. The number of blocks used should + * be set by the device-specific routine. In fact, even the + * call to sndbuf_setblksz() should not be here! XXX + */ + irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / blksz; - DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __func__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz)); RANGE(irqhz, 16, 512); - tmpblksz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz; - - /* round down to 2^x */ - blksz = 32; - while (blksz <= tmpblksz) - blksz <<= 1; - blksz >>= 1; - - /* round down to fit hw buffer size */ - if (sndbuf_getmaxsize(b) > 0) - RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2); - else - /* virtual channels don't appear to allocate bufhard */ - RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); - DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __func__, blksz, sndbuf_getmaxsize(b))); + maxsz = sndbuf_getmaxsize(b); + if (maxsz == 0) /* virtual channels don't appear to allocate bufhard */ + maxsz = CHN_2NDBUFMAXSIZE; + blksz = round_bufsz( (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz, + 16, maxsz / 2); /* Increase the size of bufsoft if before increasing bufhard. */ maxsize = sndbuf_getsize(b); @@ -1092,7 +1319,7 @@ maxsize = sndbuf_getsize(b); if (reqblksz * blkcnt > maxsize) maxsize = reqblksz * blkcnt; - if (maxsize > sndbuf_getsize(bs)) + if (sndbuf_getbps(bs) >= sndbuf_getbps(b) && maxsize > sndbuf_getsize(bs)) printf("Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE()\n", c->name, sndbuf_getsize(bs), maxsize); if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { @@ -1101,18 +1328,35 @@ goto out1; } - irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b); - DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz)); - chn_resetbuf(c); out1: - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d, reqblksz=%d blksz=%d maxsize=%d blkcnt=%d", + DEB(if (!(sndbuf_getsize(bs) == 0 || + sndbuf_getsize(bs) >= sndbuf_getsize(b) || + sndbuf_getbps(bs) < sndbuf_getbps(b))) { + printf("%s(%s): bufsoft size %d < bufhard size %d, reqblksz=%d blksz=%d maxsize=%d blkcnt=%d", __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b), reqblksz, - blksz, maxsize, blkcnt)); + blksz, maxsize, blkcnt); + }); out: c->flags &= ~CHN_F_SETBLOCKSIZE; +#if 0 + if (1) { + static uint32_t kk = 0; + printf("%u: b %d/%d/%d : (%d)%d/0x%0x | bs %d/%d/%d : (%d)%d/0x%0x\n", ++kk, + sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), + sndbuf_getbps(b), + sndbuf_getspd(b), sndbuf_getfmt(b), + sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), + sndbuf_getbps(bs), + sndbuf_getspd(bs), sndbuf_getfmt(bs)); + if (sndbuf_getsize(b) % sndbuf_getbps(b) || + sndbuf_getblksz(b) % sndbuf_getbps(b) || + sndbuf_getsize(bs) % sndbuf_getbps(bs) || + sndbuf_getblksz(b) % sndbuf_getbps(b)) { + printf("%u: bps/blksz alignment screwed!\n", kk); + } + } +#endif return ret; } @@ -1133,6 +1377,7 @@ int chn_getptr(struct pcm_channel *c) { +#if 0 int hwptr; int a = (1 << c->align) - 1; @@ -1144,6 +1389,15 @@ #endif hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ return hwptr; +#endif +#if 0 + int hwptr; + + CHN_LOCKASSERT(c); + hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; + return (hwptr - (hwptr % sndbuf_getbps(c->bufhard))); +#endif + return (c->flags & CHN_F_TRIGGERED) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; } struct pcmchan_caps * @@ -1166,7 +1420,9 @@ /* report software-supported formats */ if (report_soft_formats) - fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U16_LE|AFMT_U16_BE| + fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U32_LE|AFMT_U32_BE| + AFMT_S32_LE|AFMT_S32_BE|AFMT_U24_LE|AFMT_U24_BE| + AFMT_S24_LE|AFMT_S24_BE|AFMT_U16_LE|AFMT_U16_BE| AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8; return fmts; @@ -1177,8 +1433,9 @@ { struct feeder_class *fc; struct pcm_feederdesc desc; - u_int32_t tmp[2], type, flags, hwfmt; + u_int32_t tmp[2], type, flags, hwfmt, *fmtlist; int err; + char fmtstr[AFMTSTR_MAXSZ]; CHN_LOCKASSERT(c); while (chn_removefeeder(c) == 0); @@ -1198,8 +1455,13 @@ } c->feeder->desc->out = c->format; } else { - desc.type = FEEDER_MIXER; - desc.in = 0; + if (c->flags & CHN_F_HAS_VCHAN) { + desc.type = FEEDER_MIXER; + desc.in = c->format; + } else { + DEB(printf("can't decide which feeder type to use!\n")); + return EOPNOTSUPP; + } desc.out = c->format; desc.flags = 0; fc = feeder_getclass(&desc); @@ -1216,7 +1478,14 @@ return err; } } + c->feederflags &= ~(1 << FEEDER_VOLUME); + if (c->direction == PCMDIR_PLAY && + !(c->flags & CHN_F_VIRTUAL) && + c->parentsnddev && (c->parentsnddev->flags & SD_F_SOFTVOL) && + c->parentsnddev->mixer_dev) + c->feederflags |= 1 << FEEDER_VOLUME; flags = c->feederflags; + fmtlist = chn_getcaps(c)->fmtlist; DEB(printf("feederflags %x\n", flags)); @@ -1227,7 +1496,42 @@ desc.out = 0; desc.flags = 0; DEB(printf("find feeder type %d, ", type)); - fc = feeder_getclass(&desc); + if (type == FEEDER_VOLUME || type == FEEDER_RATE) { + if (c->feeder->desc->out & AFMT_32BIT) + strlcpy(fmtstr,"s32le", sizeof(fmtstr)); + else if (c->feeder->desc->out & AFMT_24BIT) + strlcpy(fmtstr, "s24le", sizeof(fmtstr)); + else { + /* + * 8bit doesn't provide enough headroom + * for proper processing without + * creating too much noises. Force to + * 16bit instead. + */ + strlcpy(fmtstr, "s16le", sizeof(fmtstr)); + } + if (!(c->feeder->desc->out & AFMT_8BIT) && + c->feeder->desc->out & AFMT_BIGENDIAN) + afmtstr_swap_endian(fmtstr); + if (!(c->feeder->desc->out & (AFMT_A_LAW | AFMT_MU_LAW)) && + !(c->feeder->desc->out & AFMT_SIGNED)) + afmtstr_swap_sign(fmtstr); + desc.in = afmtstr2afmt(NULL, fmtstr, AFMTSTR_MONO_RETURN); + if (desc.in == 0) + desc.in = AFMT_S16_LE; + /* feeder_volume need stereo processing */ + if (type == FEEDER_VOLUME || + c->feeder->desc->out & AFMT_STEREO) + desc.in |= AFMT_STEREO; + desc.out = desc.in; + fc = feeder_getclass(&desc); + if (fc != NULL && fc->desc != NULL) + desc.flags = fc->desc->flags; + } else { + fc = feeder_getclass(&desc); + if (fc != NULL && fc->desc != NULL) + desc = *fc->desc; + } DEB(printf("got %p\n", fc)); if (fc == NULL) { DEB(printf("can't find required feeder type %d\n", type)); @@ -1235,19 +1539,17 @@ return EOPNOTSUPP; } - if (c->feeder->desc->out != fc->desc->in) { - DEB(printf("build fmtchain from 0x%x to 0x%x: ", c->feeder->desc->out, fc->desc->in)); - tmp[0] = fc->desc->in; - tmp[1] = 0; - if (chn_fmtchain(c, tmp) == 0) { - DEB(printf("failed\n")); + DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in)); + tmp[0] = desc.in; + tmp[1] = 0; + if (chn_fmtchain(c, tmp) == 0) { + DEB(printf("failed\n")); - return ENODEV; - } - DEB(printf("ok\n")); + return ENODEV; } + DEB(printf("ok\n")); - err = chn_addfeeder(c, fc, fc->desc); + err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add feeder %p, output 0x%x, err %d\n", fc, fc->desc->out, err)); @@ -1257,31 +1559,36 @@ } } - if (fmtvalid(c->feeder->desc->out, chn_getcaps(c)->fmtlist)) { - hwfmt = c->feeder->desc->out; - } else { - if (c->direction == PCMDIR_REC) { - tmp[0] = c->format; - tmp[1] = 0; - hwfmt = chn_fmtchain(c, tmp); - } else { -#if 0 - u_int32_t *x = chn_getcaps(c)->fmtlist; - printf("acceptable formats for %s:\n", c->name); - while (*x) { - printf("[0x%8x] ", *x); - x++; - } -#endif - hwfmt = chn_fmtchain(c, chn_getcaps(c)->fmtlist); - } - } + if (c->direction == PCMDIR_REC) { + tmp[0] = c->format; + tmp[1] = 0; + hwfmt = chn_fmtchain(c, tmp); + } else + hwfmt = chn_fmtchain(c, fmtlist); - if (hwfmt == 0) + if (hwfmt == 0 || !fmtvalid(hwfmt, fmtlist)) { + DEB(printf("Invalid hardware format: 0x%08x\n", hwfmt)); return ENODEV; + } sndbuf_setfmt(c->bufhard, hwfmt); + if ((flags & (1 << FEEDER_VOLUME))) { + int vol = 100 | (100 << 8); + + CHN_UNLOCK(c); + /* + * XXX This is ugly! The way mixer subs being so secretive + * about its own internals force us to use this silly + * monkey trick. + */ + if (mixer_ioctl(c->parentsnddev->mixer_dev, + MIXER_READ(SOUND_MIXER_PCM), (caddr_t)&vol, -1, NULL) != 0) + device_printf(c->dev, "Soft Volume: Failed to read default value\n"); + CHN_LOCK(c); + chn_setvolume(c, vol & 0x7f, (vol >> 8) & 0x7f); + } + return 0; } @@ -1325,15 +1632,21 @@ */ } if (flags & CHN_N_BLOCKSIZE) { + u_int32_t fmt; int blksz; /* * scan the children, find the lowest blocksize and use that * for the hard blocksize */ + fmt = sndbuf_getfmt(c->bufsoft); blksz = sndbuf_getmaxsize(c->bufhard) / 2; SLIST_FOREACH(pce, &c->children, link) { child = pce->channel; CHN_LOCK(child); + if (fmt != sndbuf_getfmt(child->bufhard)) { + CHN_UNLOCK(child); + continue; + } if (sndbuf_getblksz(child->bufhard) < blksz) blksz = sndbuf_getblksz(child->bufhard); CHN_UNLOCK(child); --- sys/dev/sound/pcm/channel.h.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/channel.h Sat Mar 18 07:03:31 2006 @@ -114,6 +114,26 @@ int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); +#define AFMTSTR_NONE 0 /* "s16le" */ +#define AFMTSTR_SIMPLE 1 /* "s16le:s" */ +#define AFMTSTR_NUM 2 /* "s16le:2" */ +#define AFMTSTR_FULL 3 /* "s16le:stereo" */ + +#define AFMTSTR_MAXSZ 13 /* include null terminator */ + +#define AFMTSTR_MONO_RETURN 0 +#define AFMTSTR_STEREO_RETURN 1 + +struct afmtstr_table { + char *fmtstr; + u_int32_t format; +}; + +int afmtstr_swap_sign(char *); +int afmtstr_swap_endian(char *); +u_int32_t afmtstr2afmt(struct afmtstr_table *, const char *, int); +u_int32_t afmt2afmtstr(struct afmtstr_table *, u_int32_t, char *, size_t, int, int); + #define PCMDIR_VIRTUAL 2 #define PCMDIR_PLAY 1 #define PCMDIR_REC -1 @@ -137,10 +157,13 @@ #define CHN_F_DEAD 0x00020000 #define CHN_F_BADSETTING 0x00040000 #define CHN_F_SETBLOCKSIZE 0x00080000 +#define CHN_F_HAS_VCHAN 0x00100000 #define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */ -#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | CHN_F_VIRTUAL) +#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | \ + CHN_F_HAS_VCHAN | CHN_F_VIRTUAL) + #define CHN_N_RATE 0x00000001 #define CHN_N_FORMAT 0x00000002 --- sys/dev/sound/pcm/dsp.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/dsp.c Sun Mar 19 22:56:06 2006 @@ -102,7 +102,7 @@ } /* - * return the channels channels associated with an open device instance. + * return the channels associated with an open device instance. * set the priority if the device is simplex and one direction (only) is * specified. * lock channels specified. @@ -169,15 +169,20 @@ { struct pcm_channel *rdch, *wrch; struct snddev_info *d; - intrmask_t s; u_int32_t fmt; int devtype; - int rdref; int error; + int chnum; + + if (i_dev == NULL || td == NULL) + return ENODEV; + + if ((flags & (FREAD | FWRITE)) == 0) + return EINVAL; - s = spltty(); d = dsp_get_info(i_dev); devtype = PCMDEV(i_dev); + chnum = -1; /* decide default format */ switch (devtype) { @@ -197,40 +202,29 @@ fmt = 0; break; - case SND_DEV_DSPREC: + case SND_DEV_DSPHW: + /* + * HW *specific* access without channel numbering confusion + * caused by "first come first served" by dsp_clone(). + */ fmt = AFMT_U8; - if (mode & FWRITE) { - splx(s); - return EINVAL; - } + chnum = PCMCHAN(i_dev); break; default: panic("impossible devtype %d", devtype); } - rdref = 0; - /* lock snddev so nobody else can monkey with it */ pcm_lock(d); rdch = i_dev->si_drv1; wrch = i_dev->si_drv2; - if ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && (rdch || wrch)) { - /* we're a simplex device and already open, no go */ - pcm_unlock(d); - splx(s); - return EBUSY; - } - - if (((flags & FREAD) && rdch) || ((flags & FWRITE) && wrch)) { - /* - * device already open in one or both directions that - * the opener wants; we can't handle this. - */ + if (rdch || wrch || ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && + (flags & (FREAD | FWRITE)) == (FREAD | FWRITE))) { + /* simplex or not, better safe than sorry. */ pcm_unlock(d); - splx(s); return EBUSY; } @@ -242,65 +236,51 @@ */ if (flags & FREAD) { /* open for read */ - if (devtype == SND_DEV_DSPREC) - rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, PCMCHAN(i_dev)); - else - rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, -1); - if (!rdch) { - /* no channel available, exit */ - pcm_unlock(d); - splx(s); - return EBUSY; - } - /* got a channel, already locked for us */ - if (chn_reset(rdch, fmt)) { - pcm_chnrelease(rdch); - i_dev->si_drv1 = NULL; - pcm_unlock(d); - splx(s); - return ENODEV; + pcm_unlock(d); + error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, chnum); + if (error != 0 && error != EBUSY && chnum != -1 && (flags & FWRITE)) + error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, -1); + + if (error == 0 && (chn_reset(rdch, fmt) || + (fmt && chn_setspeed(rdch, DSP_DEFAULT_SPEED)))) + error = ENODEV; + + if (error != 0) { + if (rdch) + pcm_chnrelease(rdch); + return error; } if (flags & O_NONBLOCK) rdch->flags |= CHN_F_NBIO; pcm_chnref(rdch, 1); CHN_UNLOCK(rdch); - rdref = 1; - /* - * Record channel created, ref'ed and unlocked - */ + pcm_lock(d); } if (flags & FWRITE) { /* open for write */ - wrch = pcm_chnalloc(d, PCMDIR_PLAY, td->td_proc->p_pid, -1); - error = 0; + pcm_unlock(d); + error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, chnum); + if (error != 0 && error != EBUSY && chnum != -1 && (flags & FREAD)) + error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, -1); - if (!wrch) - error = EBUSY; /* XXX Right return code? */ - else if (chn_reset(wrch, fmt)) + if (error == 0 && (chn_reset(wrch, fmt) || + (fmt && chn_setspeed(wrch, DSP_DEFAULT_SPEED)))) error = ENODEV; if (error != 0) { - if (wrch) { - /* - * Free play channel - */ + if (wrch) pcm_chnrelease(wrch); - i_dev->si_drv2 = NULL; - } - if (rdref) { + if (rdch) { /* * Lock, deref and release previously created record channel */ CHN_LOCK(rdch); pcm_chnref(rdch, -1); pcm_chnrelease(rdch); - i_dev->si_drv1 = NULL; } - pcm_unlock(d); - splx(s); return error; } @@ -308,13 +288,13 @@ wrch->flags |= CHN_F_NBIO; pcm_chnref(wrch, 1); CHN_UNLOCK(wrch); + pcm_lock(d); } i_dev->si_drv1 = rdch; i_dev->si_drv2 = wrch; pcm_unlock(d); - splx(s); return 0; } @@ -323,45 +303,19 @@ { struct pcm_channel *rdch, *wrch; struct snddev_info *d; - intrmask_t s; int refs; - s = spltty(); d = dsp_get_info(i_dev); pcm_lock(d); rdch = i_dev->si_drv1; wrch = i_dev->si_drv2; + pcm_unlock(d); - refs = 0; - - if (rdch) { - CHN_LOCK(rdch); - refs += pcm_chnref(rdch, -1); - CHN_UNLOCK(rdch); - } - if (wrch) { - CHN_LOCK(wrch); - refs += pcm_chnref(wrch, -1); - CHN_UNLOCK(wrch); - } - - /* - * If there are no more references, release the channels. - */ - if ((rdch || wrch) && refs == 0) { - - if (pcm_getfakechan(d)) - pcm_getfakechan(d)->flags = 0; - - i_dev->si_drv1 = NULL; - i_dev->si_drv2 = NULL; - - dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT); - - pcm_unlock(d); - + if (rdch || wrch) { + refs = 0; if (rdch) { CHN_LOCK(rdch); + refs += pcm_chnref(rdch, -1); chn_abort(rdch); /* won't sleep */ rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); chn_reset(rdch, 0); @@ -369,6 +323,7 @@ } if (wrch) { CHN_LOCK(wrch); + refs += pcm_chnref(wrch, -1); /* * XXX: Maybe the right behaviour is to abort on non_block. * It seems that mplayer flushes the audio queue by quickly @@ -381,9 +336,24 @@ chn_reset(wrch, 0); pcm_chnrelease(wrch); } - } else + + pcm_lock(d); + if (rdch) + i_dev->si_drv1 = NULL; + if (wrch) + i_dev->si_drv2 = NULL; + /* + * If there are no more references, release the channels. + */ + if (refs == 0 && i_dev->si_drv1 == NULL && + i_dev->si_drv2 == NULL) { + if (pcm_getfakechan(d)) + pcm_getfakechan(d)->flags = 0; + /* What is this?!? */ + dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT); + } pcm_unlock(d); - splx(s); + } return 0; } @@ -391,10 +361,8 @@ dsp_read(struct cdev *i_dev, struct uio *buf, int flag) { struct pcm_channel *rdch, *wrch; - intrmask_t s; int ret; - s = spltty(); getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD); KASSERT(rdch, ("dsp_read: nonexistant channel")); @@ -402,7 +370,6 @@ if (rdch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); - splx(s); return EINVAL; } if (!(rdch->flags & CHN_F_RUNNING)) @@ -410,7 +377,6 @@ ret = chn_read(rdch, buf); relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); - splx(s); return ret; } @@ -418,10 +384,8 @@ dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { struct pcm_channel *rdch, *wrch; - intrmask_t s; int ret; - s = spltty(); getchns(i_dev, &rdch, &wrch, SD_F_PRIO_WR); KASSERT(wrch, ("dsp_write: nonexistant channel")); @@ -429,7 +393,6 @@ if (wrch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); - splx(s); return EINVAL; } if (!(wrch->flags & CHN_F_RUNNING)) @@ -437,7 +400,6 @@ ret = chn_write(wrch, buf); relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); - splx(s); return ret; } @@ -446,7 +408,6 @@ { struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; - intrmask_t s; int kill; int ret = 0, *arg_i = (int *)arg, tmp; @@ -456,10 +417,16 @@ */ d = dsp_get_info(i_dev); - if (IOCGROUP(cmd) == 'M') - return mixer_ioctl(d->mixer_dev, cmd, arg, mode, td); + if (IOCGROUP(cmd) == 'M') { + /* + * This is at least, a bug to bug compatible with OSS. + */ + if (d->mixer_dev != NULL) + return mixer_ioctl(d->mixer_dev, cmd, arg, -1, td); + else + return EBADF; + } - s = spltty(); getchns(i_dev, &rdch, &wrch, 0); kill = 0; @@ -469,7 +436,6 @@ kill |= 2; if (kill == 3) { relchns(i_dev, rdch, wrch, 0); - splx(s); return EINVAL; } if (kill & 1) @@ -483,13 +449,18 @@ * we start with the new ioctl interface. */ case AIONWRITE: /* how many bytes can write ? */ - CHN_LOCK(wrch); + if (wrch) { + CHN_LOCK(wrch); /* if (wrch && wrch->bufhard.dl) while (chn_wrfeed(wrch) == 0); */ - *arg_i = wrch? sndbuf_getfree(wrch->bufsoft) : 0; - CHN_UNLOCK(wrch); + *arg_i = sndbuf_getfree(wrch->bufsoft); + CHN_UNLOCK(wrch); + } else { + *arg_i = 0; + ret = EINVAL; + } break; case AIOSSIZE: /* set the current blocksize */ @@ -534,9 +505,15 @@ { snd_chan_param *p = (snd_chan_param *)arg; + if (cmd == AIOSFMT && + ((p->play_format != 0 && p->play_rate == 0) || + (p->rec_format != 0 && p->rec_rate == 0))) { + ret = EINVAL; + break; + } if (wrch) { CHN_LOCK(wrch); - if (cmd == AIOSFMT) { + if (cmd == AIOSFMT && p->play_format != 0) { chn_setformat(wrch, p->play_format); chn_setspeed(wrch, p->play_rate); } @@ -549,7 +526,7 @@ } if (rdch) { CHN_LOCK(rdch); - if (cmd == AIOSFMT) { + if (cmd == AIOSFMT && p->rec_format != 0) { chn_setformat(rdch, p->rec_format); chn_setspeed(rdch, p->rec_rate); } @@ -630,8 +607,10 @@ */ *arg_i = sndbuf_getready(rdch->bufsoft); CHN_UNLOCK(rdch); - } else + } else { *arg_i = 0; + ret = EINVAL; + } break; case FIOASYNC: /*set/clear async i/o */ @@ -665,9 +644,14 @@ case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = sndbuf_getblksz(chn->bufsoft); - CHN_UNLOCK(chn); + if (chn) { + CHN_LOCK(chn); + *arg_i = sndbuf_getblksz(chn->bufsoft); + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } break ; case SNDCTL_DSP_SETBLKSIZE: @@ -731,9 +715,14 @@ case SOUND_PCM_READ_RATE: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = chn->speed; - CHN_UNLOCK(chn); + if (chn) { + CHN_LOCK(chn); + *arg_i = chn->speed; + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } break; case SNDCTL_DSP_STEREO: @@ -784,16 +773,26 @@ case SOUND_PCM_READ_CHANNELS: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = (chn->format & AFMT_STEREO) ? 2 : 1; - CHN_UNLOCK(chn); + if (chn) { + CHN_LOCK(chn); + *arg_i = (chn->format & AFMT_STEREO) ? 2 : 1; + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = chn_getformats(chn); - CHN_UNLOCK(chn); + 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 */ @@ -827,6 +826,7 @@ u_int32_t fragln = (*arg_i) & 0x0000ffff; u_int32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; u_int32_t fragsz; + u_int32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; @@ -842,9 +842,12 @@ if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); - maxfrags = sndbuf_getblkcnt(rdch->bufsoft); - fragsz = sndbuf_getblksz(rdch->bufsoft); + r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft); + r_fragsz = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); + } else { + r_maxfrags = maxfrags; + r_fragsz = fragsz; } if (wrch && ret == 0) { CHN_LOCK(wrch); @@ -852,6 +855,9 @@ maxfrags = sndbuf_getblkcnt(wrch->bufsoft); fragsz = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); + } else { /* use whatever came from the read channel */ + maxfrags = r_maxfrags; + fragsz = r_fragsz; } fragln = 0; @@ -888,7 +894,7 @@ struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - chn_wrupdate(wrch); + /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_getfree(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); @@ -905,7 +911,7 @@ struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); - chn_rdupdate(rdch); + /* XXX abusive DMA update: chn_rdupdate(rdch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getreadyptr(bs); @@ -923,7 +929,7 @@ struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - chn_wrupdate(wrch); + /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); @@ -942,9 +948,23 @@ case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = (chn->format & AFMT_16BIT) ? 16 : 8; - CHN_UNLOCK(chn); + 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: @@ -990,7 +1010,7 @@ struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - chn_wrupdate(wrch); + /* XXX abusive DMA update: chn_wrupdate(wrch); */ *arg_i = sndbuf_getready(b) + sndbuf_getready(bs); CHN_UNLOCK(wrch); } else @@ -1031,7 +1051,6 @@ break; } relchns(i_dev, rdch, wrch, 0); - splx(s); return ret; } @@ -1039,10 +1058,8 @@ dsp_poll(struct cdev *i_dev, int events, struct thread *td) { struct pcm_channel *wrch = NULL, *rdch = NULL; - intrmask_t s; int ret, e; - s = spltty(); ret = 0; getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); @@ -1058,7 +1075,6 @@ } relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); return ret; } @@ -1066,12 +1082,10 @@ dsp_mmap(struct cdev *i_dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) { struct pcm_channel *wrch = NULL, *rdch = NULL, *c; - intrmask_t s; if (nprot & PROT_EXEC) return -1; - s = spltty(); getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); #if 0 /* @@ -1084,7 +1098,6 @@ } else if (rdch && (nprot & PROT_READ)) { c = rdch; } else { - splx(s); return -1; } #else @@ -1093,13 +1106,11 @@ if (c == NULL) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); return -1; } if (offset >= sndbuf_getsize(c->bufsoft)) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); return -1; } @@ -1109,7 +1120,6 @@ *paddr = vtophys(sndbuf_getbufofs(c->bufsoft, offset)); relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); return 0; } @@ -1130,8 +1140,8 @@ struct snddev_info *pcm_dev; struct snddev_channel *pcm_chan; int i, unit, devtype; - int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO}; - char *devnames[3] = {"dsp", "dspW", "audio"}; + static int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO}; + static char *devnames[3] = {"dsp", "dspW", "audio"}; if (*dev != NULL) return; @@ -1173,8 +1183,9 @@ panic("Unknown devtype %d", devtype); } - if ((pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { + if ((pdev != NULL) && (pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { *dev = pdev; + dev_ref(*dev); return; } } --- sys/dev/sound/pcm/fake.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/fake.c Sun Mar 19 22:56:06 2006 @@ -29,6 +29,10 @@ SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/fake.c,v 1.13.4.1 2005/01/30 01:00:05 imp Exp $"); static u_int32_t fk_fmt[] = { + AFMT_MU_LAW, + AFMT_STEREO | AFMT_MU_LAW, + AFMT_A_LAW, + AFMT_STEREO | AFMT_A_LAW, AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, @@ -41,6 +45,22 @@ AFMT_STEREO | AFMT_S16_BE, AFMT_U16_BE, AFMT_STEREO | AFMT_U16_BE, + AFMT_S24_LE, + AFMT_STEREO | AFMT_S24_LE, + AFMT_U24_LE, + AFMT_STEREO | AFMT_U24_LE, + AFMT_S24_BE, + AFMT_STEREO | AFMT_S24_BE, + AFMT_U24_BE, + AFMT_STEREO | AFMT_U24_BE, + AFMT_S32_LE, + AFMT_STEREO | AFMT_S32_LE, + AFMT_U32_LE, + AFMT_STEREO | AFMT_U32_LE, + AFMT_S32_BE, + AFMT_STEREO | AFMT_S32_BE, + AFMT_U32_BE, + AFMT_STEREO | AFMT_U32_BE, 0 }; static struct pcmchan_caps fk_caps = {0, 1000000, fk_fmt, 0}; @@ -120,6 +140,13 @@ c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK); c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK); c->parentsnddev = d; + /* + * Fake channel is such a blessing in disguise. Using this, + * we can keep track prefered virtual channel speed / format without + * querying kernel hint repetitively (see vchan_create / vchan.c). + */ + c->speed = 0; + c->format = 0; snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev)); return c; --- sys/dev/sound/pcm/feeder.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/feeder.c Sun Mar 19 22:56:06 2006 @@ -259,37 +259,339 @@ return 1; } +/* + * See feeder_fmtchain() for the mumbo-jumbo ridiculous explaination + * of what the heck is this FMT_Q_* + */ +#define FMT_Q_UP 1 +#define FMT_Q_DOWN 2 +#define FMT_Q_EQ 3 +#define FMT_Q_MULTI 4 + +/* + * 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_val(s1) ((s1) & 0x3f00) +#define score_cse(s1) ((s1) & 0x7f) + +u_int32_t +chn_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; + 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 +chn_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 (fmtvalid(fmt, fmts)) + return fmt; + + best = 0; + score = chn_fmtscore(fmt); + oldscore = 0; + for (i = 0; fmts[i] != 0; i++) { + score2 = chn_fmtscore(fmts[i]); + if (cheq && !score_cheq(score, score2)) + 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 +chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) +{ + return chn_fmtbestfunc(fmt, fmts, 0); +} + +u_int32_t +chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +{ + return chn_fmtbestfunc(fmt, fmts, 1); +} + +u_int32_t +chn_fmtbest(u_int32_t fmt, u_int32_t *fmts) +{ + u_int32_t best1, best2; + u_int32_t score, score1, score2; + + if (fmtvalid(fmt, fmts)) + return fmt; + + best1 = chn_fmtbeststereo(fmt, fmts); + best2 = chn_fmtbestbit(fmt, fmts); + + if (best1 != 0 && best2 != 0 && best1 != best2) { + if (fmt & AFMT_STEREO) + return best1; + else { + score = score_val(chn_fmtscore(fmt)); + score1 = score_val(chn_fmtscore(best1)); + score2 = score_val(chn_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; +} + static struct pcm_feeder * feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) { - struct feedertab_entry *fte; + struct feedertab_entry *fte, *ftebest; struct pcm_feeder *try, *ret; + uint32_t fl, qout, qsrc, qdst; + int qtype; - /* printf("trying %s (%x -> %x)...\n", source->class->name, source->desc->in, source->desc->out); */ + if (to == NULL || to[0] == 0) + return NULL; + + DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); if (fmtvalid(source->desc->out, to)) { - /* printf("got it\n"); */ + DEB(printf("got it\n")); return source; } if (maxdepth < 0) return NULL; + /* + * WARNING: THIS IS _NOT_ FOR THE FAINT HEART + * Disclaimer: I don't expect anybody could understand this + * without deep logical and mathematical analysis + * involving various unnamed probability theorem. + * + * This "Best Fit Random Chain Selection" (BLEHBLEHWHATEVER) algorithm + * is **extremely** difficult to digest especially when applied to + * large sets / numbers of random chains (feeders), each with + * unique characteristic providing different sets of in/out format. + * + * Basically, our FEEDER_FMT (see feeder_fmt.c) chains characteristic: + * 1) Format chains + * 1.1 "8bit to any, not to 8bit" + * 1.1.1 sign can remain consistent, e.g: u8 -> u16[le|be] + * 1.1.2 sign can be changed, e.g: u8 -> s16[le|be] + * 1.1.3 endian can be changed, e.g: u8 -> u16[le|be] + * 1.1.4 both can be changed, e.g: u8 -> [u|s]16[le|be] + * 1.2 "Any to 8bit, not from 8bit" + * 1.2.1 sign can remain consistent, e.g: s16le -> s8 + * 1.2.2 sign can be changed, e.g: s16le -> u8 + * 1.2.3 source endian can be anything e.g: s16[le|be] -> s8 + * 1.2.4 source endian / sign can be anything e.g: [u|s]16[le|be] -> u8 + * 1.3 "Any to any where BOTH input and output either 8bit or non-8bit" + * 1.3.1 endian MUST remain consistent + * 1.3.2 sign CAN be changed + * 1.4 "Long jump" is allowed, e.g: from 16bit to 32bit, excluding + * 16bit to 24bit . + * 2) Channel chains (mono <-> stereo) + * 2.1 Both endian and sign MUST remain consistent + * 3) Endian chains (big endian <-> little endian) + * 3.1 Channels and sign MUST remain consistent + * 4) Sign chains (signed <-> unsigned) + * 4.1 Channels and endian MUST remain consistent + * + * .. and the mother of all chaining rules: + * + * Rules 0: Source and destination MUST not contain multiple selections. + * (qtype != FMT_Q_MULTI) + * + * First of all, our caller ( chn_fmtchain() ) will reduce the possible + * multiple from/to formats to a single best format using chn_fmtbest(). + * Then, using chn_fmtscore(), we determine the chaining characteristic. + * Our main goal is to narrow it down until it reach FMT_Q_EQ chaining + * type while still adhering above chaining rules. + * + * The need for this complicated chaining procedures is inevitable, + * since currently we have more than 200 different types of FEEDER_FMT + * doing various unique format conversion. Without this (the old way), + * it is possible to generate broken chain since it doesn't do any + * sanity checking to ensure that the output format is "properly aligned" + * with the direction of conversion (quality up/down/equal). + * + * Conversion: s24le to s32le + * Possible chain: 1) s24le -> s32le (correct, optimized) + * 2) s24le -> s16le -> s32le + * (since we have feeder_24to16 and feeder_16to32) + * +-- obviously broken! + * + * Using scoring mechanisme, this will ensure that the chaining + * process do the right thing, or at least, give the best chain + * possible without causing quality (the 'Q') degradation. + */ + + qdst = chn_fmtscore(to[0]); + qsrc = chn_fmtscore(source->desc->out); + +#define score_q(s1) score_val(s1) +#define score_8bit(s1) ((s1) & 0x700) +#define score_non8bit(s1) (!score_8bit(s1)) +#define score_across8bit(s1, s2) ((score_8bit(s1) && score_non8bit(s2)) || \ + (score_8bit(s2) && score_non8bit(s1))) + +#define FMT_CHAIN_Q_UP(s1, s2) (score_q(s1) < score_q(s2)) +#define FMT_CHAIN_Q_DOWN(s1, s2) (score_q(s1) > score_q(s2)) +#define FMT_CHAIN_Q_EQ(s1, s2) (score_q(s1) == score_q(s2)) +#define FMT_Q_DOWN_FLAGS(s1, s2) (0x1 | (score_across8bit(s1, s2) ? \ + 0x2 : 0x0)) +#define FMT_Q_UP_FLAGS(s1, s2) FMT_Q_DOWN_FLAGS(s1, s2) +#define FMT_Q_EQ_FLAGS(s1, s2) (0x3ffc | \ + ((score_cheq(s1, s2) && \ + score_endianeq(s1, s2)) ? \ + 0x1 : 0x0) | \ + ((score_cheq(s1, s2) && \ + score_signeq(s1, s2)) ? \ + 0x2 : 0x0)) + + /* Determine chaining direction and set matching flag */ + fl = 0x3fff; + if (to[1] != 0) { + qtype = FMT_Q_MULTI; + printf("%s: WARNING: FMT_Q_MULTI chaining. Expect the unexpected.\n", __func__); + } else if (FMT_CHAIN_Q_DOWN(qsrc, qdst)) { + qtype = FMT_Q_DOWN; + fl = FMT_Q_DOWN_FLAGS(qsrc, qdst); + } else if (FMT_CHAIN_Q_UP(qsrc, qdst)) { + qtype = FMT_Q_UP; + fl = FMT_Q_UP_FLAGS(qsrc, qdst); + } else { + qtype = FMT_Q_EQ; + fl = FMT_Q_EQ_FLAGS(qsrc, qdst); + } + + ftebest = NULL; + SLIST_FOREACH(fte, &feedertab, link) { if (fte->desc == NULL) continue; if (fte->desc->type != FEEDER_FMT) continue; - if (fte->desc->in == source->desc->out) { + qout = chn_fmtscore(fte->desc->out); +#define FMT_Q_MULTI_VALIDATE(qt) ((qt) == FMT_Q_MULTI) +#define FMT_Q_FL_MATCH(qfl, s1, s2) (((s1) & (qfl)) == ((s2) & (qfl))) +#define FMT_Q_UP_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_UP && \ + score_q(s3) >= score_q(s1) && \ + score_q(s3) <= score_q(s2)) +#define FMT_Q_DOWN_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_DOWN && \ + score_q(s3) <= score_q(s1) && \ + score_q(s3) >= score_q(s2)) +#define FMT_Q_EQ_VALIDATE(qt, s1, s2) ((qt) == FMT_Q_EQ && \ + score_q(s1) == score_q(s2)) + if (fte->desc->in == source->desc->out && + (FMT_Q_MULTI_VALIDATE(qtype) || + (FMT_Q_FL_MATCH(fl, qout, qdst) && + (FMT_Q_UP_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_DOWN_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_EQ_VALIDATE(qtype, qdst, qout))))) { try = feeder_create(fte->feederclass, fte->desc); if (try) { try->source = source; - ret = chainok(try, stop)? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; if (ret != NULL) return ret; feeder_destroy(try); } + } else if (fte->desc->in == source->desc->out) { + /* XXX quality must be considered! */ + if (ftebest == NULL) + ftebest = fte; } } + + if (ftebest != NULL) { + try = feeder_create(ftebest->feederclass, ftebest->desc); + if (try) { + try->source = source; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } + /* printf("giving up %s...\n", source->class->name); */ return NULL; @@ -299,7 +601,7 @@ chn_fmtchain(struct pcm_channel *c, u_int32_t *to) { struct pcm_feeder *try, *del, *stop; - u_int32_t tmpfrom[2], best, *from; + u_int32_t tmpfrom[2], tmpto[2], best, *from; int i, max, bestmax; KASSERT(c != NULL, ("c == NULL")); @@ -307,44 +609,81 @@ KASSERT(to != NULL, ("to == NULL")); KASSERT(to[0] != 0, ("to[0] == 0")); + if (c == NULL || c->feeder == NULL || to == NULL || to[0] == 0) + return 0; + stop = c->feeder; + best = 0; if (c->direction == PCMDIR_REC && c->feeder->desc->type == FEEDER_ROOT) { from = chn_getcaps(c)->fmtlist; + if (from[1] != 0) { + best = chn_fmtbest(to[0], from); + if (best != 0) { + tmpfrom[0] = best; + tmpfrom[1] = 0; + from = tmpfrom; + } + } } else { tmpfrom[0] = c->feeder->desc->out; tmpfrom[1] = 0; from = tmpfrom; + if (to[1] != 0) { + best = chn_fmtbest(from[0], to); + if (best != 0) { + tmpto[0] = best; + tmpto[1] = 0; + to = tmpto; + } + } } - i = 0; - best = 0; - bestmax = 100; - while (from[i] != 0) { - c->feeder->desc->out = from[i]; - try = NULL; +#define FEEDER_FMTCHAIN_MAXDEPTH 8 + + try = NULL; + + if (to[0] != 0 && from[0] != 0 && + to[1] == 0 && from[1] == 0) { max = 0; - while (try == NULL && max < 8) { + best = from[0]; + c->feeder->desc->out = best; + do { try = feeder_fmtchain(to, c->feeder, stop, max); - if (try == NULL) - max++; - } - if (try != NULL && max < bestmax) { - bestmax = max; - best = from[i]; - } - while (try != NULL && try != stop) { - del = try; - try = try->source; - feeder_destroy(del); + DEB(if (try != NULL) { + printf("%s: 0x%08x -> 0x%08x (maxdepth: %d)\n", + __func__, from[0], to[0], max) + }); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + } else { + printf("%s: Using the old-way format chaining!\n", __func__); + i = 0; + best = 0; + bestmax = 100; + while (from[i] != 0) { + c->feeder->desc->out = from[i]; + try = NULL; + max = 0; + do { + try = feeder_fmtchain(to, c->feeder, stop, max); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + if (try != NULL && max < bestmax) { + bestmax = max; + best = from[i]; + } + while (try != NULL && try != stop) { + del = try; + try = try->source; + feeder_destroy(del); + } + i++; } - i++; - } - if (best == 0) - return 0; + if (best == 0) + return 0; - c->feeder->desc->out = best; - try = feeder_fmtchain(to, c->feeder, stop, bestmax); + c->feeder->desc->out = best; + try = feeder_fmtchain(to, c->feeder, stop, bestmax); + } if (try == NULL) return 0; @@ -371,7 +710,16 @@ printf("%s [%d]\n", try->class->name, try->desc->idx); #endif - return (c->direction == PCMDIR_REC)? best : c->feeder->desc->out; + if (c->direction == PCMDIR_REC) { + try = c->feeder; + while (try != NULL) { + if (try->desc->type == FEEDER_ROOT) + return try->desc->out; + try = try->source; + } + return best; + } else + return c->feeder->desc->out; } void --- sys/dev/sound/pcm/feeder.h.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/feeder.h Sat Mar 18 07:03:31 2006 @@ -53,6 +53,10 @@ void feeder_register(void *p); struct feeder_class *feeder_getclass(struct pcm_feederdesc *desc); +u_int32_t chn_fmtscore(u_int32_t fmt); +u_int32_t chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts); +u_int32_t chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts); +u_int32_t chn_fmtbest(u_int32_t fmt, u_int32_t *fmts); u_int32_t chn_fmtchain(struct pcm_channel *c, u_int32_t *to); int chn_addfeeder(struct pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc); int chn_removefeeder(struct pcm_channel *c); @@ -68,7 +72,7 @@ .desc = feeder ## _desc, \ .data = pdata, \ }; \ -SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _class); +SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_ANY, feeder_register, &feeder ## _class); #define FEEDER_ROOT 1 #define FEEDER_FMT 2 --- sys/dev/sound/pcm/feeder_fmt.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/feeder_fmt.c Sun Mar 19 22:56:06 2006 @@ -1,5 +1,6 @@ /*- - * Copyright (c) 1999 Cameron Grant + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,54 +23,106 @@ * 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. + * + * *New* and rewritten soft format converter, supporting 24/32bit pcm data, + * simplified and optimized. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This new implementation is fully dedicated in memory of Cameron Grant, * + * the creator of the magnificent, highly addictive feeder infrastructure. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * */ #include - #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_fmt.c,v 1.13.4.1 2005/01/30 01:00:05 imp Exp $"); MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); -#define FEEDBUFSZ 8192 +#define FEEDBUFSZ 65536 +#define FEEDBUF24SZ 65532 -static unsigned char ulaw_to_u8[] = { - 3, 7, 11, 15, 19, 23, 27, 31, - 35, 39, 43, 47, 51, 55, 59, 63, - 66, 68, 70, 72, 74, 76, 78, 80, - 82, 84, 86, 88, 90, 92, 94, 96, - 98, 99, 100, 101, 102, 103, 104, 105, - 106, 107, 108, 109, 110, 111, 112, 113, - 113, 114, 114, 115, 115, 116, 116, 117, - 117, 118, 118, 119, 119, 120, 120, 121, - 121, 121, 122, 122, 122, 122, 123, 123, - 123, 123, 124, 124, 124, 124, 125, 125, - 125, 125, 125, 125, 126, 126, 126, 126, - 126, 126, 126, 126, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 253, 249, 245, 241, 237, 233, 229, 225, - 221, 217, 213, 209, 205, 201, 197, 193, - 190, 188, 186, 184, 182, 180, 178, 176, - 174, 172, 170, 168, 166, 164, 162, 160, - 158, 157, 156, 155, 154, 153, 152, 151, - 150, 149, 148, 147, 146, 145, 144, 143, - 143, 142, 142, 141, 141, 140, 140, 139, - 139, 138, 138, 137, 137, 136, 136, 135, - 135, 135, 134, 134, 134, 134, 133, 133, - 133, 133, 132, 132, 132, 132, 131, 131, - 131, 131, 131, 131, 130, 130, 130, 130, - 130, 130, 130, 130, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, +#define FMT_TRACE(x...) /* printf(x) */ +#define FMT_TEST(x, y...) /* if (x) FMT_TRACE(y) */ + +/* + * Sign inverted ulaw/alaw -> 8 table + */ +static uint8_t ulaw_to_s8_tbl[] = { + 131, 135, 139, 143, 147, 151, 155, 159, + 163, 167, 171, 175, 179, 183, 187, 191, + 194, 196, 198, 200, 202, 204, 206, 208, + 210, 212, 214, 216, 218, 220, 222, 224, + 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, + 241, 242, 242, 243, 243, 244, 244, 245, + 245, 246, 246, 247, 247, 248, 248, 249, + 249, 249, 250, 250, 250, 250, 251, 251, + 251, 251, 252, 252, 252, 252, 253, 253, + 253, 253, 253, 253, 254, 254, 254, 254, + 254, 254, 254, 254, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 125, 121, 117, 113, 109, 105, 101, 97, + 93, 89, 85, 81, 77, 73, 69, 65, + 62, 60, 58, 56, 54, 52, 50, 48, + 46, 44, 42, 40, 38, 36, 34, 32, + 30, 29, 28, 27, 26, 25, 24, 23, + 22, 21, 20, 19, 18, 17, 16, 15, + 15, 14, 14, 13, 13, 12, 12, 11, + 11, 10, 10, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 6, 6, 5, 5, + 5, 5, 4, 4, 4, 4, 3, 3, + 3, 3, 3, 3, 2, 2, 2, 2, + 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static uint8_t alaw_to_s8_tbl[] = { + 236, 237, 234, 235, 240, 241, 238, 239, + 228, 229, 226, 227, 232, 233, 230, 231, + 246, 246, 245, 245, 248, 248, 247, 247, + 242, 242, 241, 241, 244, 244, 243, 243, + 171, 175, 163, 167, 187, 191, 179, 183, + 139, 143, 131, 135, 155, 159, 147, 151, + 214, 216, 210, 212, 222, 224, 218, 220, + 198, 200, 194, 196, 206, 208, 202, 204, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 251, 251, 251, 251, 252, 252, 252, 252, + 249, 249, 249, 249, 250, 250, 250, 250, + 254, 254, 254, 254, 254, 254, 254, 254, + 253, 253, 253, 253, 253, 253, 253, 253, + 20, 19, 22, 21, 16, 15, 18, 17, + 28, 27, 30, 29, 24, 23, 26, 25, + 10, 10, 11, 11, 8, 8, 9, 9, + 14, 14, 15, 15, 12, 12, 13, 13, + 85, 81, 93, 89, 69, 65, 77, 73, + 117, 113, 125, 121, 101, 97, 109, 105, + 42, 40, 46, 44, 34, 32, 38, 36, + 58, 56, 62, 60, 50, 48, 54, 52, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 4, 4, 4, 4, + 7, 7, 7, 7, 6, 6, 6, 6, + 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, }; -static unsigned char u8_to_ulaw[] = { +static uint8_t u8_to_ulaw_tbl[] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, @@ -104,119 +157,199 @@ 129, 129, 129, 129, 128, 128, 128, 128, }; -static unsigned char alaw_to_ulaw[] = { - 42, 43, 40, 41, 46, 47, 44, 45, - 34, 35, 32, 33, 38, 39, 36, 37, - 57, 58, 55, 56, 61, 62, 59, 60, - 49, 50, 48, 48, 53, 54, 51, 52, - 10, 11, 8, 9, 14, 15, 12, 13, - 2, 3, 0, 1, 6, 7, 4, 5, - 26, 27, 24, 25, 30, 31, 28, 29, - 18, 19, 16, 17, 22, 23, 20, 21, - 98, 99, 96, 97, 102, 103, 100, 101, - 93, 93, 92, 92, 95, 95, 94, 94, - 116, 118, 112, 114, 124, 126, 120, 122, - 106, 107, 104, 105, 110, 111, 108, 109, - 72, 73, 70, 71, 76, 77, 74, 75, - 64, 65, 63, 63, 68, 69, 66, 67, - 86, 87, 84, 85, 90, 91, 88, 89, - 79, 79, 78, 78, 82, 83, 80, 81, - 170, 171, 168, 169, 174, 175, 172, 173, - 162, 163, 160, 161, 166, 167, 164, 165, - 185, 186, 183, 184, 189, 190, 187, 188, - 177, 178, 176, 176, 181, 182, 179, 180, - 138, 139, 136, 137, 142, 143, 140, 141, - 130, 131, 128, 129, 134, 135, 132, 133, - 154, 155, 152, 153, 158, 159, 156, 157, - 146, 147, 144, 145, 150, 151, 148, 149, - 226, 227, 224, 225, 230, 231, 228, 229, - 221, 221, 220, 220, 223, 223, 222, 222, - 244, 246, 240, 242, 252, 254, 248, 250, - 234, 235, 232, 233, 238, 239, 236, 237, - 200, 201, 198, 199, 204, 205, 202, 203, - 192, 193, 191, 191, 196, 197, 194, 195, - 214, 215, 212, 213, 218, 219, 216, 217, - 207, 207, 206, 206, 210, 211, 208, 209, -}; - -static unsigned char ulaw_to_alaw[] = { - 42, 43, 40, 41, 46, 47, 44, 45, - 34, 35, 32, 33, 38, 39, 36, 37, - 58, 59, 56, 57, 62, 63, 60, 61, - 50, 51, 48, 49, 54, 55, 52, 53, - 10, 11, 8, 9, 14, 15, 12, 13, - 2, 3, 0, 1, 6, 7, 4, 5, - 27, 24, 25, 30, 31, 28, 29, 18, - 19, 16, 17, 22, 23, 20, 21, 106, - 104, 105, 110, 111, 108, 109, 98, 99, - 96, 97, 102, 103, 100, 101, 122, 120, - 126, 127, 124, 125, 114, 115, 112, 113, - 118, 119, 116, 117, 75, 73, 79, 77, - 66, 67, 64, 65, 70, 71, 68, 69, - 90, 91, 88, 89, 94, 95, 92, 93, - 82, 82, 83, 83, 80, 80, 81, 81, - 86, 86, 87, 87, 84, 84, 85, 85, - 170, 171, 168, 169, 174, 175, 172, 173, - 162, 163, 160, 161, 166, 167, 164, 165, - 186, 187, 184, 185, 190, 191, 188, 189, - 178, 179, 176, 177, 182, 183, 180, 181, - 138, 139, 136, 137, 142, 143, 140, 141, - 130, 131, 128, 129, 134, 135, 132, 133, - 155, 152, 153, 158, 159, 156, 157, 146, - 147, 144, 145, 150, 151, 148, 149, 234, - 232, 233, 238, 239, 236, 237, 226, 227, - 224, 225, 230, 231, 228, 229, 250, 248, - 254, 255, 252, 253, 242, 243, 240, 241, - 246, 247, 244, 245, 203, 201, 207, 205, - 194, 195, 192, 193, 198, 199, 196, 197, - 218, 219, 216, 217, 222, 223, 220, 221, - 210, 210, 211, 211, 208, 208, 209, 209, - 214, 214, 215, 215, 212, 212, 213, 213, +static uint8_t u8_to_alaw_tbl[] = { + 42, 42, 42, 42, 42, 43, 43, 43, + 43, 40, 40, 40, 40, 41, 41, 41, + 41, 46, 46, 46, 46, 47, 47, 47, + 47, 44, 44, 44, 44, 45, 45, 45, + 45, 34, 34, 34, 34, 35, 35, 35, + 35, 32, 32, 32, 32, 33, 33, 33, + 33, 38, 38, 38, 38, 39, 39, 39, + 39, 36, 36, 36, 36, 37, 37, 37, + 37, 58, 58, 59, 59, 56, 56, 57, + 57, 62, 62, 63, 63, 60, 60, 61, + 61, 50, 50, 51, 51, 48, 48, 49, + 49, 54, 54, 55, 55, 52, 52, 53, + 53, 10, 11, 8, 9, 14, 15, 12, + 13, 2, 3, 0, 1, 6, 7, 4, + 5, 24, 30, 28, 18, 16, 22, 20, + 106, 110, 98, 102, 122, 114, 75, 90, + 213, 197, 245, 253, 229, 225, 237, 233, + 149, 151, 145, 147, 157, 159, 153, 155, + 133, 132, 135, 134, 129, 128, 131, 130, + 141, 140, 143, 142, 137, 136, 139, 138, + 181, 181, 180, 180, 183, 183, 182, 182, + 177, 177, 176, 176, 179, 179, 178, 178, + 189, 189, 188, 188, 191, 191, 190, 190, + 185, 185, 184, 184, 187, 187, 186, 186, + 165, 165, 165, 165, 164, 164, 164, 164, + 167, 167, 167, 167, 166, 166, 166, 166, + 161, 161, 161, 161, 160, 160, 160, 160, + 163, 163, 163, 163, 162, 162, 162, 162, + 173, 173, 173, 173, 172, 172, 172, 172, + 175, 175, 175, 175, 174, 174, 174, 174, + 169, 169, 169, 169, 168, 168, 168, 168, + 171, 171, 171, 171, 170, 170, 170, 170, }; -/*****************************************************************************/ +static int +feed_table_8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int j, sign, k = FEEDER_FEED(f->source, c, b, count, source); + uint8_t *tbl = (uint8_t *)f->data; + + j = k; + sign = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; + while (j > 0) { + j--; + b[j] = tbl[b[j]] ^ sign; + } + return k; +} + +static int +feed_table_16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, sign, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + uint8_t *tbl = (uint8_t *)f->data; + + i = k; + k <<= 1; + j = k; + sign = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; + if (f->desc->out & AFMT_BIGENDIAN) { + while (i > 0) { + b[--j] = 0; + b[--j] = tbl[b[--i]] ^ sign; + } + } else { + while (i > 0) { + b[--j] = tbl[b[--i]] ^ sign; + b[--j] = 0; + } + } + return k; +} static int -feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_table_xlaw(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i, j, k; + int j, sign, k = FEEDER_FEED(f->source, c, b, count, source); + uint8_t *tbl = (uint8_t *)f->data; - k = FEEDER_FEED(f->source, c, b, count / 2, source); - j = k - 1; - i = j * 2 + 1; - while (i > 0 && j >= 0) { - b[i--] = b[j--]; - b[i--] = 0; + j = k; + sign = (f->desc->in & AFMT_SIGNED) ? 0x80 : 0x00; + while (j > 0) { + j--; + b[j] = tbl[b[j] ^ sign]; } - return k * 2; + return k; } -static struct pcm_feederdesc feeder_8to16le_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, +static struct pcm_feederdesc feeder_ulawto8_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_ulawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), + {0, 0} +}; +FEEDER_DECLARE(feeder_ulawto8, 0, ulaw_to_s8_tbl); + +static struct pcm_feederdesc feeder_alawto8_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_alawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), + {0, 0} +}; +FEEDER_DECLARE(feeder_alawto8, 0, alaw_to_s8_tbl); + +static struct pcm_feederdesc feeder_ulawto16_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_ulawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), + {0, 0} +}; +FEEDER_DECLARE(feeder_ulawto16, 0, ulaw_to_s8_tbl); + +static struct pcm_feederdesc feeder_alawto16_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_alawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), + {0, 0} }; -static kobj_method_t feeder_8to16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_8to16le), - { 0, 0 } +FEEDER_DECLARE(feeder_alawto16, 0, alaw_to_s8_tbl); + +static struct pcm_feederdesc feeder_8toulaw_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_MU_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_MU_LAW|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8toulaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_xlaw), + {0, 0} +}; +FEEDER_DECLARE(feeder_8toulaw, 0, u8_to_ulaw_tbl); + +static struct pcm_feederdesc feeder_8toalaw_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_A_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_A_LAW|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8toalaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_xlaw), + {0, 0} }; -FEEDER_DECLARE(feeder_8to16le, 0, NULL); +FEEDER_DECLARE(feeder_8toalaw, 0, u8_to_alaw_tbl); -/*****************************************************************************/ +/* + * All conversion done in byte level to preserve endianess. + */ static int -feed_16to8_init(struct pcm_feeder *f) +feed_common_init(struct pcm_feeder *f) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); + f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT|M_ZERO); if (f->data == NULL) return ENOMEM; return 0; } static int -feed_16to8_free(struct pcm_feeder *f) +feed_common_free(struct pcm_feeder *f) { if (f->data) free(f->data, M_FMTFEEDER); @@ -224,326 +357,1101 @@ return 0; } +#define swap_sign(fdr) \ + (((((fdr)->desc->in & AFMT_SIGNED) && \ + !((fdr)->desc->out & AFMT_SIGNED)) || \ + (!((fdr)->desc->in & AFMT_SIGNED) && \ + ((fdr)->desc->out & AFMT_SIGNED))) ? 0x80 : 0x00) +/* + * Bit conversion + */ static int -feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_8to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 1, k; - - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - j += 2; + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int sign; + + i = k; + k <<= 1; + j = k; + sign = swap_sign(f); + if (f->desc->out & AFMT_BIGENDIAN) { + while (i > 0) { + b[--j] = 0; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 0) { + b[--j] = b[--i] ^ sign; + b[--j] = 0; + } } - return i; + return k; } +static struct pcm_feederdesc feeder_8to16_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to16_methods[] = { + KOBJMETHOD(feeder_feed, feed_8to16), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to16, 0, NULL); -static struct pcm_feederdesc feeder_16leto8_desc[] = { +static int +feed_16to8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + uint8_t *src = (uint8_t *)f->data; + + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); + k &= ~1; + i = k; + j = k >> 1; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + i--; + b[--j] = src[--i] ^ sign; + } + } else { + while (i > 1) { + b[--j] = src[--i] ^ sign; + i--; + } + } + return k >> 1; +} +static struct pcm_feederdesc feeder_16to8_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S8, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to8_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_16to8), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to8, 0, NULL); + +static int +feed_16to24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + + k = (count / 3) << 1; + k = FEEDER_FEED(f->source, c, b, k, source); + if (k < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); + k &= ~1; + i = k; + j = (k >> 1) * 3; + k = j; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + b[--j] = 0; + b[--j] = b[--i]; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 1) { + b[--j] = b[--i] ^ sign; + b[--j] = b[--i]; + b[--j] = 0; + } + } + return k; +} +static struct pcm_feederdesc feeder_16to24_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to24_methods[] = { + KOBJMETHOD(feeder_feed, feed_16to24), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to24, 0, NULL); + +static int +feed_24to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + uint8_t *src = (uint8_t *)f->data; + + k = (count * 3) >> 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); + if (k < 3) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); + k -= k % 3; + i = (k / 3) << 1; + j = i; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + k--; + b[--i] = src[--k]; + b[--i] = src[--k] ^ sign; + } + } else { + while (i > 1) { + b[--i] = src[--k] ^ sign; + b[--i] = src[--k]; + k--; + } + } + return j; +} +static struct pcm_feederdesc feeder_24to16_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to16_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_24to16), + {0, 0} }; -static kobj_method_t feeder_16leto8_methods[] = { - KOBJMETHOD(feeder_init, feed_16to8_init), - KOBJMETHOD(feeder_free, feed_16to8_free), - KOBJMETHOD(feeder_feed, feed_16leto8), - { 0, 0 } +FEEDER_DECLARE(feeder_24to16, 1, NULL); + +static int +feed_16to32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, sign, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + if (k < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); + k &= ~1; + i = k; + j = k << 1; + k = j; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + b[--j] = 0; + b[--j] = 0; + b[--j] = b[--i]; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 1) { + b[--j] = b[--i] ^ sign; + b[--j] = b[--i]; + b[--j] = 0; + b[--j] = 0; + } + } + return k; +} +static struct pcm_feederdesc feeder_16to32_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to32_methods[] = { + KOBJMETHOD(feeder_feed, feed_16to32), + {0, 0} }; -FEEDER_DECLARE(feeder_16leto8, 1, NULL); +FEEDER_DECLARE(feeder_16to32, 0, NULL); + +static int +feed_32to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + uint8_t *src = (uint8_t *)f->data; -/*****************************************************************************/ + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); + k &= ~3; + i = k; + k >>= 1; + j = k; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 3) { + i -= 2; + b[--j] = src[--i]; + b[--j] = src[--i] ^ sign; + } + } else { + while (i > 3) { + b[--j] = src[--i] ^ sign; + b[--j] = src[--i]; + i -= 2; + } + } + return k; +} +static struct pcm_feederdesc feeder_32to16_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to16_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_32to16), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to16, 1, NULL); static int -feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_24to32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count / 2, source); + int i, j, k, sign; - j = k - 1; - i = j * 2 + 1; - while (i > 0 && j >= 0) { - b[i--] = b[j]; - b[i--] = b[j]; - j--; + k = ((count & ~3) >> 2) * 3; + k = FEEDER_FEED(f->source, c, b, k, source); + if (k < 3) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); + k -= k % 3; + i = k; + k = (k / 3) << 2; + j = k; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 2) { + b[--j] = 0; + b[--j] = b[--i]; + b[--j] = b[--i]; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 2) { + b[--j] = b[--i] ^ sign; + b[--j] = b[--i]; + b[--j] = b[--i]; + b[--j] = 0; + } } - return k * 2; + return k; } +static struct pcm_feederdesc feeder_24to32_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to32_methods[] = { + KOBJMETHOD(feeder_feed, feed_24to32), + {0, 0} +}; +FEEDER_DECLARE(feeder_24to32, 1, NULL); + +static int +feed_32to24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + uint8_t *src = (uint8_t *)f->data; + k = (count / 3) << 2; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); + k &= ~3; + i = k; + k = (k >> 2) * 3; + j = k; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 3) { + --i; + b[--j] = src[--i]; + b[--j] = src[--i]; + b[--j] = src[--i] ^ sign; + } + } else { + while (i > 3) { + b[--j] = src[--i] ^ sign; + b[--j] = src[--i]; + b[--j] = src[--i]; + --i; + } + } + return k; +} +static struct pcm_feederdesc feeder_32to24_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to24_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_32to24), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to24, 1, NULL); +/* + * Bit conversion end + */ + +/* + * Channel conversion (mono -> stereo) + */ +static int +feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + + i = k; + j = k << 1; + while (i > 0) { + b[--j] = b[--i]; + b[--j] = b[i]; + } + return k << 1; +} static struct pcm_feederdesc feeder_monotostereo8_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_U8 | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S8, AFMT_S8 | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_U8, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_MU_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_A_LAW|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo8_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo8), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_monotostereo8), + {0, 0} }; FEEDER_DECLARE(feeder_monotostereo8, 0, NULL); -/*****************************************************************************/ - static int -feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count / 2, source); - u_int8_t x, y; + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + uint8_t l, m; - j = k - 1; - i = j * 2 + 1; - while (i >= 3 && j >= 1) { - x = b[j--]; - y = b[j--]; - b[i--] = x; - b[i--] = y; - b[i--] = x; - b[i--] = y; + if (k < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; } - return k * 2; + FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); + k &= ~1; + i = k; + j = k << 1; + while (i > 1) { + l = b[--i]; + m = b[--i]; + b[--j] = l; + b[--j] = m; + b[--j] = l; + b[--j] = m; + } + return k << 1; } - static struct pcm_feederdesc feeder_monotostereo16_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo16_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo16), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_monotostereo16), + {0, 0} }; FEEDER_DECLARE(feeder_monotostereo16, 0, NULL); -/*****************************************************************************/ - static int -feed_stereotomono8_init(struct pcm_feeder *f) +feed_monotostereo24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + uint8_t l, m, n; + + if (k < 3) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); + k -= k % 3; + i = k; + j = k << 1; + while (i > 2) { + l = b[--i]; + m = b[--i]; + n = b[--i]; + b[--j] = l; + b[--j] = m; + b[--j] = n; + b[--j] = l; + b[--j] = m; + b[--j] = n; + } + return k << 1; } +static struct pcm_feederdesc feeder_monotostereo24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_monotostereo24_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo24), + {0, 0} +}; +FEEDER_DECLARE(feeder_monotostereo24, 0, NULL); static int -feed_stereotomono8_free(struct pcm_feeder *f) +feed_monotostereo32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + uint8_t l, m, n, o; + + if (k < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); + k &= ~3; + i = k; + j = k << 1; + while (i > 3) { + l = b[--i]; + m = b[--i]; + n = b[--i]; + o = b[--i]; + b[--j] = l; + b[--j] = m; + b[--j] = n; + b[--j] = o; + b[--j] = l; + b[--j] = m; + b[--j] = n; + b[--j] = o; + } + return k << 1; } +static struct pcm_feederdesc feeder_monotostereo32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_monotostereo32_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo32), + {0, 0} +}; +FEEDER_DECLARE(feeder_monotostereo32, 0, NULL); +/* + * Channel conversion (mono -> stereo) end + */ +/* + * Channel conversion (stereo -> mono) + */ static int -feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 0, k; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - j += 2; + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; } - return i; + FMT_TEST(k & 1, "%s: Bytes not 8bit (stereo) aligned.\n", __func__); + k &= ~1; + i = k >> 1; + j = i; + while (i > 0) { + k--; + b[--i] = src[--k]; + } + return j; } - static struct pcm_feederdesc feeder_stereotomono8_desc[] = { - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S8, 0}, - {0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_A_LAW, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono8_methods[] = { - KOBJMETHOD(feeder_init, feed_stereotomono8_init), - KOBJMETHOD(feeder_free, feed_stereotomono8_free), - KOBJMETHOD(feeder_feed, feed_stereotomono8), - { 0, 0 } + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_stereotomono8), + {0, 0} }; -FEEDER_DECLARE(feeder_stereotomono8, 1, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_stereotomono8, 0, NULL); static int -feed_stereotomono16_init(struct pcm_feeder *f) +feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; -} - -static int -feed_stereotomono16_free(struct pcm_feeder *f) -{ - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; -} - -static int -feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) -{ - u_int32_t i = 0, toget = count * 2; - int j = 0, k; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - b[i++] = ((u_int8_t *)f->data)[j + 1]; - j += 4; + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 3, "%s: Bytes not 16bit (stereo) aligned.\n", __func__); + k &= ~3; + i = k >> 1; + j = i; + while (i > 1) { + k -= 2; + b[--i] = src[--k]; + b[--i] = src[--k]; } - return i; + return j; } - static struct pcm_feederdesc feeder_stereotomono16_desc[] = { - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE, 0}, - {0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S16_BE, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono16_methods[] = { - KOBJMETHOD(feeder_init, feed_stereotomono16_init), - KOBJMETHOD(feeder_free, feed_stereotomono16_free), - KOBJMETHOD(feeder_feed, feed_stereotomono16), - { 0, 0 } + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_stereotomono16), + {0, 0} }; -FEEDER_DECLARE(feeder_stereotomono16, 1, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_stereotomono16, 0, NULL); static int -feed_endian(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_stereotomono24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int8_t t; - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j, k; + uint8_t *src = (uint8_t *)f->data; - while (i < j) { - t = b[i]; - b[i] = b[i + 1]; - b[i + 1] = t; - i += 2; + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); + if (k < 6) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k % 6, "%s: Bytes not 24bit (stereo) aligned.\n", __func__); + k -= k % 6; + i = k >> 1; + j = i; + while (i > 2) { + k -= 3; + b[--i] = src[--k]; + b[--i] = src[--k]; + b[--i] = src[--k]; } - return i; + return j; } - -static struct pcm_feederdesc feeder_endian_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_endian_methods[] = { - KOBJMETHOD(feeder_feed, feed_endian), - { 0, 0 } +static struct pcm_feederdesc feeder_stereotomono24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S24_BE, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_stereotomono24_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_stereotomono24), + {0, 0} }; -FEEDER_DECLARE(feeder_endian, 0, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_stereotomono24, 0, NULL); static int -feed_sign(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_stereotomono32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); - intptr_t ssz = (intptr_t)f->data, ofs = ssz - 1; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; - while (i < j) { - b[i + ofs] ^= 0x80; - i += ssz; + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 8) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 7, "%s: Bytes not 32bit (stereo) aligned.\n", __func__); + k &= ~7; + i = k >> 1; + j = i; + while (i > 3) { + k -= 4; + b[--i] = src[--k]; + b[--i] = src[--k]; + b[--i] = src[--k]; + b[--i] = src[--k]; } - return i; + return j; } +static struct pcm_feederdesc feeder_stereotomono32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S32_BE, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_stereotomono32_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_stereotomono32), + {0, 0} +}; +FEEDER_DECLARE(feeder_stereotomono32, 0, NULL); +/* + * Channel conversion (stereo -> mono) end + */ + +/* + * Sign conversion + */ +static int +feed_sign8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j = FEEDER_FEED(f->source, c, b, count, source); + i = j; + while (i > 0) + b[--i] ^= 0x80; + return j; +} static struct pcm_feederdesc feeder_sign8_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_S8, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_sign8_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_sign8), + {0, 0} }; -FEEDER_DECLARE(feeder_sign8, 0, (void *)1); +FEEDER_DECLARE(feeder_sign8, 0, NULL); + +static int +feed_sign16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j = FEEDER_FEED(f->source, c, b, count, source); -static struct pcm_feederdesc feeder_sign16le_desc[] = { + if (j < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, j); + return 0; + } + FMT_TEST(j & 1, "%s: Bytes not 16bit aligned.\n", __func__); + j &= ~1; + i = j; + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + i--; + b[--i] ^= 0x80; + } + } else { + while (i > 1) { + b[--i] ^= 0x80; + i--; + } + } + return j; +} +static struct pcm_feederdesc feeder_sign16_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_sign16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign), - { 0, 0 } + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_sign16_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign16), + {0, 0} }; -FEEDER_DECLARE(feeder_sign16le, -1, (void *)2); +FEEDER_DECLARE(feeder_sign16, 0, NULL); -/*****************************************************************************/ +static int +feed_sign24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j = FEEDER_FEED(f->source, c, b, count, source); + + if (j < 3) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, j); + return 0; + } + FMT_TEST(j % 3, "%s: Bytes not 24bit aligned.\n", __func__); + j -= j % 3; + i = j; + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 2) { + i -= 2; + b[--i] ^= 0x80; + } + } else { + while (i > 2) { + b[--i] ^= 0x80; + i -= 2; + } + } + return j; +} +static struct pcm_feederdesc feeder_sign24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_sign24_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign24), + {0, 0} +}; +FEEDER_DECLARE(feeder_sign24, 0, NULL); static int -feed_table(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_sign32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j = FEEDER_FEED(f->source, c, b, count, source); - while (i < j) { - b[i] = ((u_int8_t *)f->data)[b[i]]; - i++; + if (j < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, j); + return 0; } - return i; + FMT_TEST(j & 3, "%s: Bytes not 32bit aligned.\n", __func__); + j &= ~3; + i = j; + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 3) { + i -= 3; + b[--i] ^= 0x80; + } + } else { + while (i > 3) { + b[--i] ^= 0x80; + i -= 3; + } + } + return j; } +static struct pcm_feederdesc feeder_sign32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_sign32_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign32), + {0, 0} +}; +FEEDER_DECLARE(feeder_sign32, 0, NULL); +/* + * Sign conversion end. + */ -static struct pcm_feederdesc feeder_ulawtou8_desc[] = { - {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_ulawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_u8); +/* + * Endian conversion. + */ +static int +feed_endian16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j = FEEDER_FEED(f->source, c, b, count, source); + uint8_t v; -static struct pcm_feederdesc feeder_u8toulaw_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, - {0}, + if (j < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, j); + return 0; + } + FMT_TEST(j & 1, "%s: Bytes not 16bit aligned.\n", __func__); + j &= ~1; + i = j; + while (i > 1) { + v = b[--i]; + b[i] = b[i - 1]; + b[--i] = v; + } + return j; +} +static struct pcm_feederdesc feeder_endian16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_u8toulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } +static kobj_method_t feeder_endian16_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian16), + {0, 0} }; -FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw); +FEEDER_DECLARE(feeder_endian16, 0, NULL); -static struct pcm_feederdesc feeder_alawtoulaw_desc[] = { - {FEEDER_FMT, AFMT_A_LAW, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_alawtoulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_alawtoulaw, 0, alaw_to_ulaw); +static int +feed_endian24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j = FEEDER_FEED(f->source, c, b, count, source); + uint8_t v; -static struct pcm_feederdesc feeder_ulawtoalaw_desc[] = { - {FEEDER_FMT, AFMT_MU_LAW, AFMT_A_LAW, 0}, - {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_ulawtoalaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } + if (j < 3) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, j); + return 0; + } + FMT_TEST(j % 3, "%s: Bytes not 24bit aligned.\n", __func__); + j -= j % 3; + i = j; + while (i > 2) { + v = b[--i]; + b[i] = b[i - 2]; + b[i -= 2] = v; + } + return j; +} +static struct pcm_feederdesc feeder_endian24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_endian24_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian24), + {0, 0} }; -FEEDER_DECLARE(feeder_ulawtoalaw, 0, ulaw_to_alaw); - +FEEDER_DECLARE(feeder_endian24, 0, NULL); +static int +feed_endian32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j = FEEDER_FEED(f->source, c, b, count, source); + uint8_t l, m; + if (j < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, j); + return 0; + } + FMT_TEST(j & 3, "%s: Bytes not 32bit aligned.\n", __func__); + j &= ~3; + i = j; + while (i > 3) { + l = b[--i]; + m = b[--i]; + b[i] = b[i - 1]; + b[i + 1] = b[i - 2]; + b[--i] = m; + b[--i] = l; + } + return j; +} +static struct pcm_feederdesc feeder_endian32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_endian32_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian32), + {0, 0} +}; +FEEDER_DECLARE(feeder_endian32, 0, NULL); +/* + * Endian conversion end + */ --- sys/dev/sound/pcm/feeder_rate.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/feeder_rate.c Sun Mar 19 22:56:06 2006 @@ -1,5 +1,7 @@ /*- - * Copyright (c) 2003 Orion Hodson + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003 Orion Hodson + * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,7 +25,34 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * MAINTAINER: Orion Hodson + * 2006-02-21: + * ========== + * + * Major cleanup and overhaul to remove much redundant codes. + * Highlights: + * 1) Support for signed / unsigned 16, 24 and 32 bit, + * big / little endian, + * 2) Unlimited channels. + * + * 2005-06-11: + * ========== + * + * *New* and rewritten soft sample rate converter supporting arbitrary sample + * rates, fine grained scaling/coefficients and a unified up/down stereo + * converter. Most of the disclaimers from orion's notes also applies + * here, regarding linear interpolation deficiencies and pre/post + * anti-aliasing filtering issues. This version comes with a much simpler and + * tighter interface, although it works almost exactly like the older one. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This new implementation is fully dedicated in memory of Cameron Grant, * + * the creator of the magnificent, highly addictive feeder infrastructure. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Orion's notes: + * ============= * * This rate conversion code uses linear interpolation without any * pre- or post- interpolation filtering to combat aliasing. This @@ -31,200 +60,352 @@ * stage in the future. * * Since this accuracy of interpolation is sensitive and examination - * of the algorithm output is harder from the kernel, th code is + * of the algorithm output is harder from the kernel, the code is * designed to be compiled in the kernel and in a userland test * harness. This is done by selectively including and excluding code * with several portions based on whether _KERNEL is defined. It's a * little ugly, but exceedingly useful. The testsuite and its * revisions can be found at: - * http://people.freebsd.org/~orion/feedrate/ + * http://people.freebsd.org/~orion/files/feedrate/ * * Special thanks to Ken Marx for exposing flaws in the code and for * testing revisions. */ -#ifdef _KERNEL - #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_rate.c,v 1.10.6.1 2005/01/30 01:00:05 imp Exp $"); -#endif /* _KERNEL */ +#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ +#define RATE_TEST(x, y) /* if (!(x)) printf y */ +#define RATE_TRACE(x...) /* printf(x) */ MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); -#ifndef RATE_ASSERT -#define RATE_ASSERT(x, y) /* KASSERT(x) */ -#endif /* RATE_ASSERT */ - -#ifndef RATE_TRACE -#define RATE_TRACE(x...) /* printf(x) */ -#endif - -/*****************************************************************************/ - -/* The following coefficients are coupled. They are chosen to be - * guarantee calculable factors for the interpolation routine. They - * have been tested over the range of RATEMIN-RATEMAX Hz. Decreasing - * the granularity increases the required buffer size and affects the - * gain values at different points in the space. These values were - * found by running the test program with -p (probe) and some trial - * and error. - * - * ROUNDHZ the granularity of sample rates (fits n*11025 and n*8000). - * FEEDBUFSZ the amount of buffer space. - * MINGAIN the minimum acceptable gain in coefficients search. +#define FEEDBUFSZ 65536 +#define RATEMIN 4000 +/* Be reasonable. Playtime is over. */ +#define RATEMAX 256000 + +/* + * Don't overflow 32bit integer, since everything is done + * within 32bit arithmetic. */ -#define ROUNDHZ 25 -#define FEEDBUFSZ 8192 -#define MINGAIN 92 +#define RATE_FACTOR_MIN 1 +#define RATE_FACTOR_MAX PCM_S24_MAX +#define RATE_FACTOR_SAFE(val) (!((val) < RATE_FACTOR_MIN || \ + (val) > RATE_FACTOR_MAX)) -#define RATEMIN 4000 -#define RATEMAX 48000 +#define RATE_BUFSZ_MIN 8 +#define RATE_BUFSZ_MAX 131072 struct feed_rate_info; -typedef int (*rate_convert_method)(struct feed_rate_info *, - uint32_t, uint32_t, int16_t *); +typedef uint32_t (*feed_rate_converter)(struct feed_rate_info *, uint8_t *, uint32_t); -static int -convert_stereo_up(struct feed_rate_info *info, - uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); +struct feed_rate_info { + uint32_t src, dst; /* rounded source / destination rates */ + uint32_t rsrc, rdst; /* original source / destination rates */ + uint32_t gx, gy; /* interpolation / decimation ratio */ + uint32_t alpha; /* interpolation distance */ + uint32_t pos, bpos; /* current sample / buffer positions */ + uint32_t bufsz; /* total buffer size limit */ + uint32_t bufsz_init; /* allocated buffer size */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample */ + uint32_t stray; /* stray bytes */ + uint8_t *buffer; + feed_rate_converter convert; +}; + +int feeder_rate_ratemin = RATEMIN; +int feeder_rate_ratemax = RATEMAX; +static int feeder_rate_buffersize = FEEDBUFSZ; +static int feeder_rate_round = 1; + +/* + * sysctls.. I love sysctls.. + */ +TUNABLE_INT("hw.snd.feeder_rate_ratemin", &feeder_rate_ratemin); +TUNABLE_INT("hw.snd.feeder_rate_ratemax", &feeder_rate_ratemin); +TUNABLE_INT("hw.snd.feeder_rate_buffersize", &feeder_rate_buffersize); +TUNABLE_INT("hw.snd.feeder_rate_round", &feeder_rate_round); static int -convert_stereo_down(struct feed_rate_info *info, - uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); +sysctl_hw_snd_feeder_rate_ratemin(SYSCTL_HANDLER_ARGS) +{ + int err, val; -struct feed_rate_info { - uint32_t src, dst; /* source and destination rates */ - uint16_t buffer_ticks; /* number of available samples in buffer */ - uint16_t buffer_pos; /* next available sample in buffer */ - uint16_t rounds; /* maximum number of cycle rounds w buffer */ - uint16_t alpha; /* interpolation distance */ - uint16_t sscale; /* src clock scale */ - uint16_t dscale; /* dst clock scale */ - uint16_t mscale; /* scale factor to avoid divide per sample */ - uint16_t mroll; /* roll to again avoid divide per sample */ - uint16_t channels; /* 1 = mono, 2 = stereo */ + val = feeder_rate_ratemin; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (RATE_FACTOR_SAFE(val) && val < feeder_rate_ratemax) + feeder_rate_ratemin = val; + else + err = EINVAL; + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemin, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemin, "I", + "minimum allowable rate"); - rate_convert_method convert; - int16_t buffer[FEEDBUFSZ]; -}; +static int +sysctl_hw_snd_feeder_rate_ratemax(SYSCTL_HANDLER_ARGS) +{ + int err, val; -#define bytes_per_sample 2 -#define src_ticks_per_cycle(info) (info->dscale * info->rounds) -#define dst_ticks_per_cycle(info) (info->sscale * info->rounds) -#define bytes_per_tick(info) (info->channels * bytes_per_sample) -#define src_bytes_per_cycle(info) \ - (src_ticks_per_cycle(info) * bytes_per_tick(info)) -#define dst_bytes_per_cycle(info) \ - (dst_ticks_per_cycle(info) * bytes_per_tick(info)) + val = feeder_rate_ratemax; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (RATE_FACTOR_SAFE(val) && val > feeder_rate_ratemin) + feeder_rate_ratemax = val; + else + err = EINVAL; + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemax, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemax, "I", + "maximum allowable rate"); -static uint32_t -gcd(uint32_t x, uint32_t y) +static int +sysctl_hw_snd_feeder_rate_buffersize(SYSCTL_HANDLER_ARGS) { - uint32_t w; + int err, val; + + val = feeder_rate_buffersize; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + /* + * Don't waste too much kernel space + */ + if (val < RATE_BUFSZ_MIN || val > RATE_BUFSZ_MAX) + err = EINVAL; + else + feeder_rate_buffersize = val; + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_buffersize, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_buffersize, "I", + "default circular buffer size"); + +static int +sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = feeder_rate_round; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (val == 0 || val == 1) + feeder_rate_round = val; + else + err = EINVAL; + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I", + "allow 25Hz rate-rounding to avoid large integer multiplication and division"); + +#define FEEDER_RATE_CONVERT(FMTBIT, RATE_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_convert_##SIGNS##FMTBIT##ENDIANS(struct feed_rate_info *info, \ + uint8_t *dst, uint32_t max) \ +{ \ + uint32_t ret, smpsz, bps, ch, pos, bpos, gx, gy, alpha, distance; \ + int32_t x, y; \ + int i; \ + uint8_t *src, *sx, *sy; \ + \ + ret = 0; \ + alpha = info->alpha; \ + gx = info->gx; \ + gy = info->gy; \ + pos = info->pos; \ + bpos = info->bpos; \ + src = info->buffer + pos; \ + ch = info->channels; \ + bps = info->bps; \ + smpsz = bps * ch; \ + for (;;) { \ + if (alpha < gx) { \ + alpha += gy; \ + pos += smpsz; \ + if (pos == bpos) \ + break; \ + src += smpsz; \ + } else { \ + alpha -= gx; \ + distance = (alpha << PCM_FXSHIFT) / gy; \ + sx = src - smpsz; \ + sy = src; \ + i = ch; \ + do { \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(sx); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(sy); \ + x = (((RATE_INTCAST)x * distance) + \ + ((RATE_INTCAST)y * ((1 << PCM_FXSHIFT) - \ + distance))) >> PCM_FXSHIFT; \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(dst, x); \ + dst += bps; \ + sx += bps; \ + sy += bps; \ + ret += bps; \ + } while (--i); \ + if (ret == max) \ + break; \ + } \ + } \ + info->alpha = alpha; \ + info->pos = pos; \ + return ret; \ +} + +FEEDER_RATE_CONVERT(8, int32_t, S, s, NE, ne) +FEEDER_RATE_CONVERT(16, int32_t, S, s, LE, le) +FEEDER_RATE_CONVERT(24, int32_t, S, s, LE, le) +FEEDER_RATE_CONVERT(32, intpcm_t, S, s, LE, le) +FEEDER_RATE_CONVERT(16, int32_t, S, s, BE, be) +FEEDER_RATE_CONVERT(24, int32_t, S, s, BE, be) +FEEDER_RATE_CONVERT(32, intpcm_t, S, s, BE, be) +/* unsigned */ +FEEDER_RATE_CONVERT(8, int32_t, U, u, NE, ne) +FEEDER_RATE_CONVERT(16, int32_t, U, u, LE, le) +FEEDER_RATE_CONVERT(24, int32_t, U, u, LE, le) +FEEDER_RATE_CONVERT(32, intpcm_t, U, u, LE, le) +FEEDER_RATE_CONVERT(16, int32_t, U, u, BE, be) +FEEDER_RATE_CONVERT(24, int32_t, U, u, BE, be) +FEEDER_RATE_CONVERT(32, intpcm_t, U, u, BE, be) + +static void +feed_speed_ratio(uint32_t src, uint32_t dst, uint32_t *gx, uint32_t *gy) +{ + uint32_t w, x = src, y = dst; + while (y != 0) { w = x % y; x = y; y = w; } - return x; + *gx = src / x; + *gy = dst / x; +} + +static void +feed_rate_reset(struct feed_rate_info *info) +{ + info->src = info->rsrc - (info->rsrc % (feeder_rate_round ? 25 : 1)); + info->dst = info->rdst - (info->rdst % (feeder_rate_round ? 25 : 1)); + info->gx = 1; + info->gy = 1; + info->alpha = 0; + info->channels = 2; + info->bps = 2; + info->convert = NULL; + info->bufsz = info->bufsz_init; + info->pos = 4; + info->bpos = 8; + info->stray = 0; } static int feed_rate_setup(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; - uint32_t mscale, mroll, l, r, g; - - /* Beat sample rates down by greatest common divisor */ - g = gcd(info->src, info->dst); - info->sscale = info->dst / g; - info->dscale = info->src / g; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_rate_converter convert; + } convtbl[] = { + { AFMT_S8, 1, PCM_8_BPS, feed_convert_s8ne }, + { AFMT_S16_LE, 1, PCM_16_BPS, feed_convert_s16le }, + { AFMT_S24_LE, 1, PCM_24_BPS, feed_convert_s24le }, + { AFMT_S32_LE, 1, PCM_32_BPS, feed_convert_s32le }, + { AFMT_S16_BE, 1, PCM_16_BPS, feed_convert_s16be }, + { AFMT_S24_BE, 1, PCM_24_BPS, feed_convert_s24be }, + { AFMT_S32_BE, 1, PCM_32_BPS, feed_convert_s32be }, + { AFMT_S8 | AFMT_STEREO, 2, PCM_8_BPS, feed_convert_s8ne }, + { AFMT_S16_LE | AFMT_STEREO, 2, PCM_16_BPS, feed_convert_s16le }, + { AFMT_S24_LE | AFMT_STEREO, 2, PCM_24_BPS, feed_convert_s24le }, + { AFMT_S32_LE | AFMT_STEREO, 2, PCM_32_BPS, feed_convert_s32le }, + { AFMT_S16_BE | AFMT_STEREO, 2, PCM_16_BPS, feed_convert_s16be }, + { AFMT_S24_BE | AFMT_STEREO, 2, PCM_24_BPS, feed_convert_s24be }, + { AFMT_S32_BE | AFMT_STEREO, 2, PCM_32_BPS, feed_convert_s32be }, + /* unsigned */ + { AFMT_U8, 1, PCM_8_BPS, feed_convert_u8ne }, + { AFMT_U16_LE, 1, PCM_16_BPS, feed_convert_u16le }, + { AFMT_U24_LE, 1, PCM_24_BPS, feed_convert_u24le }, + { AFMT_U32_LE, 1, PCM_32_BPS, feed_convert_u32le }, + { AFMT_U16_BE, 1, PCM_16_BPS, feed_convert_u16be }, + { AFMT_U24_BE, 1, PCM_24_BPS, feed_convert_u24be }, + { AFMT_U32_BE, 1, PCM_32_BPS, feed_convert_u32be }, + { AFMT_U8 | AFMT_STEREO, 2, PCM_8_BPS, feed_convert_u8ne }, + { AFMT_U16_LE | AFMT_STEREO, 2, PCM_16_BPS, feed_convert_u16le }, + { AFMT_U24_LE | AFMT_STEREO, 2, PCM_24_BPS, feed_convert_u24le }, + { AFMT_U32_LE | AFMT_STEREO, 2, PCM_32_BPS, feed_convert_u32le }, + { AFMT_U16_BE | AFMT_STEREO, 2, PCM_16_BPS, feed_convert_u16be }, + { AFMT_U24_BE | AFMT_STEREO, 2, PCM_24_BPS, feed_convert_u24be }, + { AFMT_U32_BE | AFMT_STEREO, 2, PCM_32_BPS, feed_convert_u32be }, + { 0, 0, 0, NULL }, + }; + uint32_t i; + + feed_rate_reset(info); + + if (info->src != info->dst) + feed_speed_ratio(info->src, info->dst, + &info->gx, &info->gy); - info->alpha = 0; - info->buffer_ticks = 0; - info->buffer_pos = 0; + if (!(RATE_FACTOR_SAFE(info->gx) && RATE_FACTOR_SAFE(info->gy))) + return -1; - /* Pick suitable conversion routine */ - if (info->src > info->dst) { - info->convert = convert_stereo_down; - } else { - info->convert = convert_stereo_up; + for (i = 0; i < sizeof(convtbl) / sizeof(*convtbl); i++) { + if (convtbl[i].format == 0) + return -1; + if (f->desc->out == convtbl[i].format) { + info->channels = convtbl[i].channels; + info->bps = convtbl[i].bps; + info->convert = convtbl[i].convert; + break; + } } /* - * Determine number of conversion rounds that will fit into - * buffer. NB Must set info->rounds to one before using - * src_ticks_per_cycle here since it used by src_ticks_per_cycle. - */ - info->rounds = 1; - r = (FEEDBUFSZ - bytes_per_tick(info)) / - (src_ticks_per_cycle(info) * bytes_per_tick(info)); - if (r == 0) { - RATE_TRACE("Insufficient buffer space for conversion %d -> %d " - "(%d < %d)\n", info->src, info->dst, FEEDBUFSZ, - src_ticks_per_cycle(info) * bytes_per_tick(info)); - return -1; - } - info->rounds = r; - - /* - * Find scale and roll combination that allows us to trade - * costly divide operations in the main loop for multiply-rolls. + * No need to interpolate/decimate, just do plain copy. */ - for (l = 96; l >= MINGAIN; l -= 3) { - for (mroll = 0; mroll < 16; mroll ++) { - mscale = (1 << mroll) / info->sscale; - - r = (mscale * info->sscale * 100) >> mroll; - if (r > l && r <= 100) { - info->mscale = mscale; - info->mroll = mroll; - RATE_TRACE("Converting %d to %d with " - "mscale = %d and mroll = %d " - "(gain = %d / 100)\n", - info->src, info->dst, - info->mscale, info->mroll, r); - return 0; - } - } - } - - RATE_TRACE("Failed to find a converter within %d%% gain for " - "%d to %d.\n", l, info->src, info->dst); + if (info->gx == info->gy) + info->convert = NULL; + + info->pos = info->bps * info->channels; + info->bpos = info->pos << 1; + info->bufsz -= info->bufsz % info->pos; + + RATE_TRACE("%s: %u (%u) -> %u (%u) [%u/%u] , " + "format=0x%08x, channels=%u, bufsz=%u\n", + __func__, info->src, info->rsrc, info->dst, info->rdst, + info->gx, info->gy, + f->desc->out, info->channels, + info->bufsz - (info->bpos >> 1)); - return -2; + return 0; } static int -feed_rate_set(struct pcm_feeder *f, int what, int value) +feed_rate_set(struct pcm_feeder *f, int what, int32_t value) { struct feed_rate_info *info = f->data; - int rvalue; - - if (value < RATEMIN || value > RATEMAX) { + + if (value < feeder_rate_ratemin || value > feeder_rate_ratemax) return -1; - } - - rvalue = (value / ROUNDHZ) * ROUNDHZ; - if (value - rvalue > ROUNDHZ / 2) { - rvalue += ROUNDHZ; - } - - switch(what) { + + switch (what) { case FEEDRATE_SRC: - info->src = rvalue; + info->rsrc = value; break; case FEEDRATE_DST: - info->dst = rvalue; + info->rdst = value; break; default: return -1; } - return feed_rate_setup(f); } @@ -233,11 +414,11 @@ { struct feed_rate_info *info = f->data; - switch(what) { + switch (what) { case FEEDRATE_SRC: - return info->src; + return info->rsrc; case FEEDRATE_DST: - return info->dst; + return info->rdst; default: return -1; } @@ -249,15 +430,26 @@ { struct feed_rate_info *info; + if (f->desc->out != f->desc->in) + return EINVAL; + info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info == NULL) return ENOMEM; - info->src = DSP_DEFAULT_SPEED; - info->dst = DSP_DEFAULT_SPEED; - info->channels = 2; - + /* + * bufsz_init = sample from last cycle + conversion space + */ + info->bufsz_init = 8 + feeder_rate_buffersize; + info->buffer = malloc(sizeof(*info->buffer) * info->bufsz_init, + M_RATEFEEDER, M_NOWAIT | M_ZERO); + if (info->buffer == NULL) { + free(info, M_RATEFEEDER); + return ENOMEM; + } + info->rsrc = DSP_DEFAULT_SPEED; + info->rdst = DSP_DEFAULT_SPEED; f->data = info; - return 0; + return feed_rate_setup(f); } static int @@ -266,6 +458,8 @@ struct feed_rate_info *info = f->data; if (info) { + if (info->buffer) + free(info->buffer, M_RATEFEEDER); free(info, M_RATEFEEDER); } f->data = NULL; @@ -273,210 +467,177 @@ } static int -convert_stereo_up(struct feed_rate_info *info, - uint32_t src_ticks, - uint32_t dst_ticks, - int16_t *dst) -{ - uint32_t max_dst_ticks; - int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o; - int16_t *src; - - sp = info->buffer_pos * 2; - se = sp + src_ticks * 2; - - src = info->buffer; - alpha = info->alpha * info->mscale; - dalpha = info->dscale * info->mscale; /* Alpha increment */ - malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ - mroll = info->mroll; +feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_rate_info *info = f->data; + uint32_t i, smpsz; + int32_t fetch, slot; - /* - * For efficiency the main conversion loop should only depend on - * one variable. We use the state to work out the maximum number - * of output samples that are available and eliminate the checking of - * sp from the loop. - */ - max_dst_ticks = src_ticks * info->dst / info->src - alpha / dalpha; - if (max_dst_ticks < dst_ticks) { - dst_ticks = max_dst_ticks; - } + if (info->convert == NULL) + return FEEDER_FEED(f->source, c, b, count, source); - dp = 0; - de = dst_ticks * 2; /* - * Unrolling this loop manually does not help much here because - * of the alpha, malpha comparison. + * This loop has been optimized to generalize both up / down + * sampling without causing missing samples or excessive buffer + * feeding. The tricky part is to calculate *precise* (slot) value + * needed for the entire conversion space since we are bound to + * return and fill up the buffer according to the requested 'count'. + * Too much feeding will cause the extra buffer stay within temporary + * circular buffer forever and always manifest itself as a truncated + * sound during end of playback / recording. Too few, and we end up + * with possible underruns and waste of cpu cycles. + * + * 'Stray' management exist to combat with possible unaligned + * buffering by the caller. */ - while (dp < de) { - o = malpha - alpha; - x = alpha * src[sp + 2] + o * src[sp]; - dst[dp++] = x >> mroll; - x = alpha * src[sp + 3] + o * src[sp + 1]; - dst[dp++] = x >> mroll; - alpha += dalpha; - if (alpha >= malpha) { - alpha -= malpha; - sp += 2; - } - } - RATE_ASSERT(sp <= se, ("%s: Source overrun\n", __func__)); - - info->buffer_pos = sp / info->channels; - info->alpha = alpha / info->mscale; - - return dp / info->channels; -} - -static int -convert_stereo_down(struct feed_rate_info *info, - uint32_t src_ticks, - uint32_t dst_ticks, - int16_t *dst) -{ - int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o, m, - mdalpha, mstep; - int16_t *src; - - sp = info->buffer_pos * 2; - se = sp + src_ticks * 2; - - src = info->buffer; - alpha = info->alpha * info->mscale; - dalpha = info->dscale * info->mscale; /* Alpha increment */ - malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ - mroll = info->mroll; - - dp = 0; - de = dst_ticks * 2; - - m = dalpha / malpha; - mstep = m * 2; - mdalpha = dalpha - m * malpha; - + smpsz = info->bps * info->channels; + RATE_TEST(count >= smpsz && (count % smpsz) == 0, + ("%s: Count size not sample integral (%d)\n", __func__, count)); + if (count < smpsz) + return 0; + count -= count % smpsz; /* - * TODO: eliminate sp or dp from this loop comparison for a few - * extra % performance. + * This slot count formula will stay here for the next million years + * to come. This is the key of our circular buffering precision. */ - while (sp < se && dp < de) { - o = malpha - alpha; - x = alpha * src[sp + 2] + o * src[sp]; - dst[dp++] = x >> mroll; - x = alpha * src[sp + 3] + o * src[sp + 1]; - dst[dp++] = x >> mroll; - - alpha += mdalpha; - sp += mstep; - if (alpha >= malpha) { - alpha -= malpha; - sp += 2; - } - } - - info->buffer_pos = sp / 2; - info->alpha = alpha / info->mscale; - - RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, - ("%s: Source overrun\n", __func__)); - - return dp / 2; -} - -static int -feed_rate(struct pcm_feeder *f, - struct pcm_channel *c, - uint8_t *b, - uint32_t count, - void *source) -{ - struct feed_rate_info *info = f->data; - - uint32_t done, s_ticks, d_ticks; - done = 0; - - RATE_ASSERT(info->channels == 2, - ("%s: channels (%d) != 2", __func__, info->channels)); - - while (done < count) { - /* Slurp in more data if input buffer is not full */ - while (info->buffer_ticks < src_ticks_per_cycle(info)) { - uint8_t *u8b; - int fetch; - fetch = src_bytes_per_cycle(info) - - info->buffer_ticks * bytes_per_tick(info); - u8b = (uint8_t*)info->buffer + - (info->buffer_ticks + 1) * - bytes_per_tick(info); - fetch = FEEDER_FEED(f->source, c, u8b, fetch, source); - RATE_ASSERT(fetch % bytes_per_tick(info) == 0, - ("%s: fetched unaligned bytes (%d)", - __func__, fetch)); - info->buffer_ticks += fetch / bytes_per_tick(info); - RATE_ASSERT(src_ticks_per_cycle(info) >= - info->buffer_ticks, - ("%s: buffer overfilled (%d > %d).", - __func__, info->buffer_ticks, - src_ticks_per_cycle(info))); - if (fetch == 0) + slot = (((info->gx * (count / smpsz)) + info->gy - info->alpha - 1) / info->gy) * smpsz; + RATE_TEST((slot % smpsz) == 0, ("%s: Slot count not sample integral (%d)\n", + __func__, slot)); + RATE_TEST(info->stray == 0, ("%s: [1] Stray bytes: %u\n", + __func__,info->stray)); + if (info->pos != smpsz && info->bpos - info->pos == smpsz && + info->bpos + slot > info->bufsz) { + /* + * Copy last unit sample and its previous to + * beginning of buffer. + */ + bcopy(info->buffer + info->pos - smpsz, info->buffer, + sizeof(*info->buffer) * (smpsz << 1)); + info->pos = smpsz; + info->bpos = smpsz << 1; + } + RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", + __func__, slot)); + i = 0; + for (;;) { + for (;;) { + fetch = info->bufsz - info->bpos; + fetch -= info->stray; + RATE_ASSERT(fetch >= 0, + ("%s: [1] Buffer overrun: %d > %d\n", + __func__, info->bpos, info->bufsz)); + if (slot < fetch) + fetch = slot; + if (fetch > 0) { + RATE_ASSERT((int32_t)(info->bpos - info->stray) >= 0 && + (info->bpos - info->stray) < info->bufsz, + ("%s: DANGER - BUFFER OVERRUN! bufsz=%d, pos=%d\n", __func__, + info->bufsz, info->bpos - info->stray)); + fetch = FEEDER_FEED(f->source, c, + info->buffer + info->bpos - info->stray, + fetch, source); + info->stray = 0; + if (fetch == 0) + break; + RATE_TEST((fetch % smpsz) == 0, + ("%s: Fetch size not sample integral (%d)\n", + __func__, fetch)); + info->stray += fetch % smpsz; + RATE_TEST(info->stray == 0, + ("%s: Stray bytes detected (%d)\n", + __func__, info->stray)); + fetch -= fetch % smpsz; + info->bpos += fetch; + slot -= fetch; + RATE_ASSERT(slot >= 0, + ("%s: Negative Slot: %d\n", __func__, + slot)); + if (slot == 0) + break; + if (info->bpos == info->bufsz) + break; + } else break; } - - /* Find amount of input buffer data that should be processed */ - d_ticks = (count - done) / bytes_per_tick(info); - s_ticks = info->buffer_ticks - info->buffer_pos; - if (info->buffer_ticks != src_ticks_per_cycle(info)) { - if (s_ticks > 8) - s_ticks -= 8; - else - s_ticks = 0; - } - - d_ticks = info->convert(info, s_ticks, d_ticks, - (int16_t*)(b + done)); - if (d_ticks == 0) + if (info->pos == info->bpos) { + RATE_TEST(info->pos == smpsz, + ("%s: EOF while in progress\n", __func__)); break; - done += d_ticks * bytes_per_tick(info); - - RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, - ("%s: buffer_ticks too big\n", __func__)); - RATE_ASSERT(info->buffer_ticks <= src_ticks_per_cycle(info), - ("too many ticks %d / %d\n", - info->buffer_ticks, src_ticks_per_cycle(info))); - RATE_TRACE("%s: ticks %5d / %d pos %d\n", __func__, - info->buffer_ticks, src_ticks_per_cycle(info), - info->buffer_pos); - - if (src_ticks_per_cycle(info) <= info->buffer_pos) { - /* End of cycle reached, copy last samples to start */ - uint8_t *u8b; - u8b = (uint8_t*)info->buffer; - bcopy(u8b + src_bytes_per_cycle(info), u8b, - bytes_per_tick(info)); - - RATE_ASSERT(info->alpha == 0, - ("%s: completed cycle with " - "alpha non-zero", __func__, info->alpha)); - - info->buffer_pos = 0; - info->buffer_ticks = 0; } + RATE_ASSERT(info->pos <= info->bpos, + ("%s: [2] Buffer overrun: %d > %d\n", __func__, + info->pos, info->bpos)); + RATE_ASSERT(info->pos < info->bpos, + ("%s: Zero buffer!\n", __func__)); + RATE_ASSERT(((info->bpos - info->pos) % smpsz) == 0, + ("%s: Buffer not sample integral (%d)\n", + __func__, info->bpos - info->pos)); + i += info->convert(info, b + i, count - i); + RATE_ASSERT(info->pos <= info->bpos, + ("%s: [3] Buffer overrun: %d > %d\n", + __func__, info->pos, info->bpos)); + if (info->pos == info->bpos) { + /* + * End of buffer cycle. Copy last unit sample + * to beginning of buffer so next cycle can + * interpolate using it. + */ + RATE_TEST(info->stray == 0, ("%s: [2] Stray bytes: %u\n", __func__, info->stray)); + bcopy(info->buffer + info->pos - smpsz, info->buffer, + sizeof(*info->buffer) * smpsz); + info->bpos = smpsz; + info->pos = smpsz; + } + if (i == count) + break; } - - RATE_ASSERT(count >= done, - ("%s: generated too many bytes of data (%d > %d).", - __func__, done, count)); - if (done != count) { - RATE_TRACE("Only did %d of %d\n", done, count); - } + RATE_TEST((slot == 0 && count == i) || + (slot > 0 && count > i && + info->pos == info->bpos && info->pos == smpsz), + ("%s: Inconsistent slot/count! " + "Count Expect: %u , Got: %u, Slot Left: %d\n", + __func__, count, i, slot)); + + RATE_TEST(info->stray == 0, ("%s: [3] Stray bytes: %u\n", __func__, info->stray)); - return done; + return i; } static struct pcm_feederdesc feeder_rate_desc[] = { + {FEEDER_RATE, AFMT_S8, AFMT_S8, 0}, + {FEEDER_RATE, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_RATE, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_RATE, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_RATE, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_RATE, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_RATE, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_RATE, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + /* unsigned */ + {FEEDER_RATE, AFMT_U8, AFMT_U8, 0}, + {FEEDER_RATE, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_RATE, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_RATE, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_RATE, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_RATE, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_RATE, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_RATE, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; + static kobj_method_t feeder_rate_methods[] = { KOBJMETHOD(feeder_init, feed_rate_init), KOBJMETHOD(feeder_free, feed_rate_free), @@ -485,5 +646,5 @@ KOBJMETHOD(feeder_feed, feed_rate), {0, 0} }; -FEEDER_DECLARE(feeder_rate, 2, NULL); +FEEDER_DECLARE(feeder_rate, 2, NULL); --- sys/dev/sound/pcm/feeder_volume.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/feeder_volume.c Sun Mar 19 22:56:06 2006 @@ -0,0 +1,220 @@ +/*- + * 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, 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. + * + * feeder_volume, a long 'Lost Technology' rather than a new feature. + */ + +#include +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_volume.c,v 1.2 2005/11/14 18:37:59 ariff Exp $"); + +MALLOC_DEFINE(M_VOLUMEFEEDER, "volumefeed", "pcm volume feeder"); + +#define FVOL_TRACE(x...) /* device_printf(c->dev, x) */ +#define FVOL_TEST(x, y...) /* if (x) FVOL_TRACE(y) */ + +#define FVOL_RESOLUTION 6 /* 6bit volume resolution */ +#define FVOL_CLAMP(val) (((val) << FVOL_RESOLUTION) / 100) +#define FVOL_LEFT(val) FVOL_CLAMP((val) & 0x7f) +#define FVOL_RIGHT(val) FVOL_LEFT((val) >> 8) +#define FVOL_MAX (1 << FVOL_RESOLUTION) +#define FVOL_CALC(sval, vval) (((sval) * (vval)) >> FVOL_RESOLUTION) + +struct feed_volume_info; + +typedef uint32_t (*feed_volume_filter)(struct feed_volume_info *, + uint8_t *, int *, uint32_t); + +struct feed_volume_info { + uint32_t bps, channels; + feed_volume_filter filter; +}; + +#define FEEDER_VOLUME_FILTER(FMTBIT, VOL_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_volume_filter_##SIGNS##FMTBIT##ENDIANS(struct feed_volume_info *info, \ + uint8_t *b, int *vol, uint32_t count) \ +{ \ + uint32_t bps; \ + int32_t j; \ + int i; \ + \ + bps = info->bps; \ + i = count; \ + b += i; \ + while (i > 0) { \ + b -= bps; \ + i -= bps; \ + j = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(b); \ + j = FVOL_CALC((VOL_INTCAST)j, vol[(i / bps) & 1]); \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(b, j); \ + } \ + return count; \ +} + +FEEDER_VOLUME_FILTER(8, int32_t, S, s, NE, ne) +FEEDER_VOLUME_FILTER(16, int32_t, S, s, LE, le) +FEEDER_VOLUME_FILTER(24, int32_t, S, s, LE, le) +FEEDER_VOLUME_FILTER(32, intpcm_t, S, s, LE, le) +FEEDER_VOLUME_FILTER(16, int32_t, S, s, BE, be) +FEEDER_VOLUME_FILTER(24, int32_t, S, s, BE, be) +FEEDER_VOLUME_FILTER(32, intpcm_t, S, s, BE, be) +/* unsigned */ +FEEDER_VOLUME_FILTER(8, int32_t, U, u, NE, ne) +FEEDER_VOLUME_FILTER(16, int32_t, U, u, LE, le) +FEEDER_VOLUME_FILTER(24, int32_t, U, u, LE, le) +FEEDER_VOLUME_FILTER(32, intpcm_t, U, u, LE, le) +FEEDER_VOLUME_FILTER(16, int32_t, U, u, BE, be) +FEEDER_VOLUME_FILTER(24, int32_t, U, u, BE, be) +FEEDER_VOLUME_FILTER(32, intpcm_t, U, u, BE, be) + +static int +feed_volume_setup(struct pcm_feeder *f) +{ + struct feed_volume_info *info = f->data; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_volume_filter filter; + } voltbl[] = { + { AFMT_S8 | AFMT_STEREO, 2, PCM_8_BPS, feed_volume_filter_s8ne }, + { AFMT_S16_LE | AFMT_STEREO, 2, PCM_16_BPS, feed_volume_filter_s16le }, + { AFMT_S24_LE | AFMT_STEREO, 2, PCM_24_BPS, feed_volume_filter_s24le }, + { AFMT_S32_LE | AFMT_STEREO, 2, PCM_32_BPS, feed_volume_filter_s32le }, + { AFMT_S16_BE | AFMT_STEREO, 2, PCM_16_BPS, feed_volume_filter_s16be }, + { AFMT_S24_BE | AFMT_STEREO, 2, PCM_24_BPS, feed_volume_filter_s24be }, + { AFMT_S32_BE | AFMT_STEREO, 2, PCM_32_BPS, feed_volume_filter_s32be }, + /* unsigned */ + { AFMT_U8 | AFMT_STEREO, 2, PCM_8_BPS, feed_volume_filter_u8ne }, + { AFMT_U16_LE | AFMT_STEREO, 2, PCM_16_BPS, feed_volume_filter_u16le }, + { AFMT_U24_LE | AFMT_STEREO, 2, PCM_24_BPS, feed_volume_filter_u24le }, + { AFMT_U32_LE | AFMT_STEREO, 2, PCM_32_BPS, feed_volume_filter_u32le }, + { AFMT_U16_BE | AFMT_STEREO, 2, PCM_16_BPS, feed_volume_filter_u16be }, + { AFMT_U24_BE | AFMT_STEREO, 2, PCM_24_BPS, feed_volume_filter_u24be }, + { AFMT_U32_BE | AFMT_STEREO, 2, PCM_32_BPS, feed_volume_filter_u32be }, + { 0, 0, 0, NULL }, + }; + uint32_t i; + + for (i = 0; i < sizeof(voltbl) / sizeof(*voltbl); i++) { + if (voltbl[i].format == 0) + return -1; + if (f->desc->out == voltbl[i].format) { + info->channels = voltbl[i].channels; + info->bps = voltbl[i].bps; + info->filter = voltbl[i].filter; + break; + } + } + + return 0; +} + +static int +feed_volume_init(struct pcm_feeder *f) +{ + struct feed_volume_info *info; + + if (f->desc->in != f->desc->out) + return EINVAL; + + /* Mandatory */ + if (!(f->desc->out & AFMT_STEREO)) + return EINVAL; + + info = malloc(sizeof(*info), M_VOLUMEFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + f->data = info; + return feed_volume_setup(f); +} + +static int +feed_volume_free(struct pcm_feeder *f) +{ + struct feed_volume_info *info = f->data; + + if (info) + free(info, M_VOLUMEFEEDER); + f->data = NULL; + return 0; +} + +static int +feed_volume(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_volume_info *info = f->data; + uint32_t k, smpsz; + int vol[2]; + + vol[0] = FVOL_LEFT(c->volume); + vol[1] = FVOL_RIGHT(c->volume); + + if (vol[0] == FVOL_MAX && vol[1] == FVOL_MAX) + return FEEDER_FEED(f->source, c, b, count, source); + + smpsz = info->bps * info->channels; + count -= count % smpsz; + k = FEEDER_FEED(f->source, c, b, count, source); + if (k < smpsz) { + FVOL_TRACE("%s: Not enough data (Got: %u bytes)\n", + __func__, k); + return 0; + } + FVOL_TEST(k % smpsz, "%s: Bytes not %dbit (stereo) aligned.\n", + __func__, info->bps << 3); + k -= k % smpsz; + return info->filter(info, b, vol, k); +} + +static struct pcm_feederdesc feeder_volume_desc[] = { + {FEEDER_VOLUME, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + /* unsigned */ + {FEEDER_VOLUME, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_volume_methods[] = { + KOBJMETHOD(feeder_init, feed_volume_init), + KOBJMETHOD(feeder_free, feed_volume_free), + KOBJMETHOD(feeder_feed, feed_volume), + {0, 0} +}; +FEEDER_DECLARE(feeder_volume, 2, NULL); --- sys/dev/sound/pcm/mixer.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/mixer.c Sun Mar 19 22:56:06 2006 @@ -41,6 +41,7 @@ int hwvol_muted; int hwvol_mixer; int hwvol_step; + device_t dev; u_int32_t hwvol_mute_level; u_int32_t devs; u_int32_t recdevs; @@ -60,6 +61,7 @@ [SOUND_MIXER_LINE] = 75, [SOUND_MIXER_MIC] = 0, [SOUND_MIXER_CD] = 75, + [SOUND_MIXER_IGAIN] = 0, [SOUND_MIXER_LINE1] = 75, [SOUND_MIXER_VIDEO] = 75, [SOUND_MIXER_RECLEV] = 0, @@ -74,7 +76,7 @@ static struct cdevsw mixer_cdevsw = { .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, + .d_flags = D_TRACKCLOSE | D_NEEDGIANT, .d_open = mixer_open, .d_close = mixer_close, .d_ioctl = mixer_ioctl, @@ -113,6 +115,7 @@ static int mixer_set(struct snd_mixer *mixer, unsigned dev, unsigned lev) { + struct snddev_info *d; unsigned l, r; int v; @@ -122,9 +125,34 @@ l = min((lev & 0x00ff), 100); r = min(((lev & 0xff00) >> 8), 100); - v = MIXER_SET(mixer, dev, l, r); - if (v < 0) - return -1; + d = device_get_softc(mixer->dev); + if (dev == SOUND_MIXER_PCM && d && + (d->flags & SD_F_SOFTVOL)) { + struct snddev_channel *sce; + struct pcm_channel *ch; +#ifdef USING_MUTEX + int locked = (mixer->lock && mtx_owned((struct mtx *)(mixer->lock))) ? 1 : 0; + + if (locked) + snd_mtxunlock(mixer->lock); +#endif + SLIST_FOREACH(sce, &d->channels, link) { + ch = sce->channel; + CHN_LOCK(ch); + if (ch->direction == PCMDIR_PLAY && + (ch->feederflags & (1 << FEEDER_VOLUME))) + chn_setvolume(ch, l, r); + CHN_UNLOCK(ch); + } +#ifdef USING_MUTEX + if (locked) + snd_mtxlock(mixer->lock); +#endif + } else { + v = MIXER_SET(mixer, dev, l, r); + if (v < 0) + return -1; + } mixer->level[dev] = l | (r << 8); return 0; @@ -157,6 +185,9 @@ void mix_setdevs(struct snd_mixer *m, u_int32_t v) { + struct snddev_info *d = device_get_softc(m->dev); + if (d && (d->flags & SD_F_SOFTVOL)) + v |= SOUND_MASK_PCM; m->devs = v; } @@ -199,6 +230,7 @@ m->type = cls->name; m->devinfo = devinfo; m->busy = 0; + m->dev = dev; if (MIXER_INIT(m)) goto bad; @@ -238,10 +270,14 @@ mixer_uninit(device_t dev) { int i; + struct snddev_info *d; struct snd_mixer *m; struct cdev *pdev; + d = device_get_softc(dev); pdev = mixer_get_devt(dev); + if (d == NULL || pdev == NULL || pdev->si_drv1 == NULL) + return EBADF; m = pdev->si_drv1; snd_mtxlock(m->lock); @@ -263,6 +299,8 @@ snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); + d->mixer_dev = NULL; + return 0; } @@ -302,7 +340,7 @@ m = oidp->oid_arg1; snd_mtxlock(m->lock); - strncpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); + strlcpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); @@ -398,16 +436,13 @@ mixer_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snd_mixer *m; - intrmask_t s; m = i_dev->si_drv1; - s = spltty(); snd_mtxlock(m->lock); m->busy++; snd_mtxunlock(m->lock); - splx(s); return 0; } @@ -415,21 +450,17 @@ mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snd_mixer *m; - intrmask_t s; m = i_dev->si_drv1; - s = spltty(); snd_mtxlock(m->lock); if (!m->busy) { snd_mtxunlock(m->lock); - splx(s); return EBADF; } m->busy--; snd_mtxunlock(m->lock); - splx(s); return 0; } @@ -437,23 +468,26 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct snd_mixer *m; - intrmask_t s; int ret, *arg_i = (int *)arg; int v = -1, j = cmd & 0xff; m = i_dev->si_drv1; - if (!m->busy) + + if (m == NULL) return EBADF; - s = spltty(); snd_mtxlock(m->lock); + if (mode != -1 && !m->busy) { + snd_mtxunlock(m->lock); + return EBADF; + } + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(m, *arg_i); else ret = mixer_set(m, j, *arg_i); snd_mtxunlock(m->lock); - splx(s); return (ret == 0)? 0 : ENXIO; } @@ -481,7 +515,6 @@ return (v != -1)? 0 : ENXIO; } snd_mtxunlock(m->lock); - splx(s); return ENXIO; } @@ -495,8 +528,10 @@ return; if (strcmp(name, "mixer") == 0) { sd = devclass_get_softc(pcm_devclass, snd_unit); - if (sd != NULL) + if (sd != NULL && sd->mixer_dev != NULL) { *dev = sd->mixer_dev; + dev_ref(*dev); + } } } --- sys/dev/sound/pcm/sndstat.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/sndstat.c Sun Mar 19 22:56:06 2006 @@ -85,20 +85,17 @@ static int sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) { - intrmask_t s; int error, verbose; verbose = sndstat_verbose; error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req); if (error == 0 && req->newptr != NULL) { - s = spltty(); sx_xlock(&sndstat_lock); if (verbose < 0 || verbose > 3) error = EINVAL; else sndstat_verbose = verbose; sx_xunlock(&sndstat_lock); - splx(s); } return error; } @@ -108,19 +105,15 @@ static int sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { - intrmask_t s; int error; - s = spltty(); sx_xlock(&sndstat_lock); if (sndstat_isopen) { sx_xunlock(&sndstat_lock); - splx(s); return EBUSY; } sndstat_isopen = 1; sx_xunlock(&sndstat_lock); - splx(s); if (sbuf_new(&sndstat_sbuf, NULL, 4096, 0) == NULL) { error = ENXIO; goto out; @@ -129,11 +122,9 @@ error = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM; out: if (error) { - s = spltty(); sx_xlock(&sndstat_lock); sndstat_isopen = 0; sx_xunlock(&sndstat_lock); - splx(s); } return (error); } @@ -141,34 +132,26 @@ static int sndstat_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { - intrmask_t s; - - s = spltty(); sx_xlock(&sndstat_lock); if (!sndstat_isopen) { sx_xunlock(&sndstat_lock); - splx(s); return EBADF; } sbuf_delete(&sndstat_sbuf); sndstat_isopen = 0; sx_xunlock(&sndstat_lock); - splx(s); return 0; } static int sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) { - intrmask_t s; int l, err; - s = spltty(); sx_xlock(&sndstat_lock); if (!sndstat_isopen) { sx_xunlock(&sndstat_lock); - splx(s); return EBADF; } l = min(buf->uio_resid, sbuf_len(&sndstat_sbuf) - sndstat_bufptr); @@ -176,7 +159,6 @@ sndstat_bufptr += l; sx_xunlock(&sndstat_lock); - splx(s); return err; } @@ -196,9 +178,34 @@ } int +sndstat_acquire(void) +{ + sx_xlock(&sndstat_lock); + if (sndstat_isopen) { + sx_xunlock(&sndstat_lock); + return EBUSY; + } + sndstat_isopen = 1; + sx_xunlock(&sndstat_lock); + return 0; +} + +int +sndstat_release(void) +{ + sx_xlock(&sndstat_lock); + if (!sndstat_isopen) { + sx_xunlock(&sndstat_lock); + return EBADF; + } + sndstat_isopen = 0; + sx_xunlock(&sndstat_lock); + return 0; +} + +int sndstat_register(device_t dev, char *str, sndstat_handler handler) { - intrmask_t s; struct sndstat_entry *ent; const char *devtype; int type, unit; @@ -229,14 +236,12 @@ ent->unit = unit; ent->handler = handler; - s = spltty(); sx_xlock(&sndstat_lock); SLIST_INSERT_HEAD(&sndstat_devlist, ent, link); if (type == SS_TYPE_MODULE) sndstat_files++; sndstat_maxunit = (unit > sndstat_maxunit)? unit : sndstat_maxunit; sx_xunlock(&sndstat_lock); - splx(s); return 0; } @@ -250,23 +255,19 @@ int sndstat_unregister(device_t dev) { - intrmask_t s; struct sndstat_entry *ent; - s = spltty(); sx_xlock(&sndstat_lock); SLIST_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == dev) { SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); sx_xunlock(&sndstat_lock); - splx(s); free(ent, M_DEVBUF); return 0; } } sx_xunlock(&sndstat_lock); - splx(s); return ENXIO; } @@ -274,24 +275,20 @@ int sndstat_unregisterfile(char *str) { - intrmask_t s; struct sndstat_entry *ent; - s = spltty(); sx_xlock(&sndstat_lock); SLIST_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == NULL && ent->str == str) { SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); sndstat_files--; sx_xunlock(&sndstat_lock); - splx(s); free(ent, M_DEVBUF); return 0; } } sx_xunlock(&sndstat_lock); - splx(s); return ENXIO; } @@ -304,7 +301,8 @@ struct sndstat_entry *ent; int i, j; - sbuf_printf(s, "FreeBSD Audio Driver (newpcm)\n"); + sbuf_printf(s, "FreeBSD Audio Driver (newpcm: %ubit)\n", + (unsigned int)sizeof(intpcm_t) << 3); if (SLIST_EMPTY(&sndstat_devlist)) { sbuf_printf(s, "No devices installed.\n"); sbuf_finish(s); @@ -354,13 +352,9 @@ static int sndstat_uninit(void) { - intrmask_t s; - - s = spltty(); sx_xlock(&sndstat_lock); if (sndstat_isopen) { sx_xunlock(&sndstat_lock); - splx(s); return EBUSY; } @@ -368,16 +362,9 @@ destroy_dev(sndstat_dev); sndstat_dev = 0; - splx(s); sx_xunlock(&sndstat_lock); sx_destroy(&sndstat_lock); return 0; -} - -int -sndstat_busy(void) -{ - return (sndstat_isopen); } static void --- sys/dev/sound/pcm/sound.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/sound.c Mon Mar 20 02:03:36 2006 @@ -26,6 +26,7 @@ */ #include +#include #include #include #include @@ -53,7 +54,7 @@ struct sysctl_ctx_list * snd_sysctl_tree(device_t dev) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); return &d->sysctl_tree; } @@ -61,7 +62,7 @@ struct sysctl_oid * snd_sysctl_tree_top(device_t dev) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); return d->sysctl_tree_top; } @@ -158,51 +159,161 @@ return d->fakechan; } -/* return a locked channel */ -struct pcm_channel * -pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum) +static int +pcm_setvchans(struct snddev_info *d, int newcnt) +{ + struct snddev_channel *sce = NULL; + struct pcm_channel *c = NULL; + int err = 0, vcnt, dcnt, i; + + pcm_inprog(d, 1); + + if (!(d->flags & SD_F_AUTOVCHAN)) { + err = EINVAL; + goto setvchans_out; + } + + vcnt = d->vchancount; + dcnt = d->playcount + d->reccount; + + if (newcnt < 0 || (dcnt + newcnt) > (PCMMAXCHAN + 1)) { + err = E2BIG; + goto setvchans_out; + } + + dcnt += vcnt; + + if (newcnt > vcnt) { + /* add new vchans - find a parent channel first */ + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + ((c->flags & CHN_F_HAS_VCHAN) || + (vcnt == 0 && + !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) + goto addok; + CHN_UNLOCK(c); + } + err = EBUSY; + goto setvchans_out; +addok: + c->flags |= CHN_F_BUSY; + while (err == 0 && newcnt > vcnt) { + if (dcnt > PCMMAXCHAN) { + device_printf(d->dev, "%s: Maximum channel reached.\n", __func__); + break; + } + err = vchan_create(c); + if (err == 0) { + vcnt++; + dcnt++; + } else if (err == E2BIG && newcnt > vcnt) + device_printf(d->dev, "%s: err=%d Maximum channel reached.\n", __func__, err); + } + if (vcnt == 0) + c->flags &= ~CHN_F_BUSY; + CHN_UNLOCK(c); + } else if (newcnt < vcnt) { +#define ORPHAN_CDEVT(cdevt) \ + ((cdevt) == NULL || ((cdevt)->si_drv1 == NULL && \ + (cdevt)->si_drv2 == NULL)) + while (err == 0 && newcnt < vcnt) { + i = 0; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->flags & CHN_F_VIRTUAL) && + (i++ == newcnt)) { + if (!(c->flags & CHN_F_BUSY) && + ORPHAN_CDEVT(sce->dsp_devt) && + ORPHAN_CDEVT(sce->dspW_devt) && + ORPHAN_CDEVT(sce->audio_devt) && + ORPHAN_CDEVT(sce->dspHW_devt)) + goto remok; + /* + * Either we're busy, or our cdev + * has been stolen by dsp_clone(). + * Skip, and increase newcnt. + */ + if (!(c->flags & CHN_F_BUSY)) + device_printf(d->dev, + "%s: <%s> somebody steal my cdev!\n", + __func__, c->name); + newcnt++; + } + CHN_UNLOCK(c); + } + if (vcnt != newcnt) + err = EBUSY; + break; +remok: + CHN_UNLOCK(c); + err = vchan_destroy(c); + if (err == 0) + vcnt--; + else + device_printf(d->dev, + "%s: WARNING: vchan_destroy() failed!", + __func__); + } + } + +setvchans_out: + pcm_inprog(d, -1); + return err; +} + +/* return error status and a locked channel */ +int +pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, + pid_t pid, int chnum) { struct pcm_channel *c; - struct snddev_channel *sce; + struct snddev_channel *sce; int err; - snd_mtxassert(d->lock); - +retry_chnalloc: + err = ENODEV; /* scan for a free channel */ SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); - if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) { - if (chnum == -1 || c->num == chnum) { + if (c->direction == direction && !(c->flags & CHN_F_BUSY)) { + if (chnum < 0 || sce->chan_num == chnum) { c->flags |= CHN_F_BUSY; c->pid = pid; - return c; + *ch = c; + return 0; } } + if (sce->chan_num == chnum) { + if (c->direction != direction) + err = EOPNOTSUPP; + else if (c->flags & CHN_F_BUSY) + err = EBUSY; + else + err = EINVAL; + CHN_UNLOCK(c); + return err; + } else if (c->direction == direction && (c->flags & CHN_F_BUSY)) + err = EBUSY; CHN_UNLOCK(c); } /* no channel available */ - if (direction == PCMDIR_PLAY) { - if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) { - /* try to create a vchan */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if (!SLIST_EMPTY(&c->children)) { - err = vchan_create(c); - CHN_UNLOCK(c); - if (!err) - return pcm_chnalloc(d, direction, pid, -1); - else - device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); - } else - CHN_UNLOCK(c); - } + if (chnum == -1 && direction == PCMDIR_PLAY && d->vchancount > 0 && + d->vchancount < snd_maxautovchans && + d->devcount <= PCMMAXCHAN) { + err = pcm_setvchans(d, d->vchancount + 1); + if (err == 0) { + chnum = -2; + goto retry_chnalloc; } } - return NULL; + return err; } /* release a locked channel and unlock it */ @@ -246,46 +357,10 @@ static void pcm_setmaxautovchans(struct snddev_info *d, int num) { - struct pcm_channel *c; - struct snddev_channel *sce; - int err, done; - - if (num > 0 && d->vchancount == 0) { - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) { - c->flags |= CHN_F_BUSY; - err = vchan_create(c); - if (err) { - c->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(c); - device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); - } else - CHN_UNLOCK(c); - return; - } - CHN_UNLOCK(c); - } - } - if (num == 0 && d->vchancount > 0) { - done = 0; - while (!done) { - done = 1; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if ((c->flags & CHN_F_VIRTUAL) && !(c->flags & CHN_F_BUSY)) { - done = 0; - snd_mtxlock(d->lock); - err = vchan_destroy(c); - snd_mtxunlock(d->lock); - if (err) - device_printf(d->dev, "vchan_destroy(%s) == %d\n", c->name, err); - break; /* restart */ - } - } - } - } + if (num > 0 && d->vchancount == 0) + pcm_setvchans(d, 1); + else if (num == 0 && d->vchancount > 0) + pcm_setvchans(d, 0); } #ifdef USING_DEVFS @@ -320,9 +395,9 @@ v = snd_maxautovchans; error = sysctl_handle_int(oidp, &v, sizeof(v), req); if (error == 0 && req->newptr != NULL) { - if (v < 0 || v >= SND_MAXVCHANS || pcm_devclass == NULL) - return EINVAL; - if (v != snd_maxautovchans) { + if (v < 0 || v > PCMMAXCHAN) + return E2BIG; + if (pcm_devclass != NULL && v != snd_maxautovchans) { for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!d) @@ -340,9 +415,11 @@ struct pcm_channel * pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) { - struct pcm_channel *ch; + struct snddev_channel *sce; + struct pcm_channel *ch, *c; char *dirs; - int direction, err, *pnum; + uint32_t flsearch = 0; + int direction, err, rpnum, *pnum; switch(dir) { case PCMDIR_PLAY: @@ -361,6 +438,7 @@ dirs = "virtual"; direction = PCMDIR_PLAY; pnum = &d->vchancount; + flsearch = CHN_F_VIRTUAL; break; default: @@ -379,14 +457,54 @@ } snd_mtxlock(d->lock); - ch->num = (*pnum)++; + ch->num = 0; + rpnum = 0; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if (direction != c->direction || + (c->flags & CHN_F_VIRTUAL) != flsearch) + continue; + if (ch->num == c->num) + ch->num++; + else { +#if 0 + device_printf(d->dev, + "%s: %s channel numbering screwed (Expect: %d, Got: %d)\n", + __func__, dirs, ch->num, c->num); +#endif + goto retry_num_search; + } + rpnum++; + } + goto retry_num_search_out; +retry_num_search: + rpnum = 0; + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + if (direction != c->direction || + (c->flags & CHN_F_VIRTUAL) != flsearch) + continue; + if (ch->num == c->num) { + ch->num++; + goto retry_num_search; + } + rpnum++; + } +retry_num_search_out: + if (*pnum != rpnum) { + device_printf(d->dev, + "%s: WARNING: pnum screwed : dirs=%s, pnum=%d, rpnum=%d\n", + __func__, dirs, *pnum, rpnum); + *pnum = rpnum; + } + (*pnum)++; snd_mtxunlock(d->lock); ch->pid = -1; ch->parentsnddev = d; ch->parentchannel = parent; ch->dev = d->dev; - snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); + snprintf(ch->name, CHN_NAMELEN, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); err = chn_init(ch, devinfo, dir, direction); if (err) { @@ -425,14 +543,17 @@ int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce, *tmp, *after; - int device = device_get_unit(d->dev); + struct snddev_channel *sce, *tmp, *after; + unsigned rdevcount; + int device = device_get_unit(d->dev); + size_t namelen; + char dtype; /* * Note it's confusing nomenclature. * dev_t * device -> pcm_device - * unit -> pcm_channel + * unit -> pcm_channel * channel -> snddev_channel * device_t * unit -> pcm_device @@ -445,37 +566,119 @@ snd_mtxlock(d->lock); sce->channel = ch; - sce->chan_num= d->devcount++; - if (SLIST_EMPTY(&d->channels)) { + sce->chan_num = 0; + rdevcount = 0; + after = NULL; + SLIST_FOREACH(tmp, &d->channels, link) { + if (sce->chan_num == tmp->chan_num) + sce->chan_num++; + else { +#if 0 + device_printf(d->dev, + "%s: cdev numbering screwed (Expect: %d, Got: %d)\n", + __func__, sce->chan_num, tmp->chan_num); +#endif + goto retry_chan_num_search; + } + after = tmp; + rdevcount++; + } + goto retry_chan_num_search_out; +retry_chan_num_search: + /* + * Look for possible channel numbering collision. This may not + * be optimized, but it will ensure that no collision occured. + * Can be considered cheap since none of the locking/unlocking + * operations involved. + */ + rdevcount = 0; + after = NULL; + SLIST_FOREACH(tmp, &d->channels, link) { + if (sce->chan_num == tmp->chan_num) { + sce->chan_num++; + goto retry_chan_num_search; + } + if (sce->chan_num > tmp->chan_num) + after = tmp; + rdevcount++; + } +retry_chan_num_search_out: + /* + * Don't overflow PCMMKMINOR / PCMMAXCHAN. + */ + if (sce->chan_num > PCMMAXCHAN) { + snd_mtxunlock(d->lock); + device_printf(d->dev, + "%s: WARNING: sce->chan_num overflow! (%d)\n", + __func__, sce->chan_num); + free(sce, M_DEVBUF); + return E2BIG; + } + if (d->devcount != rdevcount) { + device_printf(d->dev, + "%s: WARNING: devcount screwed! d->devcount=%u, rdevcount=%u\n", + __func__, d->devcount, rdevcount); + d->devcount = rdevcount; + } + d->devcount++; + if (after == NULL) { SLIST_INSERT_HEAD(&d->channels, sce, link); } else { - after = NULL; + SLIST_INSERT_AFTER(after, sce, link); + } +#if 0 + if (1) { + int cnum = 0; SLIST_FOREACH(tmp, &d->channels, link) { - after = tmp; + if (cnum != tmp->chan_num) + device_printf(d->dev, + "%s: WARNING: inconsistent cdev numbering! (Expect: %d, Got: %d)\n", + __func__, cnum, tmp->chan_num); + cnum++; } - SLIST_INSERT_AFTER(after, sce, link); + } +#endif + + if (ch->flags & CHN_F_VIRTUAL) + dtype = 'v'; + else if (ch->direction == PCMDIR_PLAY) + dtype = 'p'; + else if (ch->direction == PCMDIR_REC) + dtype = 'r'; + else + dtype = 'u'; /* we're screwed */ + + namelen = strlen(ch->name); + if ((CHN_NAMELEN - namelen) > 11) { /* ":dspXX.TYYY" */ + snprintf(ch->name + namelen, + CHN_NAMELEN - namelen, ":dsp%d.%c%d", + device, dtype, ch->num); } snd_mtxunlock(d->lock); - sce->dsp_devt= make_dev(&dsp_cdevsw, + + /* + * I will revisit these someday, and nuke it mercilessly.. + */ + sce->dsp_devt = make_dev(&dsp_cdevsw, PCMMKMINOR(device, SND_DEV_DSP, sce->chan_num), UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", device, sce->chan_num); - sce->dspW_devt= make_dev(&dsp_cdevsw, + sce->dspW_devt = make_dev(&dsp_cdevsw, PCMMKMINOR(device, SND_DEV_DSP16, sce->chan_num), UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", device, sce->chan_num); - sce->audio_devt= make_dev(&dsp_cdevsw, + sce->audio_devt = make_dev(&dsp_cdevsw, PCMMKMINOR(device, SND_DEV_AUDIO, sce->chan_num), UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", device, sce->chan_num); - if (ch->direction == PCMDIR_REC) - sce->dspr_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSPREC, - sce->chan_num), UID_ROOT, GID_WHEEL, - 0666, "dspr%d.%d", device, sce->chan_num); + /* Except this. */ + sce->dspHW_devt = make_dev(&dsp_cdevsw, + PCMMKMINOR(device, SND_DEV_DSPHW, sce->chan_num), + UID_ROOT, GID_WHEEL, 0666, "dsp%d.%c%d", + device, dtype, ch->num); return 0; } @@ -483,7 +686,7 @@ int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce; + struct snddev_channel *sce; #if 0 int ourlock; @@ -506,10 +709,10 @@ gotit: SLIST_REMOVE(&d->channels, sce, snddev_channel, link); - if (ch->direction == PCMDIR_REC) - d->reccount--; - else if (ch->flags & CHN_F_VIRTUAL) + if (ch->flags & CHN_F_VIRTUAL) d->vchancount--; + else if (ch->direction == PCMDIR_REC) + d->reccount--; else d->playcount--; @@ -525,9 +728,9 @@ int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; - int err; + int err; ch = pcm_chn_create(d, NULL, cls, dir, devinfo); if (!ch) { @@ -542,28 +745,14 @@ return err; } - CHN_LOCK(ch); - if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) && - ch->direction == PCMDIR_PLAY && d->vchancount == 0) { - ch->flags |= CHN_F_BUSY; - err = vchan_create(ch); - if (err) { - ch->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(ch); - device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err); - return err; - } - } - CHN_UNLOCK(ch); - return err; } static int pcm_killchan(device_t dev) { - struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; + struct snddev_info *d = device_get_softc(dev); + struct snddev_channel *sce; struct pcm_channel *ch; int error = 0; @@ -579,26 +768,28 @@ int pcm_setstatus(device_t dev, char *str) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); snd_mtxlock(d->lock); - strncpy(d->status, str, SND_STATUSLEN); + strlcpy(d->status, str, SND_STATUSLEN); snd_mtxunlock(d->lock); + if (snd_maxautovchans > 0) + pcm_setvchans(d, 1); return 0; } -u_int32_t +uint32_t pcm_getflags(device_t dev) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); return d->flags; } void -pcm_setflags(device_t dev, u_int32_t val) +pcm_setflags(device_t dev, uint32_t val) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); d->flags = val; } @@ -606,7 +797,7 @@ void * pcm_getdevinfo(device_t dev) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); return d->devinfo; } @@ -614,7 +805,7 @@ unsigned int pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); int sz, x; sz = 0; @@ -644,7 +835,7 @@ int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); @@ -654,7 +845,14 @@ d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); +#if 0 + /* + * d->flags should be cleared by the allocator of the softc. + * We cannot clear this field here because several devices set + * this flag before calling pcm_register(). + */ d->flags = 0; +#endif d->dev = dev; d->devinfo = devinfo; d->devcount = 0; @@ -664,9 +862,8 @@ d->inprog = 0; SLIST_INIT(&d->channels); - SLIST_INIT(&d->channels); - if (((numplay == 0) || (numrec == 0)) && (numplay != numrec)) + if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; d->fakechan = fkchan_setup(dev); @@ -684,13 +881,16 @@ SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); #endif - if (numplay > 0) - vchan_initsys(dev); - if (numplay == 1) + if (numplay > 0) { d->flags |= SD_F_AUTOVCHAN; + vchan_initsys(dev); + } + if ((d->flags & SD_F_AC97) && d->mixer_dev != NULL && + d->mixer_dev->si_drv1 != NULL) + ac97_initsys(dev); sndstat_register(dev, d->status, sndstat_prepare_pcm); - return 0; + return 0; no: snd_mtxfree(d->lock); return ENXIO; @@ -699,59 +899,106 @@ int pcm_unregister(device_t dev) { - struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; + struct snddev_info *d = device_get_softc(dev); + struct snddev_channel *sce; + struct pcmchan_children *pce; struct pcm_channel *ch; + if (sndstat_acquire() != 0) { + device_printf(dev, "unregister: sndstat busy\n"); + return EBUSY; + } + snd_mtxlock(d->lock); if (d->inprog) { device_printf(dev, "unregister: operation in progress\n"); snd_mtxunlock(d->lock); + sndstat_release(); return EBUSY; } - if (sndstat_busy() != 0) { - device_printf(dev, "unregister: sndstat busy\n"); - snd_mtxunlock(d->lock); - return EBUSY; - } - SLIST_FOREACH(sce, &d->channels, link) { ch = sce->channel; if (ch->refcount > 0) { device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); snd_mtxunlock(d->lock); + sndstat_release(); return EBUSY; } } - SLIST_FOREACH(sce, &d->channels, link) { - destroy_dev(sce->dsp_devt); - destroy_dev(sce->dspW_devt); - destroy_dev(sce->audio_devt); - if (sce->dspr_devt) - destroy_dev(sce->dspr_devt); - } - - if (mixer_uninit(dev)) { + if (mixer_uninit(dev) == EBUSY) { device_printf(dev, "unregister: mixer busy\n"); snd_mtxunlock(d->lock); + sndstat_release(); return EBUSY; } + SLIST_FOREACH(sce, &d->channels, link) { + if (sce->dsp_devt) { + destroy_dev(sce->dsp_devt); + sce->dsp_devt = NULL; + } + if (sce->dspW_devt) { + destroy_dev(sce->dspW_devt); + sce->dspW_devt = NULL; + } + if (sce->audio_devt) { + destroy_dev(sce->audio_devt); + sce->audio_devt = NULL; + } + if (sce->dspHW_devt) { + destroy_dev(sce->dspHW_devt); + sce->dspHW_devt = NULL; + } + d->devcount--; + ch = sce->channel; + if (ch == NULL) + continue; + pce = SLIST_FIRST(&ch->children); + while (pce != NULL) { +#if 0 + device_printf(d->dev, "<%s> removing <%s>\n", + ch->name, (pce->channel != NULL) ? + pce->channel->name : "unknown"); +#endif + SLIST_REMOVE(&ch->children, pce, pcmchan_children, link); + free(pce, M_DEVBUF); + pce = SLIST_FIRST(&ch->children); + } + } + #ifdef SND_DYNSYSCTL d->sysctl_tree_top = NULL; sysctl_ctx_free(&d->sysctl_tree); #endif + +#if 0 + SLIST_FOREACH(sce, &d->channels, link) { + ch = sce->channel; + if (ch == NULL) + continue; + if (!SLIST_EMPTY(&ch->children)) + device_printf(d->dev, "%s: WARNING: <%s> dangling child!\n", + __func__, ch->name); + } +#endif while (!SLIST_EMPTY(&d->channels)) pcm_killchan(dev); chn_kill(d->fakechan); fkchan_kill(d->fakechan); - sndstat_unregister(dev); +#if 0 + device_printf(d->dev, "%s: devcount=%u, playcount=%u, " + "reccount=%u, vchancount=%u\n", + __func__, d->devcount, d->playcount, d->reccount, + d->vchancount); +#endif snd_mtxunlock(d->lock); snd_mtxfree(d->lock); + sndstat_unregister(dev); + sndstat_release(); return 0; } @@ -760,11 +1007,11 @@ static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) { - struct snddev_info *d; - struct snddev_channel *sce; + struct snddev_info *d; + struct snddev_channel *sce; struct pcm_channel *c; struct pcm_feeder *f; - int pc, rc, vc; + int pc, rc, vc; if (verbose < 1) return 0; @@ -802,6 +1049,10 @@ SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; + + KASSERT(c->bufhard != NULL && c->bufsoft != NULL, + ("hosed pcm channel setup")); + sbuf_printf(s, "\n\t"); /* it would be better to indent child channels */ @@ -817,16 +1068,22 @@ sbuf_printf(s, ", pid %d", c->pid); sbuf_printf(s, "\n\t"); - if (c->bufhard != NULL && c->bufsoft != NULL) { - sbuf_printf(s, "interrupts %d, ", c->interrupts); - if (c->direction == PCMDIR_REC) - sbuf_printf(s, "overruns %d, hfree %d, sfree %d", - c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft)); - else - sbuf_printf(s, "underruns %d, ready %d", - c->xruns, sndbuf_getready(c->bufsoft)); - sbuf_printf(s, "\n\t"); - } + sbuf_printf(s, "interrupts %d, ", c->interrupts); + if (c->direction == PCMDIR_REC) + sbuf_printf(s, "overruns %d, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, 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)); + else + sbuf_printf(s, "underruns %d, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, 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)); + sbuf_printf(s, "\n\t"); sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); sbuf_printf(s, " -> "); @@ -839,7 +1096,8 @@ sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); if (f->desc->type == FEEDER_RATE) sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); - if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER) + if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER || + f->desc->type == FEEDER_VOLUME) sbuf_printf(s, "(0x%08x)", f->desc->out); sbuf_printf(s, " -> "); f = f->parent; @@ -860,127 +1118,31 @@ sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c; - int err, newcnt, cnt, busy; - int x; + int err, newcnt; d = oidp->oid_arg1; - x = pcm_inprog(d, 1); - if (x != 1) { - pcm_inprog(d, -1); - return EINPROGRESS; - } - - busy = 0; - cnt = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) { - cnt++; - if (c->flags & CHN_F_BUSY) - busy++; - } - CHN_UNLOCK(c); - } - - newcnt = cnt; - + newcnt = d->vchancount; err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); - if (err == 0 && req->newptr != NULL) { - - if (newcnt < 0 || newcnt > SND_MAXVCHANS) { - pcm_inprog(d, -1); - return E2BIG; - } - - if (newcnt > cnt) { - /* add new vchans - find a parent channel first */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - /* not a candidate if not a play channel */ - if (c->direction != PCMDIR_PLAY) - goto next; - /* not a candidate if a virtual channel */ - if (c->flags & CHN_F_VIRTUAL) - goto next; - /* not a candidate if it's in use */ - if (!(c->flags & CHN_F_BUSY) || - !(SLIST_EMPTY(&c->children))) - /* - * if we get here we're a nonvirtual - * play channel, and either - * 1) not busy - * 2) busy with children, not directly - * open - * - * thus we can add children - */ - goto addok; -next: - CHN_UNLOCK(c); - } - pcm_inprog(d, -1); - return EBUSY; -addok: - c->flags |= CHN_F_BUSY; - while (err == 0 && newcnt > cnt) { - err = vchan_create(c); - if (err == 0) - cnt++; - } - if (SLIST_EMPTY(&c->children)) - c->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(c); - } else if (newcnt < cnt) { - if (busy > newcnt) { - printf("cnt %d, newcnt %d, busy %d\n", cnt, newcnt, busy); - pcm_inprog(d, -1); - return EBUSY; - } - - snd_mtxlock(d->lock); - while (err == 0 && newcnt < cnt) { - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL) - goto remok; + if (err == 0 && req->newptr != NULL && d->vchancount != newcnt) + err = pcm_setvchans(d, newcnt); - CHN_UNLOCK(c); - } - snd_mtxunlock(d->lock); - pcm_inprog(d, -1); - return EINVAL; -remok: - CHN_UNLOCK(c); - err = vchan_destroy(c); - if (err == 0) - cnt--; - } - snd_mtxunlock(d->lock); - } - } - pcm_inprog(d, -1); return err; } #endif /************************************************************************/ -#if notyet static int sound_modevent(module_t mod, int type, void *data) { +#if 0 return (midi_modevent(mod, type, data)); +#else + return 0; +#endif } DEV_MODULE(sound, sound_modevent, NULL); -#else -DEV_MODULE(sound, NULL, NULL); -#endif /* notyet */ MODULE_VERSION(sound, SOUND_MODVER); --- sys/dev/sound/pcm/sound.h.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/sound.h Sun Mar 19 22:49:56 2006 @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -123,14 +124,20 @@ [etc.] */ -#define PCMMINOR(x) (minor(x)) -#define PCMCHAN(x) ((PCMMINOR(x) & 0x00ff0000) >> 16) -#define PCMUNIT(x) ((PCMMINOR(x) & 0x000000f0) >> 4) -#define PCMDEV(x) (PCMMINOR(x) & 0x0000000f) -#define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) +#define PCMMAXCHAN 0xff +#define PCMMAXDEV 0x0f +#define PCMMAXUNIT 0x0f +#define PCMMINOR(x) (minor(x)) +#define PCMCHAN(x) ((PCMMINOR(x) >> 16) & PCMMAXCHAN) +#define PCMUNIT(x) ((PCMMINOR(x) >> 4) & PCMMAXUNIT) +#define PCMDEV(x) (PCMMINOR(x) & PCMMAXDEV) +#define PCMMKMINOR(u, d, c) ((((c) & PCMMAXCHAN) << 16) | \ + (((u) & PCMMAXUNIT) << 4) | ((d) & PCMMAXDEV)) #define SD_F_SIMPLEX 0x00000001 -#define SD_F_AUTOVCHAN 0x00000002 +#define SD_F_AUTOVCHAN 0x00000002 +#define SD_F_SOFTVOL 0x00000004 +#define SD_F_AC97 0x00000008 #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) @@ -142,22 +149,301 @@ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) +/* + * Macros for reading/writing PCM sample / int values from bytes array. + * Since every process is done using signed integer (and to make our life + * less miserable), unsigned sample will be converted to its signed + * counterpart and restored during writing back. To avoid overflow, + * we truncate 32bit (and only 32bit) samples down to 24bit (see below + * for the reason), unless PCM_USE_64BIT_ARITH is defined. + */ + +/* + * Automatically turn on 64bit arithmetic on suitable archs + * (amd64 64bit, ia64, etc..) for wider 32bit samples / integer processing. + */ +#if LONG_BIT >= 64 +#undef PCM_USE_64BIT_ARITH +#define PCM_USE_64BIT_ARITH 1 +#else +#if 0 +#undef PCM_USE_64BIT_ARITH +#define PCM_USE_64BIT_ARITH 1 +#endif +#endif + +#ifdef PCM_USE_64BIT_ARITH +typedef int64_t intpcm_t; +#else +typedef int32_t intpcm_t; +#endif + +/* 32bit fixed point shift */ +#define PCM_FXSHIFT 8 + +#define PCM_S8_MAX 0x7f +#define PCM_S8_MIN -0x80 +#define PCM_S16_MAX 0x7fff +#define PCM_S16_MIN -0x8000 +#define PCM_S24_MAX 0x7fffff +#define PCM_S24_MIN -0x800000 +#ifdef PCM_USE_64BIT_ARITH +#if LONG_BIT >= 64 +#define PCM_S32_MAX 0x7fffffffL +#define PCM_S32_MIN -0x80000000L +#else +#define PCM_S32_MAX 0x7fffffffLL +#define PCM_S32_MIN -0x80000000LL +#endif +#else +#define PCM_S32_MAX 0x7fffffff +#define PCM_S32_MIN (-0x7fffffff - 1) +#endif + +/* Bytes-per-sample definition */ +#define PCM_8_BPS 1 +#define PCM_16_BPS 2 +#define PCM_24_BPS 3 +#define PCM_32_BPS 4 + +#if BYTE_ORDER == LITTLE_ENDIAN +#define PCM_READ_S16_LE(b8) *((int16_t *)(b8)) +#define _PCM_READ_S32_LE(b8) *((int32_t *)(b8)) +#define PCM_READ_S16_BE(b8) \ + ((int32_t)((b8)[1] | ((int8_t)((b8)[0])) << 8)) +#define _PCM_READ_S32_BE(b8) \ + ((int32_t)((b8)[3] | (b8)[2] << 8 | (b8)[1] << 16 | \ + ((int8_t)((b8)[0])) << 24)) + +#define PCM_WRITE_S16_LE(b8, val) *((int16_t *)(b8)) = (val) +#define _PCM_WRITE_S32_LE(b8, val) *((int32_t *)(b8)) = (val) +#define PCM_WRITE_S16_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = val >> 8; \ + } while(0) +#define _PCM_WRITE_S32_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = val >> 24; \ + } while(0) + +#define PCM_READ_U16_LE(b8) ((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_LE(b8) ((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) +#define PCM_READ_U16_BE(b8) \ + ((int32_t)((b8)[1] | ((int8_t)((b8)[0] ^ 0x80)) << 8)) +#define _PCM_READ_U32_BE(b8) \ + ((int32_t)((b8)[3] | (b8)[2] << 8 | (b8)[1] << 16 | \ + ((int8_t)((b8)[0] ^ 0x80)) << 24)) + +#define PCM_WRITE_U16_LE(b8, val) *((uint16_t *)(b8)) = (val) ^ 0x8000 +#define _PCM_WRITE_U32_LE(b8, val) *((uint32_t *)(b8)) = (val) ^ 0x80000000 +#define PCM_WRITE_U16_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = (val >> 8) ^ 0x80; \ + } while(0) +#define _PCM_WRITE_U32_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = (val >> 24) ^ 0x80; \ + } while(0) +#else /* !LITTLE_ENDIAN */ +#define PCM_READ_S16_LE(b8) \ + ((int32_t)((b8)[0] | ((int8_t)((b8)[1])) << 8)) +#define _PCM_READ_S32_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | (b8)[2] << 16 | \ + ((int8_t)((b8)[3])) << 24)) +#define PCM_READ_S16_BE(b8) *((int16_t *)(b8)) +#define _PCM_READ_S32_BE(b8) *((int32_t *)(b8)) + +#define PCM_WRITE_S16_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + } while(0) +#define _PCM_WRITE_S32_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = val >> 24; \ + } while(0) +#define PCM_WRITE_S16_BE(b8, val) *((int16_t *)(b8)) = (val) +#define _PCM_WRITE_S32_BE(b8, val) *((int32_t *)(b8)) = (val) + +#define PCM_READ_U16_LE(b8) \ + ((int32_t)((b8)[0] | ((int8_t)((b8)[1] ^ 0x80)) << 8)) +#define _PCM_READ_U32_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | (b8)[2] << 16 | \ + ((int8_t)((b8)[3] ^ 0x80)) << 24)) +#define PCM_READ_U16_BE(b8) ((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_BE(b8) ((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) + +#define PCM_WRITE_U16_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = (val >> 8) ^ 0x80; \ + } while(0) +#define _PCM_WRITE_U32_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = (val >> 24) ^ 0x80; \ + } while(0) +#define PCM_WRITE_U16_BE(b8, val) *((uint16_t *)(b8)) = (val) ^ 0x8000 +#define _PCM_WRITE_U32_BE(b8, val) *((uint32_t *)(b8)) = (val) ^ 0x80000000 +#endif + +#define PCM_READ_S24_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | ((int8_t)((b8)[2])) << 16)) +#define PCM_READ_S24_BE(b8) \ + ((int32_t)((b8)[2] | (b8)[1] << 8 | ((int8_t)((b8)[0])) << 16)) + +#define PCM_WRITE_S24_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + } while(0) +#define PCM_WRITE_S24_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = val >> 16; \ + } while(0) + +#define PCM_READ_U24_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | \ + ((int8_t)((b8)[2] ^ 0x80)) << 16)) +#define PCM_READ_U24_BE(b8) \ + ((int32_t)((b8)[2] | (b8)[1] << 8 | \ + ((int8_t)((b8)[0] ^ 0x80)) << 16)) + +#define PCM_WRITE_U24_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = (val >> 16) ^ 0x80; \ + } while(0) +#define PCM_WRITE_U24_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = (val >> 16) ^ 0x80; \ + } while(0) + +#ifdef PCM_USE_64BIT_ARITH +#define PCM_READ_S32_LE(b8) _PCM_READ_S32_LE(b8) +#define PCM_READ_S32_BE(b8) _PCM_READ_S32_BE(b8) +#define PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) +#define PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) + +#define PCM_READ_U32_LE(b8) _PCM_READ_U32_LE(b8) +#define PCM_READ_U32_BE(b8) _PCM_READ_U32_BE(b8) +#define PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) +#define PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) +#else /* !PCM_USE_64BIT_ARITH */ +/* + * 24bit integer ?!? This is quite unfortunate, eh? Get the fact straight: + * Dynamic range for: + * 1) Human =~ 140db + * 2) 16bit = 96db (close enough) + * 3) 24bit = 144db (perfect) + * 4) 32bit = 196db (way too much) + * 5) Bugs Bunny = Gazillion!@%$Erbzzztt-EINVAL db + * Since we're not Bugs Bunny ..uh..err.. avoiding 64bit arithmetic, 24bit + * is pretty much sufficient for our signed integer processing. + */ +#define PCM_READ_S32_LE(b8) (_PCM_READ_S32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_S32_BE(b8) (_PCM_READ_S32_BE(b8) >> PCM_FXSHIFT) +#define PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, (val) << PCM_FXSHIFT) + +#define PCM_READ_U32_LE(b8) (_PCM_READ_U32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_U32_BE(b8) (_PCM_READ_U32_BE(b8) >> PCM_FXSHIFT) +#define PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, (val) << PCM_FXSHIFT) +#endif + +/* + * 8bit sample is pretty much useless since it doesn't provide + * sufficient dynamic range throughout our filtering process. + * For the sake of completeness, declare it anyway. + */ +#define PCM_READ_S8(b8) *((int8_t *)(b8)) +#define PCM_READ_S8_NE(b8) PCM_READ_S8(b8) +#define PCM_READ_U8(b8) ((int8_t)(*((uint8_t *)(b8)) ^ 0x80)) +#define PCM_READ_U8_NE(b8) PCM_READ_U8(b8) + +#define PCM_WRITE_S8(b8, val) *((int8_t *)(b8)) = (val) +#define PCM_WRITE_S8_NE(b8, val) PCM_WRITE_S8(b8, val) +#define PCM_WRITE_U8(b8, val) *((uint8_t *)(b8)) = (val) ^ 0x80 +#define PCM_WRITE_U8_NE(b8, val) PCM_WRITE_U8(b8, val) + +#define PCM_CLAMP_S8(val) \ + (((val) > PCM_S8_MAX) ? PCM_S8_MAX : \ + (((val) < PCM_S8_MIN) ? PCM_S8_MIN : (val))) +#define PCM_CLAMP_S16(val) \ + (((val) > PCM_S16_MAX) ? PCM_S16_MAX : \ + (((val) < PCM_S16_MIN) ? PCM_S16_MIN : (val))) +#define PCM_CLAMP_S24(val) \ + (((val) > PCM_S24_MAX) ? PCM_S24_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S24_MIN : (val))) + +#ifdef PCM_USE_64BIT_ARITH +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S32_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S32_MIN) ? PCM_S32_MIN : (val))) +#else +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S24_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S32_MIN : \ + ((val) << PCM_FXSHIFT))) +#endif + +#define PCM_CLAMP_U8(val) PCM_CLAMP_S8(val) +#define PCM_CLAMP_U16(val) PCM_CLAMP_S16(val) +#define PCM_CLAMP_U24(val) PCM_CLAMP_S24(val) +#define PCM_CLAMP_U32(val) PCM_CLAMP_S32(val) + /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) +#define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) -#define AFMT_8BIT (AFMT_U8 | AFMT_S8) -#define AFMT_SIGNED (AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) -#define AFMT_BIGENDIAN (AFMT_S16_BE | AFMT_U16_BE) +#define AFMT_8BIT (AFMT_MU_LAW | AFMT_A_LAW | AFMT_U8 | AFMT_S8) +#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ + AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) +#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ + AFMT_S16_BE | AFMT_U16_BE) struct pcm_channel *fkchan_setup(device_t dev); int fkchan_kill(struct pcm_channel *c); /* - * Major nuber for the sound driver. + * Major number for the sound driver. */ #define SND_CDEV_MAJOR 30 -#define SND_MAXVCHANS 255 +/* XXX Flawed definition. I'll fix it someday. */ +#define SND_MAXVCHANS PCMMAXCHAN /* * Minor numbers for the sound driver. @@ -180,7 +466,7 @@ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 -#define SND_DEV_DSPREC 11 /* recording channels */ +#define SND_DEV_DSPHW 11 /* specific channel request */ #define DSP_DEFAULT_SPEED 8000 @@ -208,7 +494,7 @@ struct sysctl_oid *snd_sysctl_tree_top(device_t dev); struct pcm_channel *pcm_getfakechan(struct snddev_info *d); -struct pcm_channel *pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum); +int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, int chnum); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); @@ -240,11 +526,12 @@ int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS); typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); +int sndstat_acquire(void); +int sndstat_release(void); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); int sndstat_unregisterfile(char *str); -int sndstat_busy(void); #define SND_DECLARE_FILE(version) \ _SND_DECLARE_FILE(__LINE__, version) @@ -280,7 +567,7 @@ struct cdev *dsp_devt; struct cdev *dspW_devt; struct cdev *audio_devt; - struct cdev *dspr_devt; + struct cdev *dspHW_devt; }; struct snddev_info { --- sys/dev/sound/pcm/vchan.c.orig Wed Nov 2 01:47:29 2005 +++ sys/dev/sound/pcm/vchan.c Sun Mar 19 22:56:06 2006 @@ -1,5 +1,6 @@ /*- - * Copyright (c) 2001 Cameron Grant + * Copyright (c) 2001 Cameron Grant + * Copyright (c) 2006 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,6 +23,9 @@ * 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. + * */ #include @@ -30,58 +34,228 @@ SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/vchan.c,v 1.16.2.1 2005/01/30 01:00:05 imp Exp $"); +MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); + +/* + * Default speed / format + */ +#define VCHAN_DEFAULT_SPEED 48000 +#define VCHAN_DEFAULT_AFMT (AFMT_S16_LE | AFMT_STEREO) +#define VCHAN_DEFAULT_STRFMT "s16le" + +extern int feeder_rate_ratemin; +extern int feeder_rate_ratemax; + +struct feed_vchan_info; + +typedef uint32_t (*feed_vchan_mixer)(struct feed_vchan_info *, + uint8_t *, uint8_t *, uint32_t); + +struct feed_vchan_info { + uint32_t bps, channels, zero_sample; + feed_vchan_mixer mix; +}; + struct vchinfo { - u_int32_t spd, fmt, blksz, bps, run; + uint32_t spd, fmt, fmts[2], blksz, bps, run; struct pcm_channel *channel, *parent; struct pcmchan_caps caps; }; -static u_int32_t vchan_fmt[] = { - AFMT_STEREO | AFMT_S16_LE, - 0 +/* support everything (mono / stereo), except a-law / mu-law */ +static struct afmtstr_table vchan_supported_fmts[] = { + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +/* alias table, shorter. */ +static const struct { + char *alias, *fmtstr; +} vchan_fmtstralias[] = { + { "8", "u8" }, { "16", "s16le" }, + { "24", "s24le" }, { "32", "s32le" }, + { NULL, NULL }, }; +#define vchan_valid_format(fmt) \ + afmt2afmtstr(vchan_supported_fmts, fmt, NULL, 0, 0, \ + AFMTSTR_STEREO_RETURN) +#define vchan_valid_strformat(strfmt) \ + afmtstr2afmt(vchan_supported_fmts, strfmt, AFMTSTR_STEREO_RETURN); + +/* + * Need specialized WRITE macros since 32bit might involved saturation + * if calculation is done within 32bit arithmetic. + */ +#define VCHAN_PCM_WRITE_S8_NE(b8, val) PCM_WRITE_S8(b8, val) +#define VCHAN_PCM_WRITE_S16_LE(b8, val) PCM_WRITE_S16_LE(b8, val) +#define VCHAN_PCM_WRITE_S24_LE(b8, val) PCM_WRITE_S24_LE(b8, val) +#define VCHAN_PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) +#define VCHAN_PCM_WRITE_S16_BE(b8, val) PCM_WRITE_S16_BE(b8, val) +#define VCHAN_PCM_WRITE_S24_BE(b8, val) PCM_WRITE_S24_BE(b8, val) +#define VCHAN_PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) +#define VCHAN_PCM_WRITE_U8_NE(b8, val) PCM_WRITE_U8(b8, val) +#define VCHAN_PCM_WRITE_U16_LE(b8, val) PCM_WRITE_U16_LE(b8, val) +#define VCHAN_PCM_WRITE_U24_LE(b8, val) PCM_WRITE_U24_LE(b8, val) +#define VCHAN_PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) +#define VCHAN_PCM_WRITE_U16_BE(b8, val) PCM_WRITE_U16_BE(b8, val) +#define VCHAN_PCM_WRITE_U24_BE(b8, val) PCM_WRITE_U24_BE(b8, val) +#define VCHAN_PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) + +#define FEEDER_VCHAN_MIX(FMTBIT, VCHAN_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS(struct feed_vchan_info *info, \ + uint8_t *to, uint8_t *tmp, uint32_t count) \ +{ \ + uint32_t bps; \ + int32_t x, y; \ + VCHAN_INTCAST z; \ + int i; \ + \ + bps = info->bps; \ + i = count; \ + tmp += i; \ + to += i; \ + while (i > 0) { \ + tmp -= bps; \ + to -= bps; \ + i -= bps; \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(tmp); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(to); \ + z = (VCHAN_INTCAST)x + y; \ + x = PCM_CLAMP_##SIGN##FMTBIT(z); \ + VCHAN_PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(to, x); \ + } \ + return count; \ +} + +FEEDER_VCHAN_MIX(8, int32_t, S, s, NE, ne) +FEEDER_VCHAN_MIX(16, int32_t, S, s, LE, le) +FEEDER_VCHAN_MIX(24, int32_t, S, s, LE, le) +FEEDER_VCHAN_MIX(32, intpcm_t, S, s, LE, le) +FEEDER_VCHAN_MIX(16, int32_t, S, s, BE, be) +FEEDER_VCHAN_MIX(24, int32_t, S, s, BE, be) +FEEDER_VCHAN_MIX(32, intpcm_t, S, s, BE, be) +/* unsigned */ +FEEDER_VCHAN_MIX(8, int32_t, U, u, NE, ne) +FEEDER_VCHAN_MIX(16, int32_t, U, u, LE, le) +FEEDER_VCHAN_MIX(24, int32_t, U, u, LE, le) +FEEDER_VCHAN_MIX(32, intpcm_t, U, u, LE, le) +FEEDER_VCHAN_MIX(16, int32_t, U, u, BE, be) +FEEDER_VCHAN_MIX(24, int32_t, U, u, BE, be) +FEEDER_VCHAN_MIX(32, intpcm_t, U, u, BE, be) + static int -vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) +feed_vchan_setup(struct pcm_feeder *f) { - /* - * to is the output buffer, tmp is the input buffer - * count is the number of 16bit samples to mix - */ - int i; - int x; - - for(i = 0; i < count; i++) { - x = to[i]; - x += tmp[i]; - if (x < -32768) { - /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ - x = -32768; - } - if (x > 32767) { - /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ - x = 32767; + struct feed_vchan_info *info = f->data; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_vchan_mixer mix; + } vchan_mix_tbl[] = { + { AFMT_S8, 1, PCM_8_BPS, feed_vchan_mix_s8ne }, + { AFMT_S16_LE, 1, PCM_16_BPS, feed_vchan_mix_s16le }, + { AFMT_S24_LE, 1, PCM_24_BPS, feed_vchan_mix_s24le }, + { AFMT_S32_LE, 1, PCM_32_BPS, feed_vchan_mix_s32le }, + { AFMT_S16_BE, 1, PCM_16_BPS, feed_vchan_mix_s16be }, + { AFMT_S24_BE, 1, PCM_24_BPS, feed_vchan_mix_s24be }, + { AFMT_S32_BE, 1, PCM_32_BPS, feed_vchan_mix_s32be }, + { AFMT_S8 | AFMT_STEREO, 2, PCM_8_BPS, feed_vchan_mix_s8ne }, + { AFMT_S16_LE | AFMT_STEREO, 2, PCM_16_BPS, feed_vchan_mix_s16le }, + { AFMT_S24_LE | AFMT_STEREO, 2, PCM_24_BPS, feed_vchan_mix_s24le }, + { AFMT_S32_LE | AFMT_STEREO, 2, PCM_32_BPS, feed_vchan_mix_s32le }, + { AFMT_S16_BE | AFMT_STEREO, 2, PCM_16_BPS, feed_vchan_mix_s16be }, + { AFMT_S24_BE | AFMT_STEREO, 2, PCM_24_BPS, feed_vchan_mix_s24be }, + { AFMT_S32_BE | AFMT_STEREO, 2, PCM_32_BPS, feed_vchan_mix_s32be }, + /* unsigned */ + { AFMT_U8, 1, PCM_8_BPS, feed_vchan_mix_u8ne }, + { AFMT_U16_LE, 1, PCM_16_BPS, feed_vchan_mix_u16le }, + { AFMT_U24_LE, 1, PCM_24_BPS, feed_vchan_mix_u24le }, + { AFMT_U32_LE, 1, PCM_32_BPS, feed_vchan_mix_u32le }, + { AFMT_U16_BE, 1, PCM_16_BPS, feed_vchan_mix_u16be }, + { AFMT_U24_BE, 1, PCM_24_BPS, feed_vchan_mix_u24be }, + { AFMT_U32_BE, 1, PCM_32_BPS, feed_vchan_mix_u32be }, + { AFMT_U8 | AFMT_STEREO, 2, PCM_8_BPS, feed_vchan_mix_u8ne }, + { AFMT_U16_LE | AFMT_STEREO, 2, PCM_16_BPS, feed_vchan_mix_u16le }, + { AFMT_U24_LE | AFMT_STEREO, 2, PCM_24_BPS, feed_vchan_mix_u24le }, + { AFMT_U32_LE | AFMT_STEREO, 2, PCM_32_BPS, feed_vchan_mix_u32le }, + { AFMT_U16_BE | AFMT_STEREO, 2, PCM_16_BPS, feed_vchan_mix_u16be }, + { AFMT_U24_BE | AFMT_STEREO, 2, PCM_24_BPS, feed_vchan_mix_u24be }, + { AFMT_U32_BE | AFMT_STEREO, 2, PCM_32_BPS, feed_vchan_mix_u32be }, + { 0, 0, 0, NULL }, + }; + uint32_t i; + + for (i = 0; i < sizeof(vchan_mix_tbl) / sizeof(*vchan_mix_tbl); i++) { + if (vchan_mix_tbl[i].format == 0) + return -1; + if (f->desc->out == vchan_mix_tbl[i].format) { + info->channels = vchan_mix_tbl[i].channels; + info->bps = vchan_mix_tbl[i].bps; + info->mix = vchan_mix_tbl[i].mix; + break; } - to[i] = x & 0x0000ffff; } + + info->zero_sample = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; + return 0; } static int -feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_vchan_init(struct pcm_feeder *f) +{ + struct feed_vchan_info *info; + + if (f->desc->out != f->desc->in) + return EINVAL; + + info = malloc(sizeof(*info), M_VCHANFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + f->data = info; + return feed_vchan_setup(f); +} + +static int +feed_vchan_free(struct pcm_feeder *f) { - /* we're going to abuse things a bit */ + struct feed_vchan_info *info = f->data; + + if (info) + free(info, M_VCHANFEEDER); + f->data = NULL; + return 0; +} + +static int +feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_vchan_info *info = f->data; struct snd_dbuf *src = source; struct pcmchan_children *cce; struct pcm_channel *ch; - int16_t *tmp, *dst; - unsigned int cnt; + uint32_t cnt, rcnt = 0, sz; + uint8_t *tmp; - if (sndbuf_getsize(src) < count) - panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x", - c->name, sndbuf_getsize(src), count, c->flags); - count &= ~1; - bzero(b, count); + sz = sndbuf_getsize(src); + if (sz < count) + count = sz; + + sz = info->bps * info->channels; + count -= count % sz; + if (count < sz) + return 0; /* * we are going to use our source as a temporary buffer since it's @@ -89,33 +263,65 @@ * list of children and calling vchan_mix_* to mix count bytes from each * into our destination buffer, b */ - dst = (int16_t *)b; - tmp = (int16_t *)sndbuf_getbuf(src); - bzero(tmp, count); + tmp = sndbuf_getbuf(src); + memset(b, info->zero_sample, count); SLIST_FOREACH(cce, &c->children, link) { ch = cce->channel; CHN_LOCK(ch); if (ch->flags & CHN_F_TRIGGERED) { if (ch->flags & CHN_F_MAPPED) sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); - cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); - vchan_mix_s16(dst, tmp, cnt / 2); + cnt = FEEDER_FEED(ch->feeder, ch, tmp, count, ch->bufsoft); + cnt -= cnt % sz; + cnt = info->mix(info, b, tmp, cnt); + if (cnt > rcnt) + rcnt = cnt; } CHN_UNLOCK(ch); } - return count; + return rcnt; } -static struct pcm_feederdesc feeder_vchan_s16_desc[] = { +static struct pcm_feederdesc feeder_vchan_desc[] = { + {FEEDER_MIXER, AFMT_S8, AFMT_S8, 0}, + {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_MIXER, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_MIXER, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_MIXER, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_MIXER, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_MIXER, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_MIXER, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, + {FEEDER_MIXER, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + /* unsigned */ + {FEEDER_MIXER, AFMT_U8, AFMT_U8, 0}, + {FEEDER_MIXER, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_MIXER, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_MIXER, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_MIXER, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_MIXER, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_MIXER, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_MIXER, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_vchan_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_vchan_s16), - { 0, 0 } +static kobj_method_t feeder_vchan_methods[] = { + KOBJMETHOD(feeder_init, feed_vchan_init), + KOBJMETHOD(feeder_free, feed_vchan_free), + KOBJMETHOD(feeder_feed, feed_vchan), + {0, 0} }; -FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); +FEEDER_DECLARE(feeder_vchan, 2, NULL); /************************************************************/ @@ -127,6 +333,8 @@ KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + if (!ch) + return NULL; ch->parent = parent; ch->channel = c; ch->fmt = AFMT_U8; @@ -145,7 +353,7 @@ } static int -vchan_setformat(kobj_t obj, void *data, u_int32_t format) +vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchinfo *ch = data; struct pcm_channel *parent = ch->parent; @@ -154,48 +362,66 @@ ch->fmt = format; ch->bps = 1; ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; - ch->bps <<= (ch->fmt & AFMT_16BIT)? 1 : 0; - ch->bps <<= (ch->fmt & AFMT_32BIT)? 2 : 0; + if (ch->fmt & AFMT_16BIT) + ch->bps <<= 1; + else if (ch->fmt & AFMT_24BIT) + ch->bps *= 3; + else if (ch->fmt & AFMT_32BIT) + ch->bps <<= 2; CHN_UNLOCK(channel); chn_notify(parent, CHN_N_FORMAT); CHN_LOCK(channel); + sndbuf_setfmt(channel->bufsoft, format); return 0; } static int -vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchinfo *ch = data; struct pcm_channel *parent = ch->parent; struct pcm_channel *channel = ch->channel; ch->spd = speed; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_RATE); - CHN_LOCK(channel); + CHN_UNLOCK(channel); + CHN_LOCK(parent); + speed = sndbuf_getspd(parent->bufsoft); + CHN_UNLOCK(parent); + CHN_LOCK(channel); return speed; } static int -vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +vchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct vchinfo *ch = data; + struct pcm_channel *channel = ch->channel; struct pcm_channel *parent = ch->parent; /* struct pcm_channel *channel = ch->channel; */ int prate, crate; ch->blksz = blocksize; /* CHN_UNLOCK(channel); */ + sndbuf_setblksz(channel->bufhard, blocksize); chn_notify(parent, CHN_N_BLOCKSIZE); CHN_LOCK(parent); /* CHN_LOCK(channel); */ crate = ch->spd * ch->bps; - prate = sndbuf_getspd(parent->bufhard) * sndbuf_getbps(parent->bufhard); - blocksize = sndbuf_getblksz(parent->bufhard); + prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft); + blocksize = sndbuf_getblksz(parent->bufsoft); CHN_UNLOCK(parent); blocksize *= prate; blocksize /= crate; + blocksize += ch->bps; + prate = 0; + while (blocksize >> prate) + prate++; + blocksize = 1 << (prate - 1); + blocksize -= blocksize % ch->bps; + /* XXX screwed !@#$ */ + if (blocksize < ch->bps) + blocksize = 4096 - (4096 % ch->bps); return blocksize; } @@ -222,11 +448,21 @@ vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; + uint32_t fmt; - ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard); + ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft); ch->caps.maxspeed = ch->caps.minspeed; - ch->caps.fmtlist = vchan_fmt; ch->caps.caps = 0; + ch->fmts[1] = 0; + fmt = sndbuf_getfmt(ch->parent->bufsoft); + if (fmt != vchan_valid_format(fmt)) { + device_printf(ch->parent->dev, + "%s: WARNING: invalid vchan format! (0x%08x)\n", + __func__, fmt); + fmt = VCHAN_DEFAULT_AFMT; + } + ch->fmts[0] = fmt; + ch->caps.fmtlist = ch->fmts; return &ch->caps; } @@ -243,6 +479,194 @@ }; CHANNEL_DECLARE(vchan); +/* + * On the fly vchan rate settings + */ +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct snddev_channel *sce; + struct pcm_channel *c, *ch = NULL, *fake; + struct pcmchan_caps *caps; + int err = 0; + int newspd = 0; + + d = oidp->oid_arg1; + if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) + return EINVAL; + if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { + pcm_inprog(d, -1); + return EINPROGRESS; + } + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_VIRTUAL) { + /* Sanity check */ + if (ch != NULL && ch != c->parentchannel) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EINVAL; + } + if (req->newptr != NULL && + (c->flags & CHN_F_BUSY)) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EBUSY; + } + } else if (c->flags & CHN_F_HAS_VCHAN) { + /* No way!! */ + if (ch != NULL) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EINVAL; + } + ch = c; + newspd = ch->speed; + } + } + CHN_UNLOCK(c); + } + if (ch == NULL) { + pcm_inprog(d, -1); + return EINVAL; + } + err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); + if (err == 0 && req->newptr != NULL) { + if (newspd < 1 || newspd < feeder_rate_ratemin || + newspd > feeder_rate_ratemax) { + pcm_inprog(d, -1); + return EINVAL; + } + CHN_LOCK(ch); + caps = chn_getcaps(ch); + if (caps == NULL || newspd < caps->minspeed || + newspd > caps->maxspeed) { + CHN_UNLOCK(ch); + pcm_inprog(d, -1); + return EINVAL; + } + if (newspd != ch->speed) { + err = chn_setspeed(ch, newspd); + /* + * Try to avoid FEEDER_RATE on parent channel if the + * requested value is not supported by the hardware. + */ + if (!err && (ch->feederflags & (1 << FEEDER_RATE))) { + newspd = sndbuf_getspd(ch->bufhard); + err = chn_setspeed(ch, newspd); + } + CHN_UNLOCK(ch); + if (err == 0) { + fake = pcm_getfakechan(d); + if (fake != NULL) { + CHN_LOCK(fake); + fake->speed = newspd; + CHN_UNLOCK(fake); + } + } + } else + CHN_UNLOCK(ch); + } + pcm_inprog(d, -1); + return err; +} + +static int +sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct snddev_channel *sce; + struct pcm_channel *c, *ch = NULL, *fake; + uint32_t newfmt, spd; + char fmtstr[AFMTSTR_MAXSZ]; + int err = 0, i; + + d = oidp->oid_arg1; + if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) + return EINVAL; + if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { + pcm_inprog(d, -1); + return EINPROGRESS; + } + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_VIRTUAL) { + /* Sanity check */ + if (ch != NULL && ch != c->parentchannel) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EINVAL; + } + if (req->newptr != NULL && + (c->flags & CHN_F_BUSY)) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EBUSY; + } + } else if (c->flags & CHN_F_HAS_VCHAN) { + /* No way!! */ + if (ch != NULL) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EINVAL; + } + ch = c; + if (ch->format != afmt2afmtstr(vchan_supported_fmts, + ch->format, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { + strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, sizeof(fmtstr)); + } + } + } + CHN_UNLOCK(c); + } + if (ch == NULL) { + pcm_inprog(d, -1); + return EINVAL; + } + err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); + if (err == 0 && req->newptr != NULL) { + for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { + if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { + strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, sizeof(fmtstr)); + break; + } + } + newfmt = vchan_valid_strformat(fmtstr); + if (newfmt == 0) { + pcm_inprog(d, -1); + return EINVAL; + } + CHN_LOCK(ch); + if (newfmt != ch->format) { + /* Get channel speed, before chn_reset() screw it. */ + spd = ch->speed; + err = chn_reset(ch, newfmt); + if (err == 0) + err = chn_setspeed(ch, spd); + CHN_UNLOCK(ch); + if (err == 0) { + fake = pcm_getfakechan(d); + if (fake != NULL) { + CHN_LOCK(fake); + fake->format = newfmt; + CHN_UNLOCK(fake); + } + } + } else + CHN_UNLOCK(ch); + } + pcm_inprog(d, -1); + return err; +} +#endif + /* virtual channel interface */ int @@ -250,10 +674,16 @@ { struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; - struct pcm_channel *child; - int err, first; + struct pcm_channel *child, *fake; + struct pcmchan_caps *parent_caps; + uint32_t vchanfmt = 0; + int err, first, speed = 0, r; - CHN_UNLOCK(parent); + if (!(parent->flags & CHN_F_BUSY)) + return EBUSY; + + + CHN_UNLOCK(parent); pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); if (!pce) { @@ -268,16 +698,7 @@ CHN_LOCK(parent); return ENODEV; } - - CHN_LOCK(parent); - if (!(parent->flags & CHN_F_BUSY)) - return EBUSY; - - first = SLIST_EMPTY(&parent->children); - /* add us to our parent channel's children */ pce->channel = child; - SLIST_INSERT_HEAD(&parent->children, pce, link); - CHN_UNLOCK(parent); /* add us to our grandparent's channel list */ /* @@ -287,20 +708,151 @@ if (err) { pcm_chn_destroy(child); free(pce, M_DEVBUF); + CHN_LOCK(parent); + return err; } CHN_LOCK(parent); - /* XXX gross ugly hack, murder death kill */ - if (first && !err) { - err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); - if (err) - printf("chn_reset: %d\n", err); - err = chn_setspeed(parent, 44100); - if (err) - printf("chn_setspeed: %d\n", err); + /* add us to our parent channel's children */ + first = SLIST_EMPTY(&parent->children); + SLIST_INSERT_HEAD(&parent->children, pce, link); + parent->flags |= CHN_F_HAS_VCHAN; + + if (first) { + parent_caps = chn_getcaps(parent); + if (parent_caps == NULL) + err = EINVAL; + + fake = pcm_getfakechan(d); + + if (!err && fake != NULL) { + /* + * Avoid querying kernel hint, use saved value + * from fake channel. + */ + CHN_UNLOCK(parent); + CHN_LOCK(fake); + speed = fake->speed; + vchanfmt = fake->format; + CHN_UNLOCK(fake); + CHN_LOCK(parent); + } + + if (!err) { + if (vchanfmt == 0) { + const char *vfmt; + + CHN_UNLOCK(parent); + r = resource_string_value(device_get_name(parent->dev), + device_get_unit(parent->dev), + "vchanformat", &vfmt); + CHN_LOCK(parent); + if (r != 0) + vfmt = NULL; + if (vfmt != NULL) { + vchanfmt = vchan_valid_strformat(vfmt); + for (r = 0; vchanfmt == 0 && + vchan_fmtstralias[r].alias != NULL; + r++) { + if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { + vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); + break; + } + } + } + if (vchanfmt == 0) + vchanfmt = VCHAN_DEFAULT_AFMT; + } + err = chn_reset(parent, vchanfmt); + } + + if (!err) { + /* + * This is very sad. Few soundcards advertised as being + * able to do (insanely) higher/lower speed, but in + * reality, they simply can't. At least, we give user chance + * to set sane value via kernel hints or sysctl. + */ + if (speed < 1) { + CHN_UNLOCK(parent); + r = resource_int_value(device_get_name(parent->dev), + device_get_unit(parent->dev), + "vchanrate", &speed); + CHN_LOCK(parent); + if (r != 0) { + /* + * Workaround for sb16 running + * poorly at 45k / 49k. + */ + switch (parent_caps->maxspeed) { + case 45000: + case 49000: + speed = 44100; + break; + default: + speed = VCHAN_DEFAULT_SPEED; + break; + } + } + } + + /* + * Limit speed based on driver caps. + * This is supposed to help fixed rate, non-VRA + * AC97 cards, but.. (see below) + */ + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; + + /* + * We still need to limit the speed between + * feeder_rate_ratemin <-> feeder_rate_ratemax. This is + * just an escape goat if all of the above failed + * miserably. + */ + if (speed < feeder_rate_ratemin) + speed = feeder_rate_ratemin; + if (speed > feeder_rate_ratemax) + speed = feeder_rate_ratemax; + + err = chn_setspeed(parent, speed); + /* + * Try to avoid FEEDER_RATE on parent channel if the + * requested value is not supported by the hardware. + */ + if (!err && (parent->feederflags & (1 << FEEDER_RATE))) { + speed = sndbuf_getspd(parent->bufhard); + err = chn_setspeed(parent, speed); + } + + if (!err && fake != NULL) { + /* + * Save new value to fake channel. + */ + CHN_UNLOCK(parent); + CHN_LOCK(fake); + fake->speed = speed; + fake->format = vchanfmt; + CHN_UNLOCK(fake); + CHN_LOCK(parent); + } + } + + if (err) { + SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); + parent->flags &= ~CHN_F_HAS_VCHAN; + CHN_UNLOCK(parent); + free(pce, M_DEVBUF); + if (pcm_chn_remove(d, child) == 0) + pcm_chn_destroy(child); + CHN_LOCK(parent); + return err; + } } - return err; + return 0; } int @@ -309,7 +861,9 @@ struct pcm_channel *parent = c->parentchannel; struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; - int err, last; + struct snddev_channel *sce; + uint32_t spd; + int err; CHN_LOCK(parent); if (!(parent->flags & CHN_F_BUSY)) { @@ -329,21 +883,45 @@ CHN_UNLOCK(parent); return EINVAL; gotch: + SLIST_FOREACH(sce, &d->channels, link) { + if (sce->channel == c) { + if (sce->dsp_devt) { + destroy_dev(sce->dsp_devt); + sce->dsp_devt = NULL; + } + if (sce->dspW_devt) { + destroy_dev(sce->dspW_devt); + sce->dspW_devt = NULL; + } + if (sce->audio_devt) { + destroy_dev(sce->audio_devt); + sce->audio_devt = NULL; + } + if (sce->dspHW_devt) { + destroy_dev(sce->dspHW_devt); + sce->dspHW_devt = NULL; + } + d->devcount--; + break; + } + } SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); free(pce, M_DEVBUF); - last = SLIST_EMPTY(&parent->children); - if (last) - parent->flags &= ~CHN_F_BUSY; + if (SLIST_EMPTY(&parent->children)) { + parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); + spd = parent->speed; + if (chn_reset(parent, parent->format) == 0) + chn_setspeed(parent, spd); + } /* remove us from our grandparent's channel list */ err = pcm_chn_remove(d, c); - if (err) - return err; CHN_UNLOCK(parent); /* destroy ourselves */ - err = pcm_chn_destroy(c); + if (!err) + err = pcm_chn_destroy(c); return err; } @@ -357,10 +935,14 @@ d = device_get_softc(dev); SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), - sysctl_hw_snd_vchans, "I", ""); + sysctl_hw_snd_vchans, "I", "total virtual channels"); + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchanrate, "I", "default virtual channel speed for mixing"); + SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchanformat, "A", "default virtual channel format for mixing"); #endif return 0; } - - --- sys/dev/sound/usb/uaudio.c.orig Fri Apr 15 12:15:24 2005 +++ sys/dev/sound/usb/uaudio.c Fri Sep 16 06:32:00 2005 @@ -4130,10 +4130,10 @@ if (mc->ctl == type) { if (mc->nchan == 2) { /* set Right */ - uaudio_ctl_set(sc, SET_CUR, mc, 1, (int)(right*256)/100); + uaudio_ctl_set(sc, SET_CUR, mc, 1, (int)(right*255)/100); } /* set Left or Mono */ - uaudio_ctl_set(sc, SET_CUR, mc, 0, (int)(left*256)/100); + uaudio_ctl_set(sc, SET_CUR, mc, 0, (int)(left*255)/100); } } return; --- sys/dev/sound/usb/uaudio_pcm.c.orig Wed Apr 20 14:43:41 2005 +++ sys/dev/sound/usb/uaudio_pcm.c Fri Sep 16 06:32:00 2005 @@ -237,11 +237,20 @@ { u_int32_t mask; device_t pa_dev; + struct snddev_info *d; struct ua_info *ua = mix_getdevinfo(m); pa_dev = device_get_parent(ua->sc_dev); + d = device_get_softc(ua->sc_dev); mask = uaudio_query_mix_info(pa_dev); + if (d && !(mask & SOUND_MIXER_PCM)) { + /* + * Emulate missing pcm mixer controller + * through FEEDER_VOLUME + */ + d->flags |= SD_F_SOFTVOL; + } mix_setdevs(m, mask); mask = uaudio_query_recsrc_info(pa_dev);