diff options
author | ariff <ariff@FreeBSD.org> | 2006-11-26 12:24:06 +0000 |
---|---|---|
committer | ariff <ariff@FreeBSD.org> | 2006-11-26 12:24:06 +0000 |
commit | 7b36f6d96b710844a6d2abd90a94bc169f72a5d1 (patch) | |
tree | be8f05cc6b2e41975dd084107c4db8978e7c9734 /sys/dev | |
parent | 70fe7b890e66532cef253d3bad928093268a24fd (diff) | |
download | FreeBSD-src-7b36f6d96b710844a6d2abd90a94bc169f72a5d1.zip FreeBSD-src-7b36f6d96b710844a6d2abd90a94bc169f72a5d1.tar.gz |
Welcome to Once-a-year Sound Mega-Commit. Enjoy numerous updates and fixes
in every sense.
General
-------
- Multichannel safe, endian safe, format safe
* Large part of critical pcm filters such as vchan.c, feeder_rate.c,
feeder_volume.c, feeder_fmt.c and feeder.c has been rewritten so that
using them does not cause the pcm data to be converted to 16bit little
endian.
* Macrosses for accessing pcm data safely are defined within sound.h in
the form of PCM_READ_* / PCM_WRITE_*
* Currently, most of them are probably limited for mono/stereo handling,
but the future addition of true multichannel will be much easier.
- Low latency operation
* Well, this require lot more works to do not just within sound driver,
but we're heading towards right direction. Buffer/block sizing within
channel.c is rewritten to calculate precise allocation for various
combination of sample/data/rate size. As a result, applying correct
SNDCTL_DSP_POLICY value will achive expected latency behaviour simmilar
to what commercial 4front driver do.
* Signal handling fix. ctrl+c of "cat /dev/zero > /dev/dsp" does not
result long delay.
* Eliminate sound truncation if the sound data is too small.
DIY:
1) Download / extract
http://people.freebsd.org/~ariff/lowlatency/shortfiles.tar.gz
2) Do a comparison between "cat state*.au > /dev/dsp" and
"for x in state*.au ; do cat $x > /dev/dsp ; done"
- there should be no "perceivable" differences.
Double close for PR kern/31445.
CAVEAT: Low latency come with (unbearable) price especially for poorly
written applications. Applications that trying to act smarter
by requesting (wrong) blocksize/blockcount will suffer the most.
Fixup samples/patches can be found at:
http://people.freebsd.org/~ariff/ports/
- Switch minimum/maximum sampling rate limit to "1" and "2016000" (48k * 42)
due to closer compatibility with 4front driver.
Discussed with: marcus@ (long time ago?)
- All driver specific sysctls in the form of "hw.snd.pcm%d.*" have been
moved to their own dev sysctl nodes, notably:
hw.snd.pcm%d.vchans -> dev.pcm.%d.vchans
Bump __FreeBSD_version.
Driver specific
---------------
- Ditto for sysctls.
- snd_atiixp, snd_es137x, snd_via8233, snd_hda
* Numerous cleanups and fixes.
* _EXPERIMENTAL_ polling mode support using simple callout_* mechanisme.
This was intended for pure debugging and latency measurement, but proven
good enough in few unexpected and rare cases (such as problematic shared
IRQ with GIANT devices - USB). Polling can be enabled/disabled through
dev.pcm.0.polling. Disabled by default.
- snd_ich
* Fix possible overflow during speed calibration. Delay final
initialization (pcm_setstatus) after calibration finished.
PR: kern/100169
Tested by: Kevin Overman <oberman@es.net>
* Inverted EAPD for few Nec VersaPro.
PR: kern/104715
Submitted by: KAWATA Masahiko <kawata@mta.biglobe.ne.jp>
Thanks to various people, notably Joel Dahl, Yuriy Tsibizov, Kevin Oberman,
those at #freebsd-azalia @ freenode and others for testing.
Joel Dahl will do the manpage update.
Diffstat (limited to 'sys/dev')
27 files changed, 5579 insertions, 2642 deletions
diff --git a/sys/dev/sound/pci/atiixp.c b/sys/dev/sound/pci/atiixp.c index 33f0f07..bd7249c 100644 --- a/sys/dev/sound/pci/atiixp.c +++ b/sys/dev/sound/pci/atiixp.c @@ -66,17 +66,11 @@ SND_DECLARE_FILE("$FreeBSD$"); #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; @@ -92,11 +86,9 @@ struct atiixp_chinfo { 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 enable_bit, flush_bit, linkptr_bit, dt_cur_bit; + uint32_t blksz, blkcnt; + uint32_t ptr, prevptr; uint32_t fmt; int caps_32bit, dir, active; }; @@ -123,10 +115,12 @@ struct atiixp_info { uint32_t bufsz; uint32_t codec_not_ready_bits, codec_idx, codec_found; - uint32_t dma_segs; + uint32_t blkcnt; int registered_channels; struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; #define atiixp_rd(_sc, _reg) \ @@ -226,7 +220,7 @@ atiixp_enable_interrupts(struct atiixp_info *sc) */ #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); + 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; @@ -281,8 +275,7 @@ atiixp_reset_aclink(struct atiixp_info *sc) /* 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) { + while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) && --timeout) { #if 0 device_printf(sc->dev, "not up; resetting aclink hardware\n"); #endif @@ -358,7 +351,7 @@ atiixp_waitready_codec(struct atiixp_info *sc) do { if ((atiixp_rd(sc, ATI_REG_PHYS_OUT_ADDR) & - ATI_REG_PHYS_OUT_ADDR_EN) == 0) + ATI_REG_PHYS_OUT_ADDR_EN) == 0) return (0); DELAY(1); } while (--timeout); @@ -377,8 +370,7 @@ atiixp_rdcd(kobj_t obj, void *devinfo, int reg) return (-1); data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | - ATI_REG_PHYS_OUT_ADDR_EN | - ATI_REG_PHYS_OUT_RW | sc->codec_idx; + ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); @@ -408,8 +400,8 @@ atiixp_wrcd(kobj_t obj, void *devinfo, int reg, uint32_t data) 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; + (((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); @@ -417,8 +409,8 @@ atiixp_wrcd(kobj_t obj, void *devinfo, int reg, uint32_t data) } static kobj_method_t atiixp_ac97_methods[] = { - KOBJMETHOD(ac97_read, atiixp_rdcd), - KOBJMETHOD(ac97_write, atiixp_wrcd), + KOBJMETHOD(ac97_read, atiixp_rdcd), + KOBJMETHOD(ac97_write, atiixp_wrcd), { 0, 0 } }; AC97_DECLARE(atiixp_ac97); @@ -441,15 +433,15 @@ atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 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; + ch->dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; /* Native 32bit playback working properly */ ch->caps_32bit = 1; } else { ch = &sc->rch; ch->linkptr_bit = ATI_REG_IN_DMA_LINKPTR; - ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; + ch->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; + ch->dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; /* XXX Native 32bit recording appear to be broken */ ch->caps_32bit = 1; } @@ -458,8 +450,8 @@ atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, ch->parent = sc; ch->channel = c; ch->dir = dir; - ch->dma_segs = sc->dma_segs; - ch->dma_blksz = sc->bufsz / sc->dma_segs; + ch->blkcnt = sc->blkcnt; + ch->blksz = sc->bufsz / ch->blkcnt; atiixp_unlock(sc); @@ -468,9 +460,9 @@ atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 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)); + ch->sgd_table = &sc->sgd_table[num * ch->blkcnt]; + ch->sgd_addr = sc->sgd_addr + (num * ch->blkcnt * + sizeof(struct atiixp_dma_op)); atiixp_disable_dma(ch); atiixp_unlock(sc); @@ -496,7 +488,7 @@ atiixp_chan_setformat(kobj_t obj, void *data, uint32_t format) 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); + 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); @@ -527,84 +519,44 @@ 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 ((blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) + blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; - 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); - } + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && + sndbuf_resize(ch->buffer, ch->blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, ch->blkcnt); + + ch->blksz = sndbuf_getblksz(ch->buffer); - return (ch->dma_blksz); + return (ch->blksz); } static void atiixp_buildsgdt(struct atiixp_chinfo *ch) { - uint32_t addr; + struct atiixp_info *sc = ch->parent; + uint32_t addr, blksz, blkcnt; 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))); + if (sc->polling != 0) { + blksz = ch->blksz * ch->blkcnt; + blkcnt = 1; + } else { + blksz = ch->blksz; + blkcnt = ch->blkcnt; } -#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; + for (i = 0; i < blkcnt; i++) { + ch->sgd_table[i].addr = htole32(addr + (i * blksz)); + ch->sgd_table[i].status = htole16(0); + ch->sgd_table[i].size = htole16(blksz >> 2); + ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + + (((i + 1) % blkcnt) * sizeof(struct atiixp_dma_op))); } - - /* 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 @@ -614,9 +566,9 @@ atiixp_dmapos(struct atiixp_chinfo *ch) uint32_t reg, addr, sz, retry; volatile uint32_t ptr; - reg = ch->dma_dt_cur_bit; + reg = ch->dt_cur_bit; addr = sndbuf_getbufaddr(ch->buffer); - sz = ch->dma_segs * ch->dma_blksz; + sz = ch->blkcnt * ch->blksz; retry = ATI_IXP_DMA_RETRY_MAX; do { @@ -625,37 +577,200 @@ atiixp_dmapos(struct atiixp_chinfo *ch) continue; ptr -= addr; if (ptr < sz) { +#if 0 #ifdef ATI_IXP_DEBUG - if ((ptr & ~(ch->dma_blksz - 1)) != ch->dma_ptr) { + if ((ptr & ~(ch->blksz - 1)) != ch->ptr) { uint32_t delta; - delta = (sz + ptr - ch->dma_prevptr) % sz; + delta = (sz + ptr - ch->prevptr) % sz; #ifndef ATI_IXP_DEBUG_VERBOSE - if (delta < ch->dma_blksz) + if (delta < ch->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] " + "prevptr=%u ptr=%u " + "ptr=%u blkcnt=%u " + "[delta=%u != 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) ? + ch->prevptr, ptr, + ch->ptr, ch->blkcnt, + delta, ch->blksz, + (delta < ch->blksz) ? "OVERLAPPED!" : "Ok"); - ch->dma_ptr = ptr & ~(ch->dma_blksz - 1); + ch->ptr = ptr & ~(ch->blksz - 1); } - ch->dma_prevptr = ptr; + ch->prevptr = ptr; +#endif #endif return (ptr); } } while (--retry); device_printf(sc->dev, "PCMDIR_%s: invalid DMA pointer ptr=%u\n", - (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); + (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); + + return (0); +} + +static __inline int +atiixp_poll_channel(struct atiixp_chinfo *ch) +{ + uint32_t sz, delta; + volatile uint32_t ptr; + + if (ch->active == 0) + return (0); + + sz = ch->blksz * ch->blkcnt; + ptr = atiixp_dmapos(ch); + ch->ptr = ptr; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +#define atiixp_chan_active(sc) ((sc)->pch.active + (sc)->rch.active) + +static void +atiixp_poll_callback(void *arg) +{ + struct atiixp_info *sc = arg; + uint32_t trigger = 0; + + if (sc == NULL) + return; + + atiixp_lock(sc); + if (sc->polling == 0 || atiixp_chan_active(sc) == 0) { + atiixp_unlock(sc); + return; + } + + trigger |= (atiixp_poll_channel(&sc->pch) != 0) ? 1 : 0; + trigger |= (atiixp_poll_channel(&sc->rch) != 0) ? 2 : 0; + + /* XXX */ + callout_reset(&sc->poll_timer, 1/*sc->poll_ticks*/, + atiixp_poll_callback, sc); + + atiixp_unlock(sc); + + if (trigger & 1) + chn_intr(sc->pch.channel); + if (trigger & 2) + chn_intr(sc->rch.channel); +} + +static int +atiixp_chan_trigger(kobj_t obj, void *data, int go) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + uint32_t value; + int pollticks; + + atiixp_lock(sc); + + switch (go) { + case PCMTRIG_START: + atiixp_flush_dma(ch); + atiixp_buildsgdt(ch); + atiixp_wr(sc, ch->linkptr_bit, 0); + atiixp_enable_dma(ch); + atiixp_wr(sc, ch->linkptr_bit, + (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); + if (sc->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (atiixp_chan_active(sc) == 0 || + pollticks < sc->poll_ticks) { + if (bootverbose) { + if (atiixp_chan_active(sc) == 0) + device_printf(sc->dev, + "%s: pollticks=%d\n", + __func__, pollticks); + else + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + } + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, 1, + atiixp_poll_callback, sc); + } + } + ch->active = 1; + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + atiixp_disable_dma(ch); + atiixp_flush_dma(ch); + ch->active = 0; + if (sc->polling != 0) { + if (atiixp_chan_active(sc) == 0) { + callout_stop(&sc->poll_timer); + sc->poll_ticks = 1; + } else { + if (sc->pch.active != 0) + ch = &sc->pch; + else + ch = &sc->rch; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks > sc->poll_ticks) { + if (bootverbose) + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, + 1, atiixp_poll_callback, + sc); + } + } + } + break; + default: + atiixp_unlock(sc); + return (0); + break; + } + + /* Update bus busy status */ + value = atiixp_rd(sc, ATI_REG_IER); + if (atiixp_rd(sc, ATI_REG_CMD) & (ATI_REG_CMD_SEND_EN | + ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) + value |= ATI_REG_IER_SET_BUS_BUSY; + else + value &= ~ATI_REG_IER_SET_BUS_BUSY; + atiixp_wr(sc, ATI_REG_IER, value); + + atiixp_unlock(sc); return (0); } @@ -668,7 +783,10 @@ atiixp_chan_getptr(kobj_t obj, void *data) uint32_t ptr; atiixp_lock(sc); - ptr = atiixp_dmapos(ch); + if (sc->polling != 0) + ptr = ch->ptr; + else + ptr = atiixp_dmapos(ch); atiixp_unlock(sc); return (ptr); @@ -703,10 +821,14 @@ static void atiixp_intr(void *p) { struct atiixp_info *sc = p; - struct atiixp_chinfo *ch; uint32_t status, enable, detected_codecs; + uint32_t trigger = 0; atiixp_lock(sc); + if (sc->polling != 0) { + atiixp_unlock(sc); + return; + } status = atiixp_rd(sc, ATI_REG_ISR); if (status == 0) { @@ -714,26 +836,10 @@ atiixp_intr(void *p) 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 ((status & ATI_REG_ISR_OUT_STATUS) && sc->pch.active != 0) + trigger |= 1; + if ((status & ATI_REG_ISR_IN_STATUS) && sc->rch.active != 0) + trigger |= 2; #if 0 if (status & ATI_REG_ISR_IN_XRUN) { @@ -760,6 +866,11 @@ atiixp_intr(void *p) /* acknowledge */ atiixp_wr(sc, ATI_REG_ISR, status); atiixp_unlock(sc); + + if (trigger & 1) + chn_intr(sc->pch.channel); + if (trigger & 2) + chn_intr(sc->rch.channel); } static void @@ -782,7 +893,7 @@ atiixp_chip_pre_init(struct atiixp_info *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 ); + ATI_REG_CMD_SPDF_OUT_EN ); atiixp_wr(sc, ATI_REG_CMD, value); /* reset aclink */ @@ -796,12 +907,54 @@ atiixp_chip_pre_init(struct atiixp_info *sc) atiixp_unlock(sc); } +#ifdef SND_DYNSYSCTL +static int +sysctl_atiixp_polling(SYSCTL_HANDLER_ARGS) +{ + struct atiixp_info *sc; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + atiixp_lock(sc); + val = sc->polling; + atiixp_unlock(sc); + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + atiixp_lock(sc); + if (val != sc->polling) { + if (atiixp_chan_active(sc) != 0) + err = EBUSY; + else if (val == 0) { + atiixp_enable_interrupts(sc); + sc->polling = 0; + DELAY(1000); + } else { + atiixp_disable_interrupts(sc); + sc->polling = 1; + DELAY(1000); + } + } + atiixp_unlock(sc); + + return (err); +} +#endif + static void atiixp_chip_post_init(void *arg) { struct atiixp_info *sc = (struct atiixp_info *)arg; uint32_t subdev; - int i, timeout, found; + int i, timeout, found, polling; char status[SND_STATUSLEN]; atiixp_lock(sc); @@ -811,6 +964,9 @@ atiixp_chip_post_init(void *arg) sc->delayed_attach.ich_func = NULL; } + polling = sc->polling; + sc->polling = 0; + /* wait for the interrupts to happen */ timeout = 100; do { @@ -819,6 +975,7 @@ atiixp_chip_post_init(void *arg) break; } while (--timeout); + sc->polling = polling; atiixp_disable_interrupts(sc); if (timeout == 0) { @@ -835,22 +992,19 @@ atiixp_chip_post_init(void *arg) * 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)) { + 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)) { + 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)) { + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) { /* codec 2 present */ sc->codec_found++; } @@ -865,10 +1019,12 @@ atiixp_chip_post_init(void *arg) if (sc->codec == NULL) goto postinitbad; - subdev = (pci_get_subdevice(sc->dev) << 16) | pci_get_subvendor(sc->dev); + 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); + ac97_setflags(sc->codec, ac97_getflags(sc->codec) | + AC97_F_EAPD_INV); break; default: break; @@ -884,14 +1040,22 @@ atiixp_chip_post_init(void *arg) for (i = 0; i < ATI_IXP_NRCHAN; i++) pcm_addchan(sc->dev, PCMDIR_REC, &atiixp_chan_class, sc); +#ifdef SND_DYNSYSCTL + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), + sysctl_atiixp_polling, "I", "Enable polling mode"); +#endif + 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)); + 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); + if (sc->polling == 0) + atiixp_enable_interrupts(sc); atiixp_unlock(sc); return; @@ -949,7 +1113,7 @@ atiixp_pci_probe(device_t 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) { + devid == atiixp_hw[i].devid) { device_set_desc(dev, atiixp_hw[i].desc); return (BUS_PROBE_DEFAULT); } @@ -971,18 +1135,23 @@ atiixp_pci_attach(device_t dev) 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; + + callout_init(&sc->poll_timer, CALLOUT_MPSAFE); + sc->poll_ticks = 1; + + if (resource_int_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) + sc->polling = 1; + else + sc->polling = 0; pci_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); + 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"); @@ -993,14 +1162,13 @@ atiixp_pci_attach(device_t dev) sc->sh = rman_get_bushandle(sc->reg); sc->bufsz = pcm_getbuffersize(dev, ATI_IXP_BUFSZ_MIN, - ATI_IXP_BUFSZ_DEFAULT, ATI_IXP_BUFSZ_MAX); + 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)) { + 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; } @@ -1008,27 +1176,20 @@ atiixp_pci_attach(device_t dev) /* * 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; + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + sc->blkcnt = sc->bufsz / i; + i = 0; + while (sc->blkcnt >> i) + i++; + sc->blkcnt = 1 << (i - 1); + if (sc->blkcnt < ATI_IXP_DMA_CHSEGS_MIN) + sc->blkcnt = ATI_IXP_DMA_CHSEGS_MIN; + else if (sc->blkcnt > ATI_IXP_DMA_CHSEGS_MAX) + sc->blkcnt = ATI_IXP_DMA_CHSEGS_MAX; + + } else + sc->blkcnt = ATI_IXP_DMA_CHSEGS; /* * DMA tag for scatter-gather buffers and link pointers @@ -1048,8 +1209,8 @@ atiixp_pci_attach(device_t dev) /*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), + /*maxsize*/sc->blkcnt * ATI_IXP_NCHANS * + sizeof(struct atiixp_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->sgd_dmat) != 0) { @@ -1058,13 +1219,12 @@ atiixp_pci_attach(device_t dev) } if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, - BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) + 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)) + sc->blkcnt * ATI_IXP_NCHANS * sizeof(struct atiixp_dma_op), + atiixp_dma_cb, sc, 0)) goto bad; @@ -1073,7 +1233,7 @@ atiixp_pci_attach(device_t dev) 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) { + config_intrhook_establish(&sc->delayed_attach) != 0) { sc->delayed_attach.ich_func = NULL; atiixp_chip_post_init(sc); } @@ -1116,15 +1276,12 @@ atiixp_pci_suspend(device_t dev) /* 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) + if (sc->pch.active != 0) atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); - if (sc->rch.channel && sc->rch.active) + if (sc->rch.active != 0) atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); /* power down aclink and pci bus */ @@ -1161,22 +1318,23 @@ atiixp_pci_resume(device_t dev) * Resume channel activities. Reset channel format regardless * of its previous state. */ - if (sc->pch.channel) { - if (sc->pch.fmt) + if (sc->pch.channel != NULL) { + if (sc->pch.fmt != 0) atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); - if (sc->pch.active) + if (sc->pch.active != 0) atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); } - if (sc->rch.channel) { - if (sc->rch.fmt) + if (sc->rch.channel != NULL) { + if (sc->rch.fmt != 0) atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); - if (sc->rch.active) + if (sc->rch.active != 0) atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); } /* enable interrupts */ atiixp_lock(sc); - atiixp_enable_interrupts(sc); + if (sc->polling == 0) + atiixp_enable_interrupts(sc); atiixp_unlock(sc); return (0); diff --git a/sys/dev/sound/pci/cmi.c b/sys/dev/sound/pci/cmi.c index 131650d..83d9cad 100644 --- a/sys/dev/sound/pci/cmi.c +++ b/sys/dev/sound/pci/cmi.c @@ -746,9 +746,9 @@ cmi_initsys(struct sc_info* sc) to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_INT(snd_sysctl_tree(sc->dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(sc->dev)), - OID_AUTO, "_spdif_enabled", CTLFLAG_RW, + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), + OID_AUTO, "spdif_enabled", CTLFLAG_RW, &sc->spdif_enabled, 0, "enable SPDIF output at 44.1 kHz and above"); #endif /* SND_DYNSYSCTL */ diff --git a/sys/dev/sound/pci/es137x.c b/sys/dev/sound/pci/es137x.c index 0851650..e5aa86e 100644 --- a/sys/dev/sound/pci/es137x.c +++ b/sys/dev/sound/pci/es137x.c @@ -105,7 +105,9 @@ struct es_chinfo { struct snd_dbuf *buffer; struct pcmchan_caps caps; int dir, num, index; - u_int32_t fmt, blksz, bufsz; + uint32_t fmt, blksz, blkcnt, bufsz; + uint32_t ptr, prevptr; + int active; }; /* @@ -181,7 +183,7 @@ struct es_info { device_t dev; int num; - unsigned int bufsz; + unsigned int bufsz, blkcnt; /* Contents of board's registers */ uint32_t ctrl; @@ -189,6 +191,8 @@ struct es_info { uint32_t escfg; struct es_chinfo ch[ES_NCHANS]; struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; #define ES_LOCK(sc) snd_mtxlock((sc)->lock) @@ -198,14 +202,15 @@ struct es_info { /* prototypes */ static void es_intr(void *); 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 void es1371_src_write(struct es_info *, + unsigned short, unsigned short); +static unsigned int es1371_adc_rate(struct es_info *, unsigned int, int); +static unsigned int es1371_dac_rate(struct es_info *, unsigned int, int); static int es1371_init(struct es_info *); static int es1370_init(struct es_info *); -static int es1370_wrcodec(struct es_info *, u_char, u_char); +static int es1370_wrcodec(struct es_info *, unsigned char, unsigned char); -static u_int32_t es_fmt[] = { +static uint32_t es_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, @@ -234,23 +239,23 @@ static const struct { [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; -static __inline u_int32_t +static __inline uint32_t es_rd(struct es_info *es, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(es->st, es->sh, regno); + return (bus_space_read_1(es->st, es->sh, regno)); case 2: - return bus_space_read_2(es->st, es->sh, regno); + return (bus_space_read_2(es->st, es->sh, regno)); case 4: - return bus_space_read_4(es->st, es->sh, regno); + return (bus_space_read_4(es->st, es->sh, regno)); default: - return 0xFFFFFFFF; + return (0xFFFFFFFF); } } static __inline void -es_wr(struct es_info *es, int regno, u_int32_t data, int size) +es_wr(struct es_info *es, int regno, uint32_t data, int size) { switch (size) { @@ -274,12 +279,14 @@ es1370_mixinit(struct snd_mixer *m) { struct es_info *es; int i; - u_int32_t v; + uint32_t v; es = mix_getdevinfo(m); v = 0; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - if (mixtable[i].avail) v |= (1 << i); + 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 @@ -291,12 +298,14 @@ es1370_mixinit(struct snd_mixer *m) 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); + 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; + return (0); } static int @@ -305,38 +314,38 @@ es1370_mixset(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) struct es_info *es; int l, r, rl, rr, set_dac1; - if (!mixtable[dev].avail) return -1; + if (!mixtable[dev].avail) + return (-1); l = left; - r = mixtable[dev].stereo? right : l; - if (mixtable[dev].left == 0xf) { - rl = (l < 2)? 0x80 : 7 - (l - 2) / 14; - } else { - rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; - } + r = (mixtable[dev].stereo) ? right : l; + if (mixtable[dev].left == 0xf) + rl = (l < 2) ? 0x80 : 7 - (l - 2) / 14; + 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)) { + ES_DAC1_ENABLED(es->escfg)) set_dac1 = 1; - } else { + else set_dac1 = 0; - } if (mixtable[dev].stereo) { - rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; + rr = (r < 10) ? 0x80 : 15 - (r - 10) / 6; es1370_wrcodec(es, mixtable[dev].right, rr); if (set_dac1 && mixtable[SOUND_MIXER_SYNTH].stereo) - es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].right, rr); + es1370_wrcodec(es, + mixtable[SOUND_MIXER_SYNTH].right, rr); } es1370_wrcodec(es, mixtable[dev].left, rl); if (set_dac1) es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].left, rl); ES_UNLOCK(es); - return l | (r << 8); + return (l | (r << 8)); } static int -es1370_mixsetrecsrc(struct snd_mixer *m, u_int32_t src) +es1370_mixsetrecsrc(struct snd_mixer *m, uint32_t src) { struct es_info *es; int i, j = 0; @@ -349,9 +358,8 @@ es1370_mixsetrecsrc(struct snd_mixer *m, u_int32_t src) ES_LOCK(es); if ((src & (1 << SOUND_MIXER_PCM)) && ES_SINGLE_PCM_MIX(es->escfg) && - ES_DAC1_ENABLED(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); @@ -360,13 +368,13 @@ es1370_mixsetrecsrc(struct snd_mixer *m, u_int32_t src) es1370_wrcodec(es, CODEC_OMIX2, 0x3f); ES_UNLOCK(es); - return src; + return (src); } static kobj_method_t es1370_mixer_methods[] = { - KOBJMETHOD(mixer_init, es1370_mixinit), - KOBJMETHOD(mixer_set, es1370_mixset), - KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), + KOBJMETHOD(mixer_init, es1370_mixinit), + KOBJMETHOD(mixer_set, es1370_mixset), + KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), { 0, 0 } }; MIXER_DECLARE(es1370_mixer); @@ -374,30 +382,31 @@ MIXER_DECLARE(es1370_mixer); /* -------------------------------------------------------------------- */ static int -es1370_wrcodec(struct es_info *es, u_char i, u_char data) +es1370_wrcodec(struct es_info *es, unsigned char i, unsigned char data) { - u_int t; + unsigned int t; ES_LOCK_ASSERT(es); for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1370_REG_STATUS, 4) & - STAT_CSTAT) == 0) { + STAT_CSTAT) == 0) { es_wr(es, ES1370_REG_CODEC, - ((u_short)i << CODEC_INDEX_SHIFT) | data, 2); - return 0; + ((unsigned short)i << CODEC_INDEX_SHIFT) | data, 2); + return (0); } DELAY(1); } device_printf(es->dev, "%s: timed out\n", __func__); - return -1; + return (-1); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * -eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +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; @@ -408,26 +417,26 @@ eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c if (dir == PCMDIR_PLAY) { index = ES_GP(es->escfg); es->escfg = ES_SET_GP(es->escfg, index + 1); - if (index == 0) { + if (index == 0) index = ES_DAC_FIRST(es->escfg); - } else if (index == 1) { + else if (index == 1) index = ES_DAC_SECOND(es->escfg); - } else { - device_printf(es->dev, "Invalid ES_GP index: %d\n", index); + else { + device_printf(es->dev, + "Invalid ES_GP index: %d\n", index); ES_UNLOCK(es); - return NULL; + return (NULL); } if (!(index == ES_DAC1 || index == ES_DAC2)) { - device_printf(es->dev, "Unknown DAC: %d\n", - index + 1); + device_printf(es->dev, "Unknown DAC: %d\n", index + 1); ES_UNLOCK(es); - return NULL; + return (NULL); } if (es->ch[index].channel != NULL) { device_printf(es->dev, "DAC%d already initialized!\n", - index + 1); + index + 1); ES_UNLOCK(es); - return NULL; + return (NULL); } } else index = ES_ADC; @@ -443,7 +452,7 @@ eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c } else { uint32_t fixed_rate = ES_FIXED_RATE(es->escfg); if (!(fixed_rate < es_caps.minspeed || - fixed_rate > es_caps.maxspeed)) { + fixed_rate > es_caps.maxspeed)) { ch->caps.maxspeed = fixed_rate; ch->caps.minspeed = fixed_rate; } @@ -453,33 +462,42 @@ eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c ch->channel = c; ch->buffer = b; ch->bufsz = es->bufsz; - ch->blksz = ch->bufsz / 2; + ch->blkcnt = es->blkcnt; + ch->blksz = ch->bufsz / ch->blkcnt; ch->dir = dir; ES_UNLOCK(es); if (sndbuf_alloc(ch->buffer, es->parent_dmat, ch->bufsz) != 0) - return NULL; + return (NULL); ES_LOCK(es); if (dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMEADR >> 8, 1); - es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); - es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + 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); + 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); + 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); } ES_UNLOCK(es); - return ch; + return (ch); } static int -eschan_setformat(kobj_t obj, void *data, u_int32_t format) +eschan_setformat(kobj_t obj, void *data, uint32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; @@ -488,38 +506,44 @@ eschan_setformat(kobj_t obj, void *data, u_int32_t format) if (ch->dir == PCMDIR_PLAY) { 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; + 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; + 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; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_R1SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_R1SMB; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); ch->fmt = format; - return 0; + return (0); } static int -eschan1370_setspeed(kobj_t obj, void *data, u_int32_t speed) +eschan1370_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; + ES_LOCK(es); /* Fixed rate , do nothing. */ if (ch->caps.minspeed == ch->caps.maxspeed) - return 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. @@ -546,11 +570,11 @@ eschan1370_setspeed(kobj_t obj, void *data, u_int32_t speed) } es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); ES_UNLOCK(es); - return speed; + return (speed); } static int -eschan1371_setspeed(kobj_t obj, void *data, u_int32_t speed) +eschan1371_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; @@ -563,33 +587,107 @@ eschan1371_setspeed(kobj_t obj, void *data, u_int32_t speed) else i = es1371_adc_rate(es, speed, ch->index); /* record */ ES_UNLOCK(es); - delta = (speed > i) ? speed - i : i - speed; + delta = (speed > i) ? (speed - i) : (i - speed); if (delta < 2) - return speed; - return i; + return (speed); + return (i); } static int -eschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +eschan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { - 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; - 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); + struct es_info *es = ch->parent; + + blksz &= ~0x3f; + if (blksz < 0x40) + blksz = 0x40; + + if ((blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) + blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && + sndbuf_resize(ch->buffer, ch->blkcnt, blksz) != 0) + device_printf(es->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, ch->blkcnt); + + ch->bufsz = sndbuf_getsize(ch->buffer); + ch->blksz = sndbuf_getblksz(ch->buffer); + + return (ch->blksz); +} + +#define es_chan_active(es) ((es)->ch[ES_DAC1].active + \ + (es)->ch[ES_DAC2].active + \ + (es)->ch[ES_ADC].active) + +static __inline int +es_poll_channel(struct es_chinfo *ch) +{ + struct es_info *es; + uint32_t sz, delta; + uint32_t reg, ptr; + + if (ch == NULL || ch->channel == NULL || ch->active == 0) + return (0); + + es = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) + reg = ES1370_REG_DAC1_FRAMECNT; + else + reg = ES1370_REG_DAC2_FRAMECNT; + } else + reg = ES1370_REG_ADC_FRAMECNT; + sz = ch->blksz * ch->blkcnt; + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + ptr = es_rd(es, reg & 0x000000ff, 4) >> 16; + ptr <<= 2; + ch->ptr = ptr; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +static void +es_poll_callback(void *arg) +{ + struct es_info *es = arg; + uint32_t trigger = 0; + int i; + + if (es == NULL) + return; + + ES_LOCK(es); + if (es->polling == 0 || es_chan_active(es) == 0) { + ES_UNLOCK(es); + return; + } + + for (i = 0; i < ES_NCHANS; i++) { + if (es_poll_channel(&es->ch[i]) != 0) + trigger |= 1 << i; + } + + /* XXX */ + callout_reset(&es->poll_timer, 1/*es->poll_ticks*/, + es_poll_callback, es); + + ES_UNLOCK(es); + + for (i = 0; i < ES_NCHANS; i++) { + if (trigger & (1 << i)) + chn_intr(es->ch[i].channel); } - return ch->blksz; } static int @@ -600,50 +698,94 @@ eschan_trigger(kobj_t obj, void *data, int go) uint32_t cnt, b = 0; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) - return 0; + return (0); + ES_LOCK(es); 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) { if (ch->index == ES_DAC1) { es->ctrl |= CTRL_DAC1_EN; - es->sctrl &= ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD); - es->sctrl |= SCTRL_P1INTEN | b; + es->sctrl &= ~(SCTRL_P1LOOPSEL | + SCTRL_P1PAUSE | SCTRL_P1SCTRLD); + if (es->polling == 0) + es->sctrl |= SCTRL_P1INTEN; + else + es->sctrl &= ~SCTRL_P1INTEN; + es->sctrl |= b; es_wr(es, ES1370_REG_DAC1_SCOUNT, cnt, 4); /* start at beginning of buffer */ - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMECNT >> 8, 4); - es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + 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->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | + SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN); + if (es->polling == 0) + es->sctrl |= SCTRL_P2INTEN; + else + es->sctrl &= ~SCTRL_P2INTEN; + es->sctrl |= (b << 2) | + ((((b >> 1) & 1) + 1) << SCTRL_SH_P2ENDINC); es_wr(es, ES1370_REG_DAC2_SCOUNT, cnt, 4); /* start at beginning of buffer */ - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8, 4); - es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + 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 + 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 | (b << 4); + if (es->polling == 0) + es->sctrl |= SCTRL_R1INTEN; + else + es->sctrl &= ~SCTRL_R1INTEN; + es->sctrl |= b << 4; es_wr(es, ES1370_REG_ADC_SCOUNT, cnt, 4); /* start at beginning of buffer */ - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8, 4); - es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); - } else es->ctrl &= ~CTRL_ADC_EN; + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } else + es->ctrl &= ~CTRL_ADC_EN; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + if (go == PCMTRIG_START) { + if (es->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + if (es_chan_active(es) == 0) { + es->poll_ticks = 1; + callout_reset(&es->poll_timer, 1, + es_poll_callback, es); + } + } + ch->active = 1; + } else { + ch->active = 0; + if (es->polling != 0) { + if (es_chan_active(es) == 0) { + callout_stop(&es->poll_timer); + es->poll_ticks = 1; + } + } + } ES_UNLOCK(es); - return 0; + return (0); } static int @@ -651,21 +793,29 @@ eschan_getptr(kobj_t obj, void *data) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - u_int32_t reg, cnt; + uint32_t reg, cnt; - if (ch->dir == PCMDIR_PLAY) { - if (ch->index == ES_DAC1) - reg = ES1370_REG_DAC1_FRAMECNT; - else - reg = ES1370_REG_DAC2_FRAMECNT; - } else - reg = ES1370_REG_ADC_FRAMECNT; ES_LOCK(es); - es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); - cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; + if (es->polling != 0) + cnt = ch->ptr; + else { + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) + reg = ES1370_REG_DAC1_FRAMECNT; + else + reg = ES1370_REG_DAC2_FRAMECNT; + } else + reg = ES1370_REG_ADC_FRAMECNT; + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; + /* cnt is longwords */ + cnt <<= 2; + } ES_UNLOCK(es); - /* cnt is longwords */ - return cnt << 2; + + cnt &= ~0x3f; + + return (cnt); } static struct pcmchan_caps * @@ -673,29 +823,29 @@ eschan_getcaps(kobj_t obj, void *data) { struct es_chinfo *ch = data; - return &ch->caps; + return (&ch->caps); } static kobj_method_t eschan1370_methods[] = { - KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setformat, eschan_setformat), - KOBJMETHOD(channel_setspeed, eschan1370_setspeed), - KOBJMETHOD(channel_setblocksize, eschan_setblocksize), - KOBJMETHOD(channel_trigger, eschan_trigger), - KOBJMETHOD(channel_getptr, eschan_getptr), - KOBJMETHOD(channel_getcaps, eschan_getcaps), + KOBJMETHOD(channel_init, eschan_init), + KOBJMETHOD(channel_setformat, eschan_setformat), + KOBJMETHOD(channel_setspeed, eschan1370_setspeed), + KOBJMETHOD(channel_setblocksize, eschan_setblocksize), + KOBJMETHOD(channel_trigger, eschan_trigger), + KOBJMETHOD(channel_getptr, eschan_getptr), + KOBJMETHOD(channel_getcaps, eschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(eschan1370); static kobj_method_t eschan1371_methods[] = { - KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setformat, eschan_setformat), - KOBJMETHOD(channel_setspeed, eschan1371_setspeed), - KOBJMETHOD(channel_setblocksize, eschan_setblocksize), - KOBJMETHOD(channel_trigger, eschan_trigger), - KOBJMETHOD(channel_getptr, eschan_getptr), - KOBJMETHOD(channel_getcaps, eschan_getcaps), + KOBJMETHOD(channel_init, eschan_init), + KOBJMETHOD(channel_setformat, eschan_setformat), + KOBJMETHOD(channel_setspeed, eschan1371_setspeed), + KOBJMETHOD(channel_setblocksize, eschan_setblocksize), + KOBJMETHOD(channel_trigger, eschan_trigger), + KOBJMETHOD(channel_getptr, eschan_getptr), + KOBJMETHOD(channel_getcaps, eschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(eschan1371); @@ -709,6 +859,10 @@ es_intr(void *p) uint32_t intsrc, sctrl; ES_LOCK(es); + if (es->polling != 0) { + ES_UNLOCK(es); + return; + } intsrc = es_rd(es, ES1370_REG_STATUS, 4); if ((intsrc & STAT_INTR) == 0) { ES_UNLOCK(es); @@ -716,17 +870,23 @@ es_intr(void *p) } sctrl = es->sctrl; - if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; - if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; - if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; + if (intsrc & STAT_ADC) + sctrl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) + sctrl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) + sctrl &= ~SCTRL_P2INTEN; es_wr(es, ES1370_REG_SERIAL_CONTROL, sctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); - if (intsrc & STAT_ADC) chn_intr(es->ch[ES_ADC].channel); - if (intsrc & STAT_DAC1) chn_intr(es->ch[ES_DAC1].channel); - if (intsrc & STAT_DAC2) chn_intr(es->ch[ES_DAC2].channel); + 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 */ @@ -738,7 +898,7 @@ es1370_init(struct es_info *es) /* ES1370 default to fixed rate operation */ if (resource_int_value(device_get_name(es->dev), - device_get_unit(es->dev), "fixed_rate", &r) == 0) { + device_get_unit(es->dev), "fixed_rate", &r) == 0) { fixed_rate = r; if (fixed_rate) { if (fixed_rate < es_caps.minspeed) @@ -750,8 +910,8 @@ es1370_init(struct es_info *es) 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; + device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) + single_pcm = (r != 0) ? 1 : 0; else single_pcm = 1; @@ -760,34 +920,38 @@ es1370_init(struct es_info *es) single_pcm = 1; /* This is ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 1); - if (fixed_rate) { + if (fixed_rate) es->escfg = ES_SET_FIXED_RATE(es->escfg, fixed_rate); - } else { + else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); fixed_rate = DSP_DEFAULT_SPEED; } - if (single_pcm) { + if (single_pcm) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); - } else { + 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); + (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); es->ctrl |= 3 << CTRL_SH_WTSRSEL; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es->sctrl = 0; es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); - es1370_wrcodec(es, CODEC_RES_PD, 3);/* No RST, PD */ - es1370_wrcodec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use - * {LR,B}CLK2 and run off the LRCLK2 - * 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 */ + /* No RST, PD */ + es1370_wrcodec(es, CODEC_RES_PD, 3); + /* + * CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; + * program DAC_SYNC=0! + */ + es1370_wrcodec(es, CODEC_CSEL, 0); + /* Recording source is mixer */ + es1370_wrcodec(es, CODEC_ADSEL, 0); + /* MIC amp is 0db */ + es1370_wrcodec(es, CODEC_MGAIN, 0); ES_UNLOCK(es); - return 0; + return (0); } /* ES1371 specific */ @@ -805,7 +969,8 @@ es1371_init(struct es_info *es) 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); + 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 @@ -846,20 +1011,21 @@ es1371_init(struct es_info *es) es_wr(es, ES1371_REG_SMPRATE, ES1371_DIS_SRC, 4); for (idx = 0; idx < 0x80; idx++) es1371_src_write(es, idx, 0); - es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); + es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); - es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); + es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); - es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); - es1371_adc_rate(es, 22050, ES_ADC); - es1371_dac_rate(es, 22050, ES_DAC1); - es1371_dac_rate(es, 22050, ES_DAC2); - /* WARNING: + es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); + es1371_adc_rate(es, 22050, ES_ADC); + es1371_dac_rate(es, 22050, ES_DAC1); + es1371_dac_rate(es, 22050, ES_DAC2); + /* + * WARNING: * enabling the sample rate converter without properly programming * its parameters causes the chip to lock up (the SRC busy bit will * be stuck high, and I've found no way to rectify this other than @@ -878,7 +1044,7 @@ es1371_init(struct es_info *es) /* -------------------------------------------------------------------- */ static int -es1371_wrcd(kobj_t obj, void *s, int addr, u_int32_t data) +es1371_wrcd(kobj_t obj, void *s, int addr, uint32_t data) { uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; @@ -889,27 +1055,29 @@ es1371_wrcd(kobj_t obj, void *s, int addr, u_int32_t data) /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - es_wr(es, ES1371_REG_SMPRATE, - (x & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | - 0x00010000, 4); + 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) + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00000000) break; + } /* wait for a SAFE time to write addr/data and then do it, dammit */ - for (t = 0; t < 0x1000; t++) - if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00010000) break; + } - es_wr(es, ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), 4); + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & + CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & + CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); - return 0; + return (0); } static int @@ -918,83 +1086,88 @@ es1371_rdcd(kobj_t obj, void *s, int addr) uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; - for (t = 0; t < 0x1000; t++) + for (t = 0; t < 0x1000; t++) { if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; + } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - es_wr(es, ES1371_REG_SMPRATE, - (x & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | - 0x00010000, 4); + 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) + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00000000) break; + } /* wait for a SAFE time to write addr/data and then do it, dammit */ - for (t = 0; t < 0x1000; t++) - if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00010000) break; + } - es_wr(es, ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - CODEC_PORD, 4); + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & + CODEC_POADD_MASK) | CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ - for (t = 0; t < 0x1000; t++) + for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; + } return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static kobj_method_t es1371_ac97_methods[] = { - KOBJMETHOD(ac97_read, es1371_rdcd), - KOBJMETHOD(ac97_write, es1371_wrcd), + KOBJMETHOD(ac97_read, es1371_rdcd), + KOBJMETHOD(ac97_write, es1371_wrcd), { 0, 0 } }; AC97_DECLARE(es1371_ac97); /* -------------------------------------------------------------------- */ -static u_int -es1371_src_read(struct es_info *es, u_short reg) +static unsigned int +es1371_src_read(struct es_info *es, unsigned short reg) { uint32_t r; - r = es1371_wait_src_ready(es) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); + r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg); es_wr(es, ES1371_REG_SMPRATE, r, 4); - return ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es)); + 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) +es1371_src_write(struct es_info *es, unsigned short reg, unsigned short data) { uint32_t r; - r = es1371_wait_src_ready(es) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); + r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg) | ES1371_SRC_RAM_DATAO(data); es_wr(es, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE, 4); } -static u_int -es1371_adc_rate(struct es_info *es, u_int rate, int set) +static unsigned int +es1371_adc_rate(struct es_info *es, unsigned int rate, int set) { - u_int n, truncm, freq, result; + unsigned int n, truncm, freq, result; ES_LOCK_ASSERT(es); - if (rate > 48000) rate = 48000; - if (rate < 4000) rate = 4000; + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; n = rate / 3000; if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) n--; @@ -1003,46 +1176,54 @@ es1371_adc_rate(struct es_info *es, u_int rate, int set) result = (48000UL << 15) / (freq / n); if (set) { if (rate >= 24000) { - if (truncm > 239) truncm = 239; + if (truncm > 239) + truncm = 239; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, - (((239 - truncm) >> 1) << 9) | (n << 4)); + (((239 - truncm) >> 1) << 9) | (n << 4)); } else { - if (truncm > 119) truncm = 119; + if (truncm > 119) + truncm = 119; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, - 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); + 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); } es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS, - (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & - 0x00ff) | ((freq >> 5) & 0xfc00)); - es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); + (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & + 0x00ff) | ((freq >> 5) & 0xfc00)); + es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, + freq & 0x7fff); es1371_src_write(es, ES_SMPREG_VOL_ADC, n << 8); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, n << 8); } - return result; + return (result); } -static u_int -es1371_dac_rate(struct es_info *es, u_int rate, int set) +static unsigned int +es1371_dac_rate(struct es_info *es, unsigned int rate, int set) { - u_int freq, r, result, dac, dis; + unsigned int freq, r, result, dac, dis; ES_LOCK_ASSERT(es); - if (rate > 48000) rate = 48000; - if (rate < 4000) rate = 4000; + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; freq = ((rate << 15) + 1500) / 3000; result = (freq * 3000) >> 15; - + dac = (set == ES_DAC1) ? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; dis = (set == ES_DAC1) ? ES1371_DIS_P2 : ES1371_DIS_P1; - r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); + 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_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)); + r = (es1371_wait_src_ready(es) & + (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); - return result; + return (result); } static uint32_t @@ -1051,13 +1232,14 @@ es1371_wait_src_ready(struct es_info *es) uint32_t t, r; for (t = 0; t < 0x1000; t++) { - if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & ES1371_SRC_RAM_BUSY)) - return r; + if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & + ES1371_SRC_RAM_BUSY)) + return (r); DELAY(1); } device_printf(es->dev, "%s: timed out 0x%x [0x%x]\n", __func__, ES1371_REG_SMPRATE, r); - return 0; + return (0); } /* -------------------------------------------------------------------- */ @@ -1072,78 +1254,76 @@ es_pci_probe(device_t dev) switch(pci_get_devid(dev)) { case ES1370_PCI_ID: device_set_desc(dev, "AudioPCI ES1370"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371_PCI_ID: switch(pci_get_revid(dev)) { case ES1371REV_ES1371_A: device_set_desc(dev, "AudioPCI ES1371-A"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1371_B: device_set_desc(dev, "AudioPCI ES1371-B"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_A: device_set_desc(dev, "AudioPCI ES1373-A"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_B: device_set_desc(dev, "AudioPCI ES1373-B"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_8: device_set_desc(dev, "AudioPCI ES1373-8"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_CT5880_A: device_set_desc(dev, "Creative CT5880-A"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "AudioPCI ES1371-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - case ES1371_PCI_ID2: device_set_desc(dev, "Strange AudioPCI ES1371-? (vid=3274)"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; - + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); case CT4730_PCI_ID: switch(pci_get_revid(dev)) { case CT4730REV_CT4730_A: - device_set_desc(dev, "Creative SB AudioPCI CT4730/EV1938"); - return BUS_PROBE_DEFAULT; + device_set_desc(dev, + "Creative SB AudioPCI CT4730/EV1938"); + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative SB AudioPCI CT4730-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - case CT5880_PCI_ID: switch(pci_get_revid(dev)) { case CT5880REV_CT5880_C: device_set_desc(dev, "Creative CT5880-C"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_D: device_set_desc(dev, "Creative CT5880-D"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_E: device_set_desc(dev, "Creative CT5880-E"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative CT5880-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - default: - return ENXIO; + return (ENXIO); } } @@ -1200,7 +1380,7 @@ sysctl_es137x_latency_timer(SYSCTL_HANDLER_ARGS) 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) @@ -1229,7 +1409,7 @@ sysctl_es137x_fixed_rate(SYSCTL_HANDLER_ARGS) 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)) @@ -1284,7 +1464,7 @@ sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) 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)) @@ -1294,11 +1474,11 @@ sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) 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); + 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); + (caddr_t)&recsrc, -1, NULL); if (err) return (err); if (level < 0) @@ -1309,27 +1489,26 @@ sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) ES_UNLOCK(es); return (EBUSY); } - if (val) { + if (val) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); - } else { + 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)); + (1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) | - (1 << SOUND_MIXER_SYNTH)); + (1 << SOUND_MIXER_SYNTH)); err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), - (caddr_t)&level, -1, NULL); + (caddr_t)&level, -1, NULL); } else { err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), - (caddr_t)&level, -1, NULL); + (caddr_t)&level, -1, NULL); mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) & - ~(1 << SOUND_MIXER_SYNTH)); + ~(1 << SOUND_MIXER_SYNTH)); mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) & - ~(1 << SOUND_MIXER_SYNTH)); + ~(1 << SOUND_MIXER_SYNTH)); } if (!err) { level = recsrc; @@ -1338,11 +1517,47 @@ sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) 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); + err = mixer_ioctl(i_dev, + MIXER_WRITE(SOUND_MIXER_RECSRC), + (caddr_t)&recsrc, -1, NULL); } return (err); } + +static int +sysctl_es_polling(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + es = pcm_getdevinfo(dev); + if (es == NULL) + return (EINVAL); + ES_LOCK(es); + val = es->polling; + ES_UNLOCK(es); + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + ES_LOCK(es); + if (val != es->polling) { + if (es_chan_active(es) != 0) + err = EBUSY; + else if (val == 0) + es->polling = 0; + else + es->polling = 1; + } + ES_UNLOCK(es); + + return (err); +} #endif /* SND_DYNSYSCTL */ static void @@ -1365,66 +1580,69 @@ es_init_sysctls(device_t dev) to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_PROC(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"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(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) { + if (es->ch[ES_DAC2].channel != NULL && + es->ch[ES_ADC].channel != NULL) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_PROC(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"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(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) { + if (es->ch[ES_DAC1].channel != NULL && + es->ch[ES_DAC2].channel != NULL) { /* XXX: an user should be able to set this with a control tool, if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_PROC(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"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(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)) + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "latency_timer", &r) == 0 && !(r < 0 || r > 255)) pci_write_config(dev, PCIR_LATTIMER, r, 1); /* XXX: this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_PROC(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"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "latency_timer", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_latency_timer, "I", + "PCI Latency Timer configuration"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es_polling, "I", + "Enable polling mode"); #endif /* SND_DYNSYSCTL */ } static int es_pci_attach(device_t dev) { - u_int32_t data; + uint32_t data; struct es_info *es = NULL; int mapped, i, numplay, dac_cfg; char status[SND_STATUSLEN]; @@ -1434,7 +1652,7 @@ es_pci_attach(device_t dev) if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); - return ENXIO; + return (ENXIO); } es->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); es->dev = dev; @@ -1450,7 +1668,7 @@ es_pci_attach(device_t dev) es->regid = MEM_MAP_REG; es->regtype = SYS_RES_MEMORY; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, - RF_ACTIVE); + RF_ACTIVE); if (es->reg) mapped++; } @@ -1458,7 +1676,7 @@ es_pci_attach(device_t dev) es->regid = PCIR_BAR(0); es->regtype = SYS_RES_IOPORT; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, - RF_ACTIVE); + RF_ACTIVE); if (es->reg) mapped++; } @@ -1469,35 +1687,61 @@ es_pci_attach(device_t dev) es->st = rman_get_bustag(es->reg); es->sh = rman_get_bushandle(es->reg); - es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); + callout_init(&es->poll_timer, CALLOUT_MPSAFE); + es->poll_ticks = 1; if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "dac", &dac_cfg) == 0) { + device_get_unit(dev), "polling", &i) == 0 && i != 0) + es->polling = 1; + else + es->polling = 0; + + es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= ~0x3f; + if (i < 0x40) + i = 0x40; + es->blkcnt = es->bufsz / i; + i = 0; + while (es->blkcnt >> i) + i++; + es->blkcnt = 1 << (i - 1); + if (es->blkcnt < 2) + es->blkcnt = 2; + else if (es->blkcnt > 256) + es->blkcnt = 256; + + } else + es->blkcnt = 2; + + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "dac", &dac_cfg) == 0) { if (dac_cfg < 0 || dac_cfg > 3) dac_cfg = ES_DEFAULT_DAC_CFG; } else dac_cfg = ES_DEFAULT_DAC_CFG; switch (dac_cfg) { - case 0: /* Enable all DAC: DAC1, DAC2 */ - numplay = 2; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); - es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); - break; - case 1: /* Only DAC1 */ - numplay = 1; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); - break; - case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ - numplay = 2; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); - es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); - break; - case 2: /* Only DAC2 */ - default: - numplay = 1; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); - break; + 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); @@ -1526,9 +1770,8 @@ es_pci_attach(device_t dev) * This is a special case for es1370 only, where the * speed of both ADC and DAC2 locked together. */ - if (!ES_DAC2_ENABLED(es->escfg)) { + if (!ES_DAC2_ENABLED(es->escfg)) es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); - } if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; @@ -1540,8 +1783,9 @@ es_pci_attach(device_t dev) es->irqid = 0; es->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &es->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, es, &es->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, + es, &es->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } @@ -1558,8 +1802,9 @@ es_pci_attach(device_t dev) } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", - (es->regtype == SYS_RES_IOPORT)? "io" : "memory", - rman_get_start(es->reg), rman_get_start(es->irq),PCM_KLDSTRING(snd_es137x)); + (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, numplay, 1)) goto bad; @@ -1569,25 +1814,30 @@ es_pci_attach(device_t dev) es_init_sysctls(dev); pcm_setstatus(dev, status); es->escfg = ES_SET_GP(es->escfg, 0); - if (numplay == 1) { + if (numplay == 1) device_printf(dev, "<Playback: DAC%d / Record: ADC>\n", - ES_DAC_FIRST(es->escfg) + 1); - } else if (numplay == 2) { + ES_DAC_FIRST(es->escfg) + 1); + else if (numplay == 2) device_printf(dev, "<Playback: DAC%d,DAC%d / Record: ADC>\n", - ES_DAC_FIRST(es->escfg) + 1, - ES_DAC_SECOND(es->escfg) + 1); - } - return 0; - - bad: - if (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); - if (es->ih) bus_teardown_intr(dev, es->irq, es->ih); - if (es->irq) bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); - if (codec) ac97_destroy(codec); - if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); - if (es->lock) snd_mtxfree(es->lock); - if (es) free(es, M_DEVBUF); - return ENXIO; + ES_DAC_FIRST(es->escfg) + 1, ES_DAC_SECOND(es->escfg) + 1); + return (0); + +bad: + if (es->parent_dmat) + bus_dma_tag_destroy(es->parent_dmat); + if (es->ih) + bus_teardown_intr(dev, es->irq, es->ih); + if (es->irq) + bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); + if (codec) + ac97_destroy(codec); + if (es->reg) + bus_release_resource(dev, es->regtype, es->regid, es->reg); + if (es->lock) + snd_mtxfree(es->lock); + if (es) + free(es, M_DEVBUF); + return (ENXIO); } static int @@ -1597,7 +1847,8 @@ es_pci_detach(device_t dev) struct es_info *es; r = pcm_unregister(dev); - if (r) return r; + if (r) + return (r); es = pcm_getdevinfo(dev); bus_teardown_intr(dev, es->irq, es->ih); @@ -1607,7 +1858,7 @@ es_pci_detach(device_t dev) snd_mtxfree(es->lock); free(es, M_DEVBUF); - return 0; + return (0); } static device_method_t es_methods[] = { diff --git a/sys/dev/sound/pci/hda/hda_reg.h b/sys/dev/sound/pci/hda/hda_reg.h index 0bf1456..7965daf 100644 --- a/sys/dev/sound/pci/hda/hda_reg.h +++ b/sys/dev/sound/pci/hda/hda_reg.h @@ -870,30 +870,30 @@ #define HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT_SHIFT 17 #define HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT_MASK 0x00010000 #define HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT_SHIFT 16 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_MASK 0x00000800 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_SHIFT 11 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_MASK 0x00000400 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_SHIFT 10 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_MASK 0x00000200 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_SHIFT 9 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_MASK 0x00000100 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_SHIFT 8 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_MASK 0x00000080 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_SHIFT 7 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_MASK 0x00000040 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_SHIFT 6 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_MASK 0x00000020 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_SHIFT 5 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_MASK 0x00000010 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_SHIFT 4 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_MASK 0x00000008 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_SHIFT 3 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_MASK 0x000000004 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_SHIFT 2 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_MASK 0x000000002 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_SHIFT 1 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_MASK 0x000000001 -#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_SHIFT 0 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_MASK 0x00000001 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_SHIFT 0 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_MASK 0x00000002 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_SHIFT 1 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_MASK 0x00000004 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_SHIFT 2 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_MASK 0x00000008 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_SHIFT 3 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_MASK 0x00000010 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_SHIFT 4 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_MASK 0x00000020 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_SHIFT 5 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_MASK 0x00000040 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_SHIFT 6 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_MASK 0x00000080 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_SHIFT 7 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_MASK 0x00000100 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_SHIFT 8 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_MASK 0x00000200 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_SHIFT 9 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_MASK 0x00000400 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_SHIFT 10 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_MASK 0x00000800 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_SHIFT 11 #define HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(param) \ (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT_MASK) >> \ diff --git a/sys/dev/sound/pci/hda/hdac.c b/sys/dev/sound/pci/hda/hdac.c index 0889e4d..649cb73 100644 --- a/sys/dev/sound/pci/hda/hdac.c +++ b/sys/dev/sound/pci/hda/hdac.c @@ -44,7 +44,7 @@ * http://people.freebsd.org/~ariff/HDA/parser.rb . This crude * ruby parser take the verbose dmesg dump as its input. Refer to * http://www.microsoft.com/whdc/device/audio/default.mspx for various - * interesting documents, especiall UAA (Universal Audio Architecture). + * interesting documents, especially UAA (Universal Audio Architecture). * 4) Possible vendor specific support. * (snd_hda_intel, snd_hda_ati, etc..) * @@ -80,7 +80,7 @@ #include "mixer_if.h" -#define HDA_DRV_TEST_REV "20061017_0033" +#define HDA_DRV_TEST_REV "20061111_0034" #define HDA_WIDGET_PARSER_REV 1 SND_DECLARE_FILE("$FreeBSD$"); @@ -197,6 +197,11 @@ SND_DECLARE_FILE("$FreeBSD$"); #define IBM_M52_SUBVENDOR HDA_MODEL_CONSTRUCT(IBM, 0x02f6) #define IBM_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(IBM, 0xffff) +/* Lenovo */ +#define LNV_VENDORID 0x17aa +#define LNV_3KN100_SUBVENDOR HDA_MODEL_CONSTRUCT(LNV, 0x2066) +#define LNV_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(LNV, 0xffff) + /* Misc constants.. */ #define HDA_AMP_MUTE_DEFAULT (0xffffffff) @@ -512,6 +517,9 @@ static void hdac_audio_ctl_amp_set_internal(struct hdac_softc *, static int hdac_audio_ctl_ossmixer_getnextdev(struct hdac_devinfo *); static struct hdac_widget *hdac_widget_get(struct hdac_devinfo *, nid_t); +static int hdac_rirb_flush(struct hdac_softc *sc); +static int hdac_unsolq_flush(struct hdac_softc *sc); + #define hdac_command(a1, a2, a3) \ hdac_command_sendone_internal(a1, a2, a3) @@ -788,7 +796,7 @@ hdac_unsolicited_handler(struct hdac_codec *codec, uint32_t tag) } } -static void +static int hdac_stream_intr(struct hdac_softc *sc, struct hdac_chan *ch) { /* XXX to be removed */ @@ -797,7 +805,7 @@ hdac_stream_intr(struct hdac_softc *sc, struct hdac_chan *ch) #endif if (ch->blkcnt == 0) - return; + return (0); /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA @@ -822,16 +830,13 @@ hdac_stream_intr(struct hdac_softc *sc, struct hdac_chan *ch) #ifdef HDAC_INTR_EXTRA if (res & HDAC_SDSTS_BCIS) { #endif - ch->prevptr = ch->ptr; - ch->ptr += sndbuf_getblksz(ch->b); - ch->ptr %= sndbuf_getsize(ch->b); - hdac_unlock(sc); - chn_intr(ch->c); - hdac_lock(sc); + return (1); /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA } #endif + + return (0); } /**************************************************************************** @@ -845,14 +850,16 @@ hdac_intr_handler(void *context) struct hdac_softc *sc; uint32_t intsts; uint8_t rirbsts; - uint8_t rirbwp; - struct hdac_rirb *rirb_base, *rirb; - nid_t ucad; - uint32_t utag; + struct hdac_rirb *rirb_base; + uint32_t trigger = 0; sc = (struct hdac_softc *)context; hdac_lock(sc); + if (sc->polling != 0) { + hdac_unlock(sc); + return; + } /* Do we have anything to do? */ intsts = HDAC_READ_4(&sc->mem, HDAC_INTSTS); if (!HDA_FLAG_MATCH(intsts, HDAC_INTSTS_GIS)) { @@ -866,26 +873,9 @@ hdac_intr_handler(void *context) rirbsts = HDAC_READ_1(&sc->mem, HDAC_RIRBSTS); /* Get as many responses that we can */ while (HDA_FLAG_MATCH(rirbsts, HDAC_RIRBSTS_RINTFL)) { - HDAC_WRITE_1(&sc->mem, HDAC_RIRBSTS, HDAC_RIRBSTS_RINTFL); - rirbwp = HDAC_READ_1(&sc->mem, HDAC_RIRBWP); - bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, - BUS_DMASYNC_POSTREAD); - while (sc->rirb_rp != rirbwp) { - sc->rirb_rp++; - sc->rirb_rp %= sc->rirb_size; - rirb = &rirb_base[sc->rirb_rp]; - if (rirb->response_ex & HDAC_RIRB_RESPONSE_EX_UNSOLICITED) { - ucad = HDAC_RIRB_RESPONSE_EX_SDATA_IN(rirb->response_ex); - utag = rirb->response >> 26; - if (ucad > -1 && ucad < HDAC_CODEC_MAX && - sc->codecs[ucad] != NULL) { - sc->unsolq[sc->unsolq_wp++] = - (ucad << 16) | - (utag & 0xffff); - sc->unsolq_wp %= HDAC_UNSOLQ_MAX; - } - } - } + HDAC_WRITE_1(&sc->mem, + HDAC_RIRBSTS, HDAC_RIRBSTS_RINTFL); + hdac_rirb_flush(sc); rirbsts = HDAC_READ_1(&sc->mem, HDAC_RIRBSTS); } /* XXX to be removed */ @@ -894,29 +884,29 @@ hdac_intr_handler(void *context) HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, HDAC_INTSTS_CIS); #endif } + + hdac_unsolq_flush(sc); + if (intsts & HDAC_INTSTS_SIS_MASK) { - if (intsts & (1 << sc->num_iss)) - hdac_stream_intr(sc, &sc->play); - if (intsts & (1 << 0)) - hdac_stream_intr(sc, &sc->rec); + if ((intsts & (1 << sc->num_iss)) && + hdac_stream_intr(sc, &sc->play) != 0) + trigger |= 1; + if ((intsts & (1 << 0)) && + hdac_stream_intr(sc, &sc->rec) != 0) + trigger |= 2; /* XXX to be removed */ #ifdef HDAC_INTR_EXTRA - HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, intsts & HDAC_INTSTS_SIS_MASK); + HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, intsts & + HDAC_INTSTS_SIS_MASK); #endif } - if (sc->unsolq_st == HDAC_UNSOLQ_READY) { - sc->unsolq_st = HDAC_UNSOLQ_BUSY; - while (sc->unsolq_rp != sc->unsolq_wp) { - ucad = sc->unsolq[sc->unsolq_rp] >> 16; - utag = sc->unsolq[sc->unsolq_rp++] & 0xffff; - sc->unsolq_rp %= HDAC_UNSOLQ_MAX; - hdac_unsolicited_handler(sc->codecs[ucad], utag); - } - sc->unsolq_st = HDAC_UNSOLQ_READY; - } - hdac_unlock(sc); + + if (trigger & 1) + chn_intr(sc->play.c); + if (trigger & 2) + chn_intr(sc->rec.c); } /**************************************************************************** @@ -1216,6 +1206,7 @@ hdac_mem_free(struct hdac_softc *sc) if (mem->mem_res != NULL) bus_release_resource(sc->dev, SYS_RES_MEMORY, mem->mem_rid, mem->mem_res); + mem->mem_res = NULL; } /**************************************************************************** @@ -1239,7 +1230,7 @@ hdac_irq_alloc(struct hdac_softc *sc) goto fail; } result = snd_setup_intr(sc->dev, irq->irq_res, INTR_MPSAFE, - hdac_intr_handler, sc, &irq->irq_handle); + hdac_intr_handler, sc, &irq->irq_handle); if (result != 0) { device_printf(sc->dev, "%s: Unable to setup interrupt handler (%x)\n", @@ -1250,9 +1241,8 @@ hdac_irq_alloc(struct hdac_softc *sc) return (0); fail: - if (irq->irq_res != NULL) - bus_release_resource(sc->dev, SYS_RES_IRQ, irq->irq_rid, - irq->irq_res); + hdac_irq_free(sc); + return (ENXIO); } @@ -1267,11 +1257,13 @@ hdac_irq_free(struct hdac_softc *sc) struct hdac_irq *irq; irq = &sc->irq; - if (irq->irq_handle != NULL) + if (irq->irq_res != NULL && irq->irq_handle != NULL) bus_teardown_intr(sc->dev, irq->irq_res, irq->irq_handle); if (irq->irq_res != NULL) bus_release_resource(sc->dev, SYS_RES_IRQ, irq->irq_rid, irq->irq_res); + irq->irq_handle = NULL; + irq->irq_res = NULL; } /**************************************************************************** @@ -1362,16 +1354,18 @@ hdac_rirb_init(struct hdac_softc *sc) sc->rirb_rp = 0; HDAC_WRITE_2(&sc->mem, HDAC_RIRBWP, HDAC_RIRBWP_RIRBWPRST); - /* Setup the interrupt threshold */ - HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, sc->rirb_size / 2); + if (sc->polling == 0) { + /* Setup the interrupt threshold */ + HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, sc->rirb_size / 2); - /* Enable Overrun and response received reporting */ + /* Enable Overrun and response received reporting */ #if 0 - HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, - HDAC_RIRBCTL_RIRBOIC | HDAC_RIRBCTL_RINTCTL); + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, + HDAC_RIRBCTL_RIRBOIC | HDAC_RIRBCTL_RINTCTL); #else - HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, HDAC_RIRBCTL_RINTCTL); + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, HDAC_RIRBCTL_RINTCTL); #endif + } /* * Make sure that the Host CPU cache doesn't contain any dirty @@ -1439,6 +1433,8 @@ hdac_scan_codecs(struct hdac_softc *sc) "Unable to allocate memory for codec\n"); continue; } + codec->commands = NULL; + codec->responses_received = 0; codec->verbs_sent = 0; codec->sc = sc; codec->cad = i; @@ -1688,14 +1684,14 @@ hdac_widget_pin_parse(struct hdac_widget *w) w->wclass.pin.config = config; pincap = hdac_command(sc, - HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_PIN_CAP), cad); + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_PIN_CAP), cad); w->wclass.pin.cap = pincap; w->wclass.pin.ctrl = hdac_command(sc, - HDA_CMD_GET_PIN_WIDGET_CTRL(cad, nid), cad) & - ~(HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | - HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | - HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE); + HDA_CMD_GET_PIN_WIDGET_CTRL(cad, nid), cad) & + ~(HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; @@ -1907,6 +1903,144 @@ hdac_widget_get(struct hdac_devinfo *devinfo, nid_t nid) return (&devinfo->widget[nid - devinfo->startnode]); } +static __inline int +hda_poll_channel(struct hdac_chan *ch) +{ + uint32_t sz, delta; + volatile uint32_t ptr; + + if (ch->active == 0) + return (0); + + sz = ch->blksz * ch->blkcnt; + ptr = HDAC_READ_4(&ch->devinfo->codec->sc->mem, ch->off + HDAC_SDLPIB); + ch->ptr = ptr; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +#define hda_chan_active(sc) ((sc)->play.active + (sc)->rec.active) + +static void +hda_poll_callback(void *arg) +{ + struct hdac_softc *sc = arg; + uint32_t trigger = 0; + + if (sc == NULL) + return; + + hdac_lock(sc); + if (sc->polling == 0 || hda_chan_active(sc) == 0) { + hdac_unlock(sc); + return; + } + + trigger |= (hda_poll_channel(&sc->play) != 0) ? 1 : 0; + trigger |= (hda_poll_channel(&sc->rec) != 0) ? 2 : 0; + + /* XXX */ + callout_reset(&sc->poll_hda, 1/*sc->poll_ticks*/, + hda_poll_callback, sc); + + hdac_unlock(sc); + + if (trigger & 1) + chn_intr(sc->play.c); + if (trigger & 2) + chn_intr(sc->rec.c); +} + +static int +hdac_rirb_flush(struct hdac_softc *sc) +{ + struct hdac_rirb *rirb_base, *rirb; + struct hdac_codec *codec; + struct hdac_command_list *commands; + nid_t cad; + uint32_t resp; + uint8_t rirbwp; + int ret = 0; + + rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; + rirbwp = HDAC_READ_1(&sc->mem, HDAC_RIRBWP); + bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, + BUS_DMASYNC_POSTREAD); + + while (sc->rirb_rp != rirbwp) { + sc->rirb_rp++; + sc->rirb_rp %= sc->rirb_size; + rirb = &rirb_base[sc->rirb_rp]; + cad = HDAC_RIRB_RESPONSE_EX_SDATA_IN(rirb->response_ex); + if (cad < 0 || cad >= HDAC_CODEC_MAX || + sc->codecs[cad] == NULL) + continue; + resp = rirb->response; + codec = sc->codecs[cad]; + commands = codec->commands; + if (rirb->response_ex & HDAC_RIRB_RESPONSE_EX_UNSOLICITED) { + sc->unsolq[sc->unsolq_wp++] = (cad << 16) | + ((resp >> 26) & 0xffff); + sc->unsolq_wp %= HDAC_UNSOLQ_MAX; + } else if (commands != NULL && commands->num_commands > 0 && + codec->responses_received < commands->num_commands) + commands->responses[codec->responses_received++] = + resp; + ret++; + } + + return (ret); +} + +static int +hdac_unsolq_flush(struct hdac_softc *sc) +{ + nid_t cad; + uint32_t tag; + int ret = 0; + + if (sc->unsolq_st == HDAC_UNSOLQ_READY) { + sc->unsolq_st = HDAC_UNSOLQ_BUSY; + while (sc->unsolq_rp != sc->unsolq_wp) { + cad = sc->unsolq[sc->unsolq_rp] >> 16; + tag = sc->unsolq[sc->unsolq_rp++] & 0xffff; + sc->unsolq_rp %= HDAC_UNSOLQ_MAX; + hdac_unsolicited_handler(sc->codecs[cad], tag); + ret++; + } + sc->unsolq_st = HDAC_UNSOLQ_READY; + } + + return (ret); +} + +static void +hdac_poll_callback(void *arg) +{ + struct hdac_softc *sc = arg; + if (sc == NULL) + return; + + hdac_lock(sc); + if (sc->polling == 0) { + hdac_unlock(sc); + return; + } + hdac_rirb_flush(sc); + hdac_unsolq_flush(sc); + callout_reset(&sc->poll_hdac, max(hz >> 2, 1), + hdac_poll_callback, sc); + hdac_unlock(sc); +} + static void hdac_stream_stop(struct hdac_chan *ch) { @@ -1918,9 +2052,50 @@ hdac_stream_stop(struct hdac_chan *ch) HDAC_SDCTL_RUN); HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); - ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); - ctl &= ~(1 << (ch->off >> 5)); - HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); + ch->active = 0; + + if (sc->polling != 0) { + int pollticks; + + if (hda_chan_active(sc) == 0) { + callout_stop(&sc->poll_hda); + sc->poll_ticks = 1; + } else { + if (sc->play.active != 0) + ch = &sc->play; + else + ch = &sc->rec; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->b) * + sndbuf_getspd(ch->b)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: pollticks=%d < 1 !\n", + __func__, pollticks); + ); + pollticks = 1; + } + if (pollticks > sc->poll_ticks) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + ); + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_hda, 1, + hda_poll_callback, sc); + } + } + } else { + ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); + ctl &= ~(1 << (ch->off >> 5)); + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); + } } static void @@ -1929,14 +2104,52 @@ hdac_stream_start(struct hdac_chan *ch) struct hdac_softc *sc = ch->devinfo->codec->sc; uint32_t ctl; - ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); - ctl |= 1 << (ch->off >> 5); - HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); + if (sc->polling != 0) { + int pollticks; - ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); - ctl |= HDAC_SDCTL_IOCE | HDAC_SDCTL_FEIE | HDAC_SDCTL_DEIE | - HDAC_SDCTL_RUN; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->b) * sndbuf_getspd(ch->b)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: pollticks=%d < 1 !\n", + __func__, pollticks); + ); + pollticks = 1; + } + if (hda_chan_active(sc) == 0 || pollticks < sc->poll_ticks) { + HDA_BOOTVERBOSE( + if (hda_chan_active(sc) == 0) { + device_printf(sc->dev, + "%s: pollticks=%d\n", + __func__, pollticks); + } else { + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + } + ); + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_hda, 1, hda_poll_callback, + sc); + } + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + ctl |= HDAC_SDCTL_RUN; + } else { + ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); + ctl |= 1 << (ch->off >> 5); + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + ctl |= HDAC_SDCTL_IOCE | HDAC_SDCTL_FEIE | HDAC_SDCTL_DEIE | + HDAC_SDCTL_RUN; + } HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); + + ch->active = 1; } static void @@ -1988,28 +2201,32 @@ static void hdac_bdl_setup(struct hdac_chan *ch) { struct hdac_softc *sc = ch->devinfo->codec->sc; - uint64_t addr; - int blks, size, blocksize; struct hdac_bdle *bdle; + uint64_t addr; + uint32_t blksz, blkcnt; int i; addr = (uint64_t)sndbuf_getbufaddr(ch->b); - size = sndbuf_getsize(ch->b); - blocksize = sndbuf_getblksz(ch->b); - blks = size / blocksize; - bdle = (struct hdac_bdle*)ch->bdl_dma.dma_vaddr; + bdle = (struct hdac_bdle *)ch->bdl_dma.dma_vaddr; - for (i = 0; i < blks; i++, bdle++) { + if (sc->polling != 0) { + blksz = ch->blksz * ch->blkcnt; + blkcnt = 1; + } else { + blksz = ch->blksz; + blkcnt = ch->blkcnt; + } + + for (i = 0; i < blkcnt; i++, bdle++) { bdle->addrl = (uint32_t)addr; bdle->addrh = (uint32_t)(addr >> 32); - bdle->len = blocksize; - bdle->ioc = 1; - - addr += blocksize; + bdle->len = blksz; + bdle->ioc = 1 ^ sc->polling; + addr += blksz; } - HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDCBL, size); - HDAC_WRITE_2(&sc->mem, ch->off + HDAC_SDLVI, blks - 1); + HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDCBL, blksz * blkcnt); + HDAC_WRITE_2(&sc->mem, ch->off + HDAC_SDLVI, blkcnt - 1); addr = ch->bdl_dma.dma_paddr; HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDBDPL, (uint32_t)addr); HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDBDPU, (uint32_t)(addr >> 32)); @@ -2046,7 +2263,7 @@ hdac_audio_ctl_amp_set_internal(struct hdac_softc *sc, nid_t cad, nid_t nid, v = (1 << (15 - dir)) | (1 << 13) | (index << 8) | (lmute << 7) | left; hdac_command(sc, - HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, v), cad); + HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, v), cad); v = (1 << (15 - dir)) | (1 << 12) | (index << 8) | (rmute << 7) | right; } else @@ -2142,14 +2359,12 @@ hdac_command_send_internal(struct hdac_softc *sc, struct hdac_codec *codec; int corbrp; uint32_t *corb; - uint8_t rirbwp; int timeout; int retry = 10; - struct hdac_rirb *rirb_base, *rirb; - nid_t ucad; - uint32_t utag; + struct hdac_rirb *rirb_base; - if (sc == NULL || sc->codecs[cad] == NULL || commands == NULL) + if (sc == NULL || sc->codecs[cad] == NULL || commands == NULL || + commands->num_commands < 1) return; codec = sc->codecs[cad]; @@ -2180,55 +2395,22 @@ hdac_command_send_internal(struct hdac_softc *sc, } timeout = 1000; - do { - rirbwp = HDAC_READ_1(&sc->mem, HDAC_RIRBWP); - bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, - BUS_DMASYNC_POSTREAD); - if (sc->rirb_rp != rirbwp) { - do { - sc->rirb_rp++; - sc->rirb_rp %= sc->rirb_size; - rirb = &rirb_base[sc->rirb_rp]; - if (rirb->response_ex & HDAC_RIRB_RESPONSE_EX_UNSOLICITED) { - ucad = HDAC_RIRB_RESPONSE_EX_SDATA_IN(rirb->response_ex); - utag = rirb->response >> 26; - if (ucad > -1 && ucad < HDAC_CODEC_MAX && - sc->codecs[ucad] != NULL) { - sc->unsolq[sc->unsolq_wp++] = - (ucad << 16) | - (utag & 0xffff); - sc->unsolq_wp %= HDAC_UNSOLQ_MAX; - } - } else if (codec->responses_received < commands->num_commands) - codec->commands->responses[codec->responses_received++] = - rirb->response; - } while (sc->rirb_rp != rirbwp); - break; - } + while (hdac_rirb_flush(sc) == 0 && --timeout) DELAY(10); - } while (--timeout); } while ((codec->verbs_sent != commands->num_commands || - codec->responses_received != commands->num_commands) && - --retry); + codec->responses_received != commands->num_commands) && --retry); if (retry == 0) device_printf(sc->dev, - "%s: TIMEOUT numcmd=%d, sent=%d, received=%d\n", - __func__, commands->num_commands, - codec->verbs_sent, codec->responses_received); + "%s: TIMEOUT numcmd=%d, sent=%d, received=%d\n", + __func__, commands->num_commands, codec->verbs_sent, + codec->responses_received); + codec->commands = NULL; + codec->responses_received = 0; codec->verbs_sent = 0; - if (sc->unsolq_st == HDAC_UNSOLQ_READY) { - sc->unsolq_st = HDAC_UNSOLQ_BUSY; - while (sc->unsolq_rp != sc->unsolq_wp) { - ucad = sc->unsolq[sc->unsolq_rp] >> 16; - utag = sc->unsolq[sc->unsolq_rp++] & 0xffff; - sc->unsolq_rp %= HDAC_UNSOLQ_MAX; - hdac_unsolicited_handler(sc->codecs[ucad], utag); - } - sc->unsolq_st = HDAC_UNSOLQ_READY; - } + hdac_unsolq_flush(sc); } @@ -2352,16 +2534,18 @@ static int hdac_channel_setspeed(kobj_t obj, void *data, uint32_t speed) { struct hdac_chan *ch = data; - uint32_t spd = 0; + uint32_t spd = 0, threshold; int i; for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; - if (spd >= speed) + threshold = spd + ((ch->pcmrates[i + 1] != 0) ? + ((ch->pcmrates[i + 1] - spd) >> 1) : 0); + if (speed < threshold) break; } - if (spd == 0) + if (spd == 0) /* impossible */ ch->spd = 48000; else ch->spd = spd; @@ -2416,11 +2600,25 @@ hdac_stream_setup(struct hdac_chan *ch) } static int -hdac_channel_setblocksize(kobj_t obj, void *data, uint32_t blocksize) +hdac_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct hdac_chan *ch = data; + struct hdac_softc *sc = ch->devinfo->codec->sc; + + blksz &= ~0x7f; + if (blksz < 0x80) + blksz = 0x80; - sndbuf_resize(ch->b, ch->blkcnt, ch->blksz); + if ((blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->b)) + blksz = sndbuf_getmaxsize(ch->b) / ch->blkcnt; + + if ((sndbuf_getblksz(ch->b) != blksz || + sndbuf_getblkcnt(ch->b) != ch->blkcnt) && + sndbuf_resize(ch->b, ch->blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, ch->blkcnt); + + ch->blksz = sndbuf_getblksz(ch->b); return (ch->blksz); } @@ -2480,26 +2678,20 @@ hdac_channel_getptr(kobj_t obj, void *data) { struct hdac_chan *ch = data; struct hdac_softc *sc = ch->devinfo->codec->sc; - int sz, delta; uint32_t ptr; hdac_lock(sc); - ptr = HDAC_READ_4(&sc->mem, ch->off + HDAC_SDLPIB); + if (sc->polling != 0) + ptr = ch->ptr; + else + ptr = HDAC_READ_4(&sc->mem, ch->off + HDAC_SDLPIB); hdac_unlock(sc); - sz = sndbuf_getsize(ch->b); - ptr %= sz; - - if (ch->dir == PCMDIR_REC) { - delta = ptr % sndbuf_getblksz(ch->b); - if (delta != 0) { - ptr -= delta; - if (ptr < delta) - ptr = sz - delta; - else - ptr -= delta; - } - } + /* + * Round to available space and force 128 bytes aligment. + */ + ptr %= ch->blksz * ch->blkcnt; + ptr &= ~0x7f; return (ptr); } @@ -2872,11 +3064,24 @@ hdac_attach(device_t dev) sc->pci_subvendor = (uint32_t)pci_get_subdevice(sc->dev) << 16; sc->pci_subvendor |= (uint32_t)pci_get_subvendor(sc->dev) & 0x0000ffff; + callout_init(&sc->poll_hda, CALLOUT_MPSAFE); + callout_init(&sc->poll_hdac, CALLOUT_MPSAFE); + + sc->poll_ticks = 1; + if (resource_int_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) + sc->polling = 1; + else + sc->polling = 0; + sc->chan_size = pcm_getbuffersize(dev, - HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_DEFAULT); + HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX); + if (resource_int_value(device_get_name(sc->dev), - device_get_unit(sc->dev), "blocksize", &i) == 0 && - i > 0) { + device_get_unit(sc->dev), "blocksize", &i) == 0 && i > 0) { + i &= ~0x7f; + if (i < 0x80) + i = 0x80; sc->chan_blkcnt = sc->chan_size / i; i = 0; while (sc->chan_blkcnt >> i) @@ -3227,15 +3432,12 @@ static const struct { uint32_t set, unset; } hdac_quirks[] = { /* - * XXX Fixed rate quirk. Other than 48000 - * sounds pretty much like train wreck. - * * XXX Force stereo quirk. Monoural recording / playback * on few codecs (especially ALC880) seems broken or * perhaps unsupported. */ { HDA_MATCH_ALL, HDA_MATCH_ALL, - HDA_QUIRK_FIXEDRATE | HDA_QUIRK_FORCESTEREO, 0 }, + HDA_QUIRK_FORCESTEREO, 0 }, { ACER_ALL_SUBVENDOR, HDA_MATCH_ALL, HDA_QUIRK_GPIO1, 0 }, { ASUS_M5200_SUBVENDOR, HDA_CODEC_ALC880, @@ -3244,6 +3446,8 @@ static const struct { HDA_QUIRK_EAPDINV, 0 }, { ASUS_A8JC_SUBVENDOR, HDA_CODEC_AD1986A, HDA_QUIRK_EAPDINV, 0 }, + { LNV_3KN100_SUBVENDOR, HDA_CODEC_AD1986A, + HDA_QUIRK_EAPDINV, 0 }, { HDA_MATCH_ALL, HDA_CODEC_CXVENICE, 0, HDA_QUIRK_FORCESTEREO }, { HDA_MATCH_ALL, HDA_CODEC_STACXXXX, @@ -3550,8 +3754,8 @@ hdac_audio_ctl_outamp_build(struct hdac_devinfo *devinfo, } w->ctlflags |= fl; return (fl); - } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX - && HDA_PARAM_PIN_CAP_INPUT_CAP(w->wclass.pin.cap) && + } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && + HDA_PARAM_PIN_CAP_INPUT_CAP(w->wclass.pin.cap) && (w->pflags & HDA_ADC_PATH)) { conndev = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; @@ -4032,9 +4236,14 @@ hdac_pcmchannel_setup(struct hdac_devinfo *devinfo, int dir) }*/ if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) continue; + if (ret == 0) { + fmtcap = w->param.supp_stream_formats; + pcmcap = w->param.supp_pcm_size_rate; + } else { + fmtcap &= w->param.supp_stream_formats; + pcmcap &= w->param.supp_pcm_size_rate; + } ch->io[ret++] = i; - fmtcap &= w->param.supp_stream_formats; - pcmcap &= w->param.supp_pcm_size_rate; } ch->io[ret] = -1; @@ -4498,6 +4707,8 @@ hdac_release_resources(struct hdac_softc *sc) return; hdac_lock(sc); + if (sc->polling != 0) + callout_stop(&sc->poll_hdac); hdac_reset(sc); hdac_unlock(sc); snd_mtxfree(sc->lock); @@ -4597,6 +4808,65 @@ hdac_config_fetch(struct hdac_softc *sc, uint32_t *on, uint32_t *off) } } +#ifdef SND_DYNSYSCTL +static int +sysctl_hdac_polling(SYSCTL_HANDLER_ARGS) +{ + struct hdac_softc *sc; + struct hdac_devinfo *devinfo; + device_t dev; + uint32_t ctl; + int err, val; + + dev = oidp->oid_arg1; + devinfo = pcm_getdevinfo(dev); + if (devinfo == NULL || devinfo->codec == NULL || + devinfo->codec->sc == NULL) + return (EINVAL); + sc = devinfo->codec->sc; + hdac_lock(sc); + val = sc->polling; + hdac_unlock(sc); + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + hdac_lock(sc); + if (val != sc->polling) { + if (hda_chan_active(sc) != 0) + err = EBUSY; + else if (val == 0) { + callout_stop(&sc->poll_hdac); + HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, + sc->rirb_size / 2); + ctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); + ctl |= HDAC_RIRBCTL_RINTCTL; + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, ctl); + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, + HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); + sc->polling = 0; + DELAY(1000); + } else { + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, 0); + HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, 0); + ctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); + ctl &= ~HDAC_RIRBCTL_RINTCTL; + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, ctl); + callout_reset(&sc->poll_hdac, 1, hdac_poll_callback, + sc); + sc->polling = 1; + DELAY(1000); + } + } + hdac_unlock(sc); + + return (err); +} +#endif + static void hdac_attach2(void *arg) { @@ -4642,7 +4912,9 @@ hdac_attach2(void *arg) device_printf(sc->dev, "HDA_DEBUG: Enabling controller interrupt...\n"); ); - HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); + if (sc->polling == 0) + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, + HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); HDAC_WRITE_4(&sc->mem, HDAC_GCTL, HDAC_READ_4(&sc->mem, HDAC_GCTL) | HDAC_GCTL_UNSOL); @@ -4779,17 +5051,24 @@ hdac_attach2(void *arg) for (i = 0; i < rcnt; i++) pcm_addchan(sc->dev, PCMDIR_REC, &hdac_channel_class, devinfo); +#ifdef SND_DYNSYSCTL + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), + sysctl_hdac_polling, "I", "Enable polling mode"); +#endif + snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s [%s]", - rman_get_start(sc->mem.mem_res), - rman_get_start(sc->irq.irq_res), - PCM_KLDSTRING(snd_hda), HDA_DRV_TEST_REV); + rman_get_start(sc->mem.mem_res), rman_get_start(sc->irq.irq_res), + PCM_KLDSTRING(snd_hda), HDA_DRV_TEST_REV); pcm_setstatus(sc->dev, status); device_printf(sc->dev, "<HDA Codec: %s>\n", hdac_codec_name(devinfo)); HDA_BOOTVERBOSE( device_printf(sc->dev, "<HDA Codec ID: 0x%08x>\n", hdac_codec_id(devinfo)); ); - device_printf(sc->dev, "<HDA Driver Revision: %s>\n", HDA_DRV_TEST_REV); + device_printf(sc->dev, "<HDA Driver Revision: %s>\n", + HDA_DRV_TEST_REV); HDA_BOOTVERBOSE( if (devinfo->function.audio.quirks != 0) { @@ -4844,6 +5123,12 @@ hdac_attach2(void *arg) device_printf(sc->dev, "+--------------------------------------+\n"); hdac_dump_pcmchannels(sc, pcnt, rcnt); ); + + if (sc->polling != 0) { + hdac_lock(sc); + callout_reset(&sc->poll_hdac, 1, hdac_poll_callback, sc); + hdac_unlock(sc); + } } /**************************************************************************** diff --git a/sys/dev/sound/pci/hda/hdac_private.h b/sys/dev/sound/pci/hda/hdac_private.h index a4752ab..eca6cd4 100644 --- a/sys/dev/sound/pci/hda/hdac_private.h +++ b/sys/dev/sound/pci/hda/hdac_private.h @@ -266,7 +266,8 @@ struct hdac_chan { struct hdac_dma bdl_dma; uint32_t spd, fmt, fmtlist[8], pcmrates[16]; uint32_t supp_stream_formats, supp_pcm_size_rate; - int ptr, prevptr, blkcnt, blksz; + uint32_t ptr, prevptr, blkcnt, blksz; + int active; int dir; int off; int sid; @@ -310,6 +311,14 @@ struct hdac_softc { int chan_size; int chan_blkcnt; + /* + * Polling + */ + int polling; + int poll_ticks; + struct callout poll_hda; + struct callout poll_hdac; + #define HDAC_UNSOLQ_MAX 64 #define HDAC_UNSOLQ_READY 0 #define HDAC_UNSOLQ_BUSY 1 diff --git a/sys/dev/sound/pci/ich.c b/sys/dev/sound/pci/ich.c index f3401e8..7a99948 100644 --- a/sys/dev/sound/pci/ich.c +++ b/sys/dev/sound/pci/ich.c @@ -36,10 +36,14 @@ SND_DECLARE_FILE("$FreeBSD$"); /* -------------------------------------------------------------------- */ -#define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ -#define ICH_DTBL_LENGTH 32 -#define ICH_DEFAULT_BUFSZ 16384 -#define ICH_MAX_BUFSZ 65536 +#define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ +#define ICH_DTBL_LENGTH 32 +#define ICH_DEFAULT_BUFSZ 16384 +#define ICH_MAX_BUFSZ 65536 +#define ICH_MIN_BUFSZ 4096 +#define ICH_DEFAULT_BLKCNT 2 +#define ICH_MAX_BLKCNT 32 +#define ICH_MIN_BLKCNT 2 #define INTEL_VENDORID 0x8086 #define SIS_VENDORID 0x1039 @@ -71,6 +75,14 @@ SND_DECLARE_FILE("$FreeBSD$"); #define ICH_UNLOCK(sc) snd_mtxunlock((sc)->ich_lock) #define ICH_LOCK_ASSERT(sc) snd_mtxassert((sc)->ich_lock) +#if 0 +#define ICH_DEBUG(stmt) do { \ + stmt \ +} while(0) +#else +#define ICH_DEBUG(stmt) +#endif + static const struct ich_type { uint16_t vendor; uint16_t devid; @@ -122,19 +134,19 @@ static const struct ich_type { /* buffer descriptor */ struct ich_desc { - volatile u_int32_t buffer; - volatile u_int32_t length; + volatile uint32_t buffer; + volatile uint32_t length; }; struct sc_info; /* channel registers */ struct sc_chinfo { - u_int32_t num:8, run:1, run_save:1; - u_int32_t blksz, blkcnt, spd; - u_int32_t regbase, spdreg; - u_int32_t imask; - u_int32_t civ; + uint32_t num:8, run:1, run_save:1; + uint32_t blksz, blkcnt, spd; + uint32_t regbase, spdreg; + uint32_t imask; + uint32_t civ; struct snd_dbuf *buffer; struct pcm_channel *channel; @@ -148,8 +160,8 @@ struct sc_chinfo { struct sc_info { device_t dev; int hasvra, hasvrm, hasmic; - unsigned int chnum, bufsz; - int sample_size, swap_reg; + unsigned int chnum, bufsz, blkcnt; + int sample_size, swap_reg, fixedrate; struct resource *nambar, *nabmbar, *irq; int regtype, nambarid, nabmbarid, irqid; @@ -161,7 +173,7 @@ struct sc_info { struct ac97_info *codec; struct sc_chinfo ch[3]; - int ac97rate; + int ac97rate, calibrated; struct ich_desc *dtbl; bus_addr_t desc_addr; struct intr_config_hook intrhook; @@ -175,7 +187,7 @@ struct sc_info { /* -------------------------------------------------------------------- */ -static u_int32_t ich_fmt[] = { +static uint32_t ich_fmt[] = { AFMT_STEREO | AFMT_S16_LE, 0 }; @@ -184,23 +196,23 @@ static struct pcmchan_caps ich_caps = {48000, 48000, ich_fmt, 0}; /* -------------------------------------------------------------------- */ /* Hardware */ -static __inline u_int32_t +static __inline uint32_t ich_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno); + return (bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno)); case 2: - return bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno); + return (bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno)); case 4: - return bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno); + return (bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno)); default: - return 0xffffffff; + return (0xffffffff); } } static __inline void -ich_wr(struct sc_info *sc, int regno, u_int32_t data, int size) +ich_wr(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: @@ -219,20 +231,20 @@ ich_wr(struct sc_info *sc, int regno, u_int32_t data, int size) static int ich_waitcd(void *devinfo) { - int i; - u_int32_t data; struct sc_info *sc = (struct sc_info *)devinfo; + uint32_t data; + int i; for (i = 0; i < ICH_TIMEOUT; i++) { data = ich_rd(sc, ICH_REG_ACC_SEMA, 1); if ((data & 0x01) == 0) - return 0; + return (0); DELAY(1); } if ((sc->flags & IGNORE_PCR) != 0) return (0); device_printf(sc->dev, "CODEC semaphore timeout\n"); - return ETIMEDOUT; + return (ETIMEDOUT); } static int @@ -243,11 +255,11 @@ ich_rdcd(kobj_t obj, void *devinfo, int regno) regno &= 0xff; ich_waitcd(sc); - return bus_space_read_2(sc->nambart, sc->nambarh, regno); + return (bus_space_read_2(sc->nambart, sc->nambarh, regno)); } static int -ich_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) +ich_wrcd(kobj_t obj, void *devinfo, int regno, uint16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; @@ -255,7 +267,7 @@ ich_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) ich_waitcd(sc); bus_space_write_2(sc->nambart, sc->nambarh, regno, data); - return 0; + return (0); } static kobj_method_t ich_ac97_methods[] = { @@ -272,13 +284,17 @@ static void ich_filldtbl(struct sc_chinfo *ch) { struct sc_info *sc = ch->parent; - u_int32_t base; + uint32_t base; int i; base = sndbuf_getbufaddr(ch->buffer); - if (ch->blksz > sc->bufsz / ch->blkcnt) - ch->blksz = sc->bufsz / ch->blkcnt; - sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz); + if ((ch->blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) + ch->blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; + if ((sndbuf_getblksz(ch->buffer) != ch->blksz || + sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && + sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, ch->blksz, ch->blkcnt); ch->blksz = sndbuf_getblksz(ch->buffer); for (i = 0; i < ICH_DTBL_LENGTH; i++) { @@ -300,7 +316,7 @@ ich_resetchan(struct sc_info *sc, int num) else if (num == 2) regbase = ICH_REG_MC_BASE; else - return ENXIO; + return (ENXIO); ich_wr(sc, regbase + ICH_REG_X_CR, 0, 1); #if 1 @@ -313,11 +329,11 @@ ich_resetchan(struct sc_info *sc, int num) for (i = 0; i < ICH_TIMEOUT; i++) { cr = ich_rd(sc, regbase + ICH_REG_X_CR, 1); if (cr == 0) - return 0; + return (0); } device_printf(sc->dev, "cannot reset channel %d\n", num); - return ENXIO; + return (ENXIO); } /* -------------------------------------------------------------------- */ @@ -341,58 +357,78 @@ ichchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel * ch->dtbl = sc->dtbl + (ch->num * ICH_DTBL_LENGTH); ch->desc_addr = sc->desc_addr + (ch->num * ICH_DTBL_LENGTH) * sizeof(struct ich_desc); - ch->blkcnt = 2; + ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; switch(ch->num) { case 0: /* play */ KASSERT(dir == PCMDIR_PLAY, ("wrong direction")); ch->regbase = ICH_REG_PO_BASE; - ch->spdreg = sc->hasvra? AC97_REGEXT_FDACRATE : 0; + ch->spdreg = (sc->hasvra) ? AC97_REGEXT_FDACRATE : 0; ch->imask = ICH_GLOB_STA_POINT; break; case 1: /* record */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_PI_BASE; - ch->spdreg = sc->hasvra? AC97_REGEXT_LADCRATE : 0; + ch->spdreg = (sc->hasvra) ? AC97_REGEXT_LADCRATE : 0; ch->imask = ICH_GLOB_STA_PIINT; break; case 2: /* mic */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_MC_BASE; - ch->spdreg = sc->hasvrm? AC97_REGEXT_MADCRATE : 0; + ch->spdreg = (sc->hasvrm) ? AC97_REGEXT_MADCRATE : 0; ch->imask = ICH_GLOB_STA_MINT; break; default: - return NULL; + return (NULL); } + if (sc->fixedrate != 0) + ch->spdreg = 0; + ICH_UNLOCK(sc); if (sndbuf_alloc(ch->buffer, sc->dmat, sc->bufsz) != 0) - return NULL; + return (NULL); 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_BDBAR, (uint32_t)(ch->desc_addr), 4); ICH_UNLOCK(sc); - return ch; + return (ch); } static int -ichchan_setformat(kobj_t obj, void *data, u_int32_t format) +ichchan_setformat(kobj_t obj, void *data, uint32_t format) { - return 0; + + ICH_DEBUG( + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + if (sc->calibrated == 0) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + + return (0); } static int -ichchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +ichchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + ICH_DEBUG( + if (sc->calibrated == 0) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + if (ch->spdreg) { int r, ac97rate; @@ -403,30 +439,37 @@ ichchan_setspeed(kobj_t obj, void *data, u_int32_t speed) ICH_UNLOCK(sc); r = (speed * 48000) / ac97rate; /* - * Cast the return value of ac97_setrate() to u_int so that + * Cast the return value of ac97_setrate() to uint64 so that * the math don't overflow into the negative range. */ - ch->spd = ((u_int)ac97_setrate(sc->codec, ch->spdreg, r) * + ch->spd = ((uint64_t)ac97_setrate(sc->codec, ch->spdreg, r) * ac97rate) / 48000; } else { ch->spd = 48000; } - return ch->spd; + return (ch->spd); } static int -ichchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +ichchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + ICH_DEBUG( + if (sc->calibrated == 0) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + 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; + return (ch->blksz); } static int @@ -435,11 +478,18 @@ ichchan_trigger(kobj_t obj, void *data, int go) struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + ICH_DEBUG( + if (sc->calibrated == 0) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + switch (go) { case PCMTRIG_START: ch->run = 1; ICH_LOCK(sc); - ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_t)(ch->desc_addr), 4); + ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE, 1); ICH_UNLOCK(sc); break; @@ -451,7 +501,7 @@ ichchan_trigger(kobj_t obj, void *data, int go) ch->run = 0; break; } - return 0; + return (0); } static int @@ -459,7 +509,14 @@ ichchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; - u_int32_t pos; + uint32_t pos; + + ICH_DEBUG( + if (sc->calibrated == 0) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); ICH_LOCK(sc); ch->civ = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1) % ch->blkcnt; @@ -467,7 +524,7 @@ ichchan_getptr(kobj_t obj, void *data) pos = ch->civ * ch->blksz; - return pos; + return (pos); } static struct pcmchan_caps * @@ -475,7 +532,16 @@ ichchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; - return ch->spdreg? &ich_vrcaps : &ich_caps; + ICH_DEBUG( + struct sc_info *sc = ch->parent; + + if (sc->calibrated == 0) + device_printf(ch->parent->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + + return ((ch->spdreg) ? &ich_vrcaps : &ich_caps); } static kobj_method_t ichchan_methods[] = { @@ -498,10 +564,18 @@ ich_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; - u_int32_t cbi, lbi, lvi, st, gs; + uint32_t cbi, lbi, lvi, st, gs; int i; ICH_LOCK(sc); + + ICH_DEBUG( + if (sc->calibrated == 0) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + gs = ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_IMASK; if (gs & (ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES)) { /* Clear resume interrupt(s) - nothing doing with them */ @@ -515,7 +589,7 @@ ich_intr(void *p) continue; gs &= ~ch->imask; st = ich_rd(sc, ch->regbase + - (sc->swap_reg ? ICH_REG_X_PICB : ICH_REG_X_SR), + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), 2); st &= ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI; if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) { @@ -542,7 +616,7 @@ ich_intr(void *p) } /* clear status bit */ ich_wr(sc, ch->regbase + - (sc->swap_reg ? ICH_REG_X_PICB : ICH_REG_X_SR), + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), st, 2); } ICH_UNLOCK(sc); @@ -564,13 +638,26 @@ ich_initsys(struct sc_info* sc) /* XXX: this should move to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_INT(snd_sysctl_tree(sc->dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(sc->dev)), + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "ac97rate", CTLFLAG_RW, &sc->ac97rate, 48000, "AC97 link rate (default = 48000)"); #endif /* SND_DYNSYSCTL */ - return 0; + return (0); +} + +static void +ich_setstatus(struct sc_info *sc) +{ + char status[SND_STATUSLEN]; + + snprintf(status, SND_STATUSLEN, + "at io 0x%lx, 0x%lx irq %ld bufsz %u %s", + rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), + rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich)); + + pcm_setstatus(sc->dev, status); } /* -------------------------------------------------------------------- */ @@ -578,16 +665,17 @@ ich_initsys(struct sc_info* sc) * function of the ac97 codec initialization code (to be investigated). */ -static -void ich_calibrate(void *arg) +static void +ich_calibrate(void *arg) { struct sc_info *sc; struct sc_chinfo *ch; struct timeval t1, t2; - u_int8_t ociv, nciv; - u_int32_t wait_us, actual_48k_rate, bytes; + uint8_t ociv, nciv; + uint32_t wait_us, actual_48k_rate, oblkcnt; sc = (struct sc_info *)arg; + ICH_LOCK(sc); ch = &sc->ch[1]; if (sc->use_intrhook) @@ -602,8 +690,13 @@ void ich_calibrate(void *arg) KASSERT(ch->regbase == ICH_REG_PI_BASE, ("wrong direction")); - bytes = sndbuf_getsize(ch->buffer) / 2; - ichchan_setblocksize(0, ch, bytes); + oblkcnt = ch->blkcnt; + ch->blkcnt = 2; + sc->calibrated = 1; + ICH_UNLOCK(sc); + ichchan_setblocksize(0, ch, sndbuf_getmaxsize(ch->buffer) >> 1); + ICH_LOCK(sc); + sc->calibrated = 0; /* * our data format is stereo, 16 bit so each sample is 4 bytes. @@ -620,20 +713,19 @@ void ich_calibrate(void *arg) /* prepare */ ociv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); nciv = ociv; - ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_t)(ch->desc_addr), 4); + ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); /* start */ microtime(&t1); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM, 1); /* wait */ - while (nciv == ociv) { + do { microtime(&t2); if (t2.tv_sec - t1.tv_sec > 1) break; nciv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); - } - microtime(&t2); + } while (nciv == ociv); /* stop */ ich_wr(sc, ch->regbase + ICH_REG_X_CR, 0, 1); @@ -641,16 +733,20 @@ void ich_calibrate(void *arg) /* reset */ DELAY(100); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); + ch->blkcnt = oblkcnt; /* turn time delta into us */ wait_us = ((t2.tv_sec - t1.tv_sec) * 1000000) + t2.tv_usec - t1.tv_usec; if (nciv == ociv) { device_printf(sc->dev, "ac97 link rate calibration timed out after %d us\n", wait_us); + sc->calibrated = 1; + ICH_UNLOCK(sc); + ich_setstatus(sc); return; } - actual_48k_rate = (bytes * 250000) / wait_us; + actual_48k_rate = ((uint64_t)ch->blksz * 250000) / wait_us; if (actual_48k_rate < 47500 || actual_48k_rate > 48500) { sc->ac97rate = actual_48k_rate; @@ -664,6 +760,10 @@ void ich_calibrate(void *arg) printf(", will use %d Hz", sc->ac97rate); printf("\n"); } + sc->calibrated = 1; + ICH_UNLOCK(sc); + + ich_setstatus(sc); return; } @@ -682,7 +782,7 @@ ich_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) static int ich_init(struct sc_info *sc) { - u_int32_t stat; + uint32_t stat; ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); DELAY(600000); @@ -706,11 +806,11 @@ ich_init(struct sc_info *sc) #endif if (ich_resetchan(sc, 0) || ich_resetchan(sc, 1)) - return ENXIO; + return (ENXIO); if (sc->hasmic && ich_resetchan(sc, 2)) - return ENXIO; + return (ENXIO); - return 0; + return (0); } static int @@ -738,14 +838,14 @@ static int ich_pci_attach(device_t dev) { uint32_t subdev; - u_int16_t extcaps; + uint16_t extcaps; uint16_t devid, vendor; struct sc_info *sc; - char status[SND_STATUSLEN]; + int i; if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); - return ENXIO; + return (ENXIO); } sc->ich_lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); @@ -807,7 +907,30 @@ ich_pci_attach(device_t dev) sc->nabmbart = rman_get_bustag(sc->nabmbar); sc->nabmbarh = rman_get_bushandle(sc->nabmbar); - sc->bufsz = pcm_getbuffersize(dev, 4096, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); + sc->bufsz = pcm_getbuffersize(dev, + ICH_MIN_BUFSZ, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); + + if (resource_int_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "blocksize", &i) == 0 && i > 0) { + sc->blkcnt = sc->bufsz / i; + i = 0; + while (sc->blkcnt >> i) + i++; + sc->blkcnt = 1 << (i - 1); + if (sc->blkcnt < ICH_MIN_BLKCNT) + sc->blkcnt = ICH_MIN_BLKCNT; + else if (sc->blkcnt > ICH_MAX_BLKCNT) + sc->blkcnt = ICH_MAX_BLKCNT; + } else + sc->blkcnt = ICH_DEFAULT_BLKCNT; + + if (resource_int_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "fixedrate", &i) == 0 && + i != 0) + sc->fixedrate = 1; + else + sc->fixedrate = 0; + if (bus_dma_tag_create(NULL, 8, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sc->bufsz, 1, 0x3ffff, 0, NULL, NULL, &sc->dmat) != 0) { @@ -854,6 +977,8 @@ ich_pci_attach(device_t dev) case 0x81c0104d: /* Sony VAIO type T */ case 0x81c5104d: /* Sony VAIO VGN B1VP/B1XP */ case 0x3089103c: /* Compaq Presario B3800 */ + case 0x82131033: /* NEC VersaPro VJ10F/BH */ + case 0x82be1033: /* NEC VersaPro VJ12F/CH */ ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); break; default: @@ -869,7 +994,7 @@ ich_pci_attach(device_t dev) sc->hasmic = ac97_getcaps(sc->codec) & AC97_CAP_MICCHANNEL; ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); - if (pcm_register(dev, sc, 1, sc->hasmic? 2 : 1)) + if (pcm_register(dev, sc, 1, (sc->hasmic) ? 2 : 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &ichchan_class, sc); /* play */ @@ -877,23 +1002,23 @@ ich_pci_attach(device_t dev) if (sc->hasmic) pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record mic */ - snprintf(status, SND_STATUSLEN, "at io 0x%lx, 0x%lx irq %ld bufsz %u %s", - rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich)); + if (sc->fixedrate == 0) { + ich_initsys(sc); - pcm_setstatus(dev, status); - - ich_initsys(sc); - - sc->intrhook.ich_func = ich_calibrate; - sc->intrhook.ich_arg = sc; - sc->use_intrhook = 1; - if (config_intrhook_establish(&sc->intrhook) != 0) { - device_printf(dev, "Cannot establish calibration hook, will calibrate now\n"); - sc->use_intrhook = 0; - ich_calibrate(sc); + sc->intrhook.ich_func = ich_calibrate; + sc->intrhook.ich_arg = sc; + sc->use_intrhook = 1; + if (config_intrhook_establish(&sc->intrhook) != 0) { + device_printf(dev, "Cannot establish calibration hook, will calibrate now\n"); + sc->use_intrhook = 0; + ich_calibrate(sc); + } + } else { + sc->calibrated = 1; + ich_setstatus(sc); } - return 0; + return (0); bad: if (sc->codec) @@ -915,7 +1040,7 @@ bad: if (sc->ich_lock) snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); - return ENXIO; + return (ENXIO); } static int @@ -926,7 +1051,7 @@ ich_pci_detach(device_t dev) r = pcm_unregister(dev); if (r) - return r; + return (r); sc = pcm_getdevinfo(dev); bus_teardown_intr(dev, sc->irq, sc->ih); @@ -937,7 +1062,7 @@ ich_pci_detach(device_t dev) bus_dma_tag_destroy(sc->dmat); snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); - return 0; + return (0); } static void @@ -979,7 +1104,7 @@ ich_pci_suspend(device_t dev) } } ICH_UNLOCK(sc); - return 0; + return (0); } static int @@ -1001,7 +1126,7 @@ ich_pci_resume(device_t dev) if (ich_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); ICH_UNLOCK(sc); - return ENXIO; + return (ENXIO); } /* Reinit mixer */ ich_pci_codec_reset(sc); @@ -1009,7 +1134,7 @@ ich_pci_resume(device_t dev) ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); - return ENXIO; + return (ENXIO); } /* Re-start DMA engines */ for (i = 0 ; i < 3; i++) { @@ -1020,7 +1145,7 @@ ich_pci_resume(device_t dev) ichchan_trigger(0, ch, PCMTRIG_START); } } - return 0; + return (0); } static device_method_t ich_methods[] = { diff --git a/sys/dev/sound/pci/via8233.c b/sys/dev/sound/pci/via8233.c index d353b51..c1f0578 100644 --- a/sys/dev/sound/pci/via8233.c +++ b/sys/dev/sound/pci/via8233.c @@ -32,7 +32,7 @@ * Grzybowski Rafal, Russell Davies, Mark Handley, Daniel O'Connor for * comments, machine time, testing patches, and patience. VIA for * providing specs. ALSA for helpful comments and some register poke - * ordering. + * ordering. */ #include <dev/sound/pcm/sound.h> @@ -62,13 +62,16 @@ SND_DECLARE_FILE("$FreeBSD$"); #define NWRCHANS 1 /* No of write channels */ #define NCHANS (NWRCHANS + NDXSCHANS + NMSGDCHANS) #define NSEGS NCHANS * SEGS_PER_CHAN /* Segments in SGD table */ +#define VIA_SEGS_MIN 2 +#define VIA_SEGS_MAX 128 +#define VIA_SEGS_DEFAULT 2 #define VIA_DEFAULT_BUFSZ 0x1000 /* we rely on this struct being packed to 64 bits */ struct via_dma_op { - volatile u_int32_t ptr; - volatile u_int32_t flags; + volatile uint32_t ptr; + volatile uint32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 @@ -83,8 +86,9 @@ struct via_chinfo { struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; - int dir, blksz; - int rbase; + int dir, rbase, active; + unsigned int blksz, blkcnt; + unsigned int ptr, prevptr; }; struct via_info { @@ -100,18 +104,20 @@ struct via_info { void *ih; struct ac97_info *codec; - unsigned int bufsz; + unsigned int bufsz, blkcnt; int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; - u_int16_t codec_caps; - u_int16_t n_dxs_registered; + uint16_t codec_caps; + uint16_t n_dxs_registered; struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; -static u_int32_t via_fmt[] = { +static uint32_t via_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, @@ -122,6 +128,23 @@ static u_int32_t via_fmt[] = { static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; +static __inline int +via_chan_active(struct via_info *via) +{ + int i, ret = 0; + + if (via == NULL) + return (0); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) + ret += via->pch[i].active; + + for (i = 0; i < NWRCHANS; i++) + ret += via->rch[i].active; + + return (ret); +} + #ifdef SND_DYNSYSCTL static int sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) @@ -140,9 +163,9 @@ sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) err = sysctl_handle_int(oidp, &new_en, sizeof(new_en), req); if (err || req->newptr == NULL) - return err; + return (err); if (new_en < 0 || new_en > 1) - return EINVAL; + return (EINVAL); if (new_en) r |= VIA_SPDIF_EN; @@ -152,7 +175,7 @@ sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) pci_write_config(dev, VIA_PCI_SPDIF, r, 1); snd_mtxunlock(via->lock); - return 0; + return (0); } static int @@ -170,15 +193,50 @@ sysctl_via8233_dxs_src(SYSCTL_HANDLER_ARGS) err = sysctl_handle_int(oidp, &val, sizeof(val), req); if (err || req->newptr == NULL) - return err; + return (err); if (val < 0 || val > 1) - return EINVAL; + return (EINVAL); snd_mtxlock(via->lock); via->dxs_src = val; snd_mtxunlock(via->lock); - return 0; + return (0); +} + +static int +sysctl_via_polling(SYSCTL_HANDLER_ARGS) +{ + struct via_info *via; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + via = pcm_getdevinfo(dev); + if (via == NULL) + return (EINVAL); + snd_mtxlock(via->lock); + val = via->polling; + snd_mtxunlock(via->lock); + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + snd_mtxlock(via->lock); + if (val != via->polling) { + if (via_chan_active(via) != 0) + err = EBUSY; + else if (val == 0) + via->polling = 0; + else + via->polling = 1; + } + snd_mtxunlock(via->lock); + + return (err); } #endif /* SND_DYNSYSCTL */ @@ -190,38 +248,41 @@ via_init_sysctls(device_t dev) if not done before 7.0-RELEASE, this needs to be converted to a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ - SYSCTL_ADD_PROC(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"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(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(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "dxs_src", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_dxs_src, "I", + "Enable VIA DXS Sample Rate Converter"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via_polling, "I", + "Enable polling mode"); #endif } -static __inline u_int32_t +static __inline uint32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(via->st, via->sh, regno); + return (bus_space_read_1(via->st, via->sh, regno)); case 2: - return bus_space_read_2(via->st, via->sh, regno); + return (bus_space_read_2(via->st, via->sh, regno)); case 4: - return bus_space_read_4(via->st, via->sh, regno); + return (bus_space_read_4(via->st, via->sh, regno)); default: - return 0xFFFFFFFF; + return (0xFFFFFFFF); } } static __inline void -via_wr(struct via_info *via, int regno, u_int32_t data, int size) +via_wr(struct via_info *via, int regno, uint32_t data, int size) { switch (size) { @@ -248,11 +309,11 @@ via_waitready_codec(struct via_info *via) /* poll until codec not busy */ for (i = 0; i < 1000; i++) { if ((via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_BUSY) == 0) - return 0; + return (0); DELAY(1); } printf("via: codec busy\n"); - return 1; + return (1); } static int @@ -263,25 +324,26 @@ via_waitvalid_codec(struct via_info *via) /* poll until codec valid */ for (i = 0; i < 1000; i++) { if (via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_CODEC00_VALID) - return 0; + return (0); DELAY(1); } printf("via: codec invalid\n"); - return 1; + return (1); } static int -via_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val) +via_write_codec(kobj_t obj, void *addr, int reg, uint32_t val) { struct via_info *via = addr; - if (via_waitready_codec(via)) return -1; + if (via_waitready_codec(via)) + return (-1); - via_wr(via, VIA_AC97_CONTROL, + via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_INDEX(reg) | VIA_AC97_DATA(val), 4); - return 0; + return (0); } static int @@ -290,23 +352,23 @@ via_read_codec(kobj_t obj, void *addr, int reg) struct via_info *via = addr; if (via_waitready_codec(via)) - return -1; + return (-1); - via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | - VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); + via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | + VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); if (via_waitready_codec(via)) - return -1; + return (-1); if (via_waitvalid_codec(via)) - return -1; + return (-1); - return via_rd(via, VIA_AC97_CONTROL, 2); + return (via_rd(via, VIA_AC97_CONTROL, 2)); } static kobj_method_t via_ac97_methods[] = { - KOBJMETHOD(ac97_read, via_read_codec), - KOBJMETHOD(ac97_write, via_write_codec), + KOBJMETHOD(ac97_read, via_read_codec), + KOBJMETHOD(ac97_write, via_write_codec), { 0, 0 } }; AC97_DECLARE(via_ac97); @@ -316,31 +378,30 @@ AC97_DECLARE(via_ac97); static int via_buildsgdt(struct via_chinfo *ch) { - u_int32_t phys_addr, flag; - int i, seg_size; + uint32_t phys_addr, flag; + int i; - seg_size = sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN; phys_addr = sndbuf_getbufaddr(ch->buffer); - for (i = 0; i < SEGS_PER_CHAN; i++) { - flag = (i == SEGS_PER_CHAN - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; - ch->sgd_table[i].ptr = phys_addr + (i * seg_size); - ch->sgd_table[i].flags = flag | seg_size; + for (i = 0; i < ch->blkcnt; i++) { + flag = (i == ch->blkcnt - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; + ch->sgd_table[i].ptr = phys_addr + (i * ch->blksz); + ch->sgd_table[i].flags = flag | ch->blksz; } - return 0; + return (0); } /* -------------------------------------------------------------------- */ /* Format setting functions */ static int -via8233wr_setformat(kobj_t obj, void *data, u_int32_t format) +via8233wr_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t f = WR_FORMAT_STOP_INDEX; + uint32_t f = WR_FORMAT_STOP_INDEX; if (format & AFMT_STEREO) f |= WR_FORMAT_STEREO; @@ -350,15 +411,15 @@ via8233wr_setformat(kobj_t obj, void *data, u_int32_t format) via_wr(via, VIA_WR0_FORMAT, f, 4); snd_mtxunlock(via->lock); - return 0; + return (0); } static int -via8233dxs_setformat(kobj_t obj, void *data, u_int32_t format) +via8233dxs_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t r, v; + uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); @@ -367,22 +428,22 @@ via8233dxs_setformat(kobj_t obj, void *data, u_int32_t format) v &= ~(VIA8233_DXS_RATEFMT_STEREO | VIA8233_DXS_RATEFMT_16BIT); if (format & AFMT_STEREO) v |= VIA8233_DXS_RATEFMT_STEREO; - if (format & AFMT_16BIT) + if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); snd_mtxunlock(via->lock); - return 0; + return (0); } static int -via8233msgd_setformat(kobj_t obj, void *data, u_int32_t format) +via8233msgd_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t s = 0xff000000; - u_int8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; + uint32_t s = 0xff000000; + uint8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; if (format & AFMT_STEREO) { v |= MC_SGD_CHANNELS(2); @@ -397,30 +458,30 @@ via8233msgd_setformat(kobj_t obj, void *data, u_int32_t format) via_wr(via, VIA_MC_SGD_FORMAT, v, 1); snd_mtxunlock(via->lock); - return 0; + return (0); } /* -------------------------------------------------------------------- */ /* Speed setting functions */ static int -via8233wr_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233wr_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) - return ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed); + return (ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed)); - return 48000; + return (48000); } static int -via8233dxs_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233dxs_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t r, v; + uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); @@ -432,19 +493,19 @@ via8233dxs_setspeed(kobj_t obj, void *data, u_int32_t speed) via_wr(via, r, v, 4); snd_mtxunlock(via->lock); - return speed; + return (speed); } static int -via8233msgd_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233msgd_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) - return ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed); + return (ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed)); - return 48000; + return (48000); } /* -------------------------------------------------------------------- */ @@ -458,8 +519,8 @@ via8233wr_getcaps(kobj_t obj, void *data) /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) - return &via_vracaps; - return &via_caps; + return (&via_vracaps); + return (&via_caps); } static struct pcmchan_caps * @@ -475,8 +536,8 @@ via8233dxs_getcaps(kobj_t obj, void *data) * conversion. */ if (via->dxs_src) - return &via_vracaps; - return &via_caps; + return (&via_vracaps); + return (&via_caps); } static struct pcmchan_caps * @@ -487,21 +548,30 @@ via8233msgd_getcaps(kobj_t obj, void *data) /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) - return &via_vracaps; - return &via_caps; + return (&via_vracaps); + return (&via_caps); } /* -------------------------------------------------------------------- */ /* Common functions */ static int -via8233chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +via8233chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct via_chinfo *ch = data; - sndbuf_resize(ch->buffer, SEGS_PER_CHAN, blocksize); + if ((blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) + blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && + sndbuf_resize(ch->buffer, ch->blkcnt, blksz) != 0) + printf("via: %s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, ch->blkcnt); + ch->blksz = sndbuf_getblksz(ch->buffer); - return ch->blksz; + + return (ch->blksz); } static int @@ -509,18 +579,23 @@ via8233chan_getptr(kobj_t obj, void *data) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t v, index, count; + uint32_t v, index, count; int ptr; 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 */ + if (via->polling != 0) { + ptr = ch->ptr; + snd_mtxunlock(via->lock); + } else { + v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); + snd_mtxunlock(via->lock); + index = v >> 24; /* Last completed buffer */ + count = v & 0x00ffffff; /* Bytes remaining */ + ptr = (index + 1) * ch->blksz - count; + ptr %= ch->blkcnt * ch->blksz; /* Wrap to available space */ + } - return ptr; + return (ptr); } static void @@ -528,8 +603,8 @@ via8233chan_reset(struct via_info *via, struct via_chinfo *ch) { via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via_wr(via, ch->rbase + VIA_RP_CONTROL, 0x00, 1); - via_wr(via, ch->rbase + VIA_RP_STATUS, - SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); + via_wr(via, ch->rbase + VIA_RP_STATUS, + SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); } /* -------------------------------------------------------------------- */ @@ -538,13 +613,14 @@ via8233chan_reset(struct via_info *via, struct via_chinfo *ch) static void via8233chan_sgdinit(struct via_info *via, struct via_chinfo *ch, int chnum) { - ch->sgd_table = &via->sgd_table[chnum * SEGS_PER_CHAN]; - ch->sgd_addr = via->sgd_addr + chnum * SEGS_PER_CHAN * sizeof(struct via_dma_op); + ch->sgd_table = &via->sgd_table[chnum * via->blkcnt]; + ch->sgd_addr = via->sgd_addr + chnum * via->blkcnt * + sizeof(struct via_dma_op); } static void* via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch = &via->rch[c->num]; @@ -553,6 +629,7 @@ via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, ch->channel = c; ch->buffer = b; ch->dir = dir; + ch->blkcnt = via->blkcnt; ch->rbase = VIA_WR_BASE(c->num); snd_mtxlock(via->lock); @@ -560,19 +637,19 @@ via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; + return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, c->num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); - return ch; + return (ch); } static void* via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch = &via->pch[c->num]; @@ -581,6 +658,7 @@ via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, ch->channel = c; ch->buffer = b; ch->dir = dir; + ch->blkcnt = via->blkcnt; /* * All cards apparently support DXS3, but not other DXS @@ -593,19 +671,19 @@ via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, snd_mtxunlock(via->lock); if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; + return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + c->num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); - return ch; + return (ch); } static void* via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; struct via_chinfo *ch = &via->pch[c->num]; @@ -615,16 +693,17 @@ via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, ch->buffer = b; ch->dir = dir; ch->rbase = VIA_MC_SGD_STATUS; + ch->blkcnt = via->blkcnt; if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; + return (NULL); snd_mtxlock(via->lock); via8233chan_sgdinit(via, ch, NWRCHANS + c->num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); - return ch; + return (ch); } static void @@ -635,7 +714,8 @@ via8233chan_mute(struct via_info *via, struct via_chinfo *ch, int muted) muted = (muted) ? VIA8233_DXS_MUTE : 0; via_wr(via, ch->rbase + VIA8233_RP_DXS_LVOL, muted, 1); via_wr(via, ch->rbase + VIA8233_RP_DXS_RVOL, muted, 1); - r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & VIA8233_DXS_MUTE; + r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & + VIA8233_DXS_MUTE; if (r != muted) { printf("via: failed to set dxs volume " "(dxs base 0x%02x).\n", ch->rbase); @@ -643,11 +723,125 @@ via8233chan_mute(struct via_info *via, struct via_chinfo *ch, int muted) } } +static __inline int +via_poll_channel(struct via_chinfo *ch) +{ + struct via_info *via; + uint32_t sz, delta; + uint32_t v, index, count; + int ptr; + + if (ch == NULL || ch->channel == NULL || ch->active == 0) + return (0); + + via = ch->parent; + sz = ch->blksz * ch->blkcnt; + v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); + index = v >> 24; + count = v & 0x00ffffff; + ptr = ((index + 1) * ch->blksz) - count; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + ch->ptr = ptr; + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +static void +via_poll_callback(void *arg) +{ + struct via_info *via = arg; + uint32_t ptrigger = 0, rtrigger = 0; + int i; + + if (via == NULL) + return; + + snd_mtxlock(via->lock); + if (via->polling == 0 || via_chan_active(via) == 0) { + snd_mtxunlock(via->lock); + return; + } + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) + ptrigger |= (via_poll_channel(&via->pch[i]) != 0) ? + (1 << i) : 0; + + for (i = 0; i < NWRCHANS; i++) + rtrigger |= (via_poll_channel(&via->rch[i]) != 0) ? + (1 << i) : 0; + + /* XXX */ + callout_reset(&via->poll_timer, 1/*via->poll_ticks*/, + via_poll_callback, via); + + snd_mtxunlock(via->lock); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + if (ptrigger & (1 << i)) + chn_intr(via->pch[i].channel); + } + for (i = 0; i < NWRCHANS; i++) { + if (rtrigger & (1 << i)) + chn_intr(via->rch[i].channel); + } +} + +static int +via_poll_ticks(struct via_info *via) +{ + struct via_chinfo *ch; + int i; + int ret = hz; + int pollticks; + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + ch = &via->pch[i]; + if (ch->channel == NULL || ch->active == 0) + continue; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks < ret) + ret = pollticks; + } + + for (i = 0; i < NWRCHANS; i++) { + ch = &via->rch[i]; + if (ch->channel == NULL || ch->active == 0) + continue; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks < ret) + ret = pollticks; + } + + return (ret); +} + static int via8233chan_trigger(kobj_t obj, void* data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + int pollticks; snd_mtxlock(via->lock); switch(go) { @@ -655,53 +849,105 @@ via8233chan_trigger(kobj_t obj, void* data, int go) via_buildsgdt(ch); via8233chan_mute(via, ch, 0); via_wr(via, ch->rbase + VIA_RP_TABLE_PTR, ch->sgd_addr, 4); + if (via->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (via_chan_active(via) == 0 || + pollticks < via->poll_ticks) { + if (bootverbose) { + if (via_chan_active(via) == 0) + printf("%s: pollticks=%d\n", + __func__, pollticks); + else + printf("%s: " + "pollticks %d -> %d\n", + __func__, via->poll_ticks, + pollticks); + } + via->poll_ticks = pollticks; + callout_reset(&via->poll_timer, 1, + via_poll_callback, via); + } + } via_wr(via, ch->rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + ((via->polling == 0) ? + (SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG) : 0), 1); + ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via8233chan_mute(via, ch, 1); via8233chan_reset(via, ch); + ch->active = 0; + if (via->polling != 0) { + if (via_chan_active(via) == 0) { + callout_stop(&via->poll_timer); + via->poll_ticks = 1; + } else { + pollticks = via_poll_ticks(via); + if (pollticks > via->poll_ticks) { + if (bootverbose) + printf("%s: pollticks " + "%d -> %d\n", + __func__, via->poll_ticks, + pollticks); + via->poll_ticks = pollticks; + callout_reset(&via->poll_timer, + 1, via_poll_callback, + via); + } + } + } + break; + default: break; } snd_mtxunlock(via->lock); - return 0; + return (0); } static kobj_method_t via8233wr_methods[] = { - KOBJMETHOD(channel_init, via8233wr_init), - KOBJMETHOD(channel_setformat, via8233wr_setformat), - KOBJMETHOD(channel_setspeed, via8233wr_setspeed), - KOBJMETHOD(channel_getcaps, via8233wr_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233wr_init), + KOBJMETHOD(channel_setformat, via8233wr_setformat), + KOBJMETHOD(channel_setspeed, via8233wr_setspeed), + KOBJMETHOD(channel_getcaps, via8233wr_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233wr); static kobj_method_t via8233dxs_methods[] = { - KOBJMETHOD(channel_init, via8233dxs_init), - KOBJMETHOD(channel_setformat, via8233dxs_setformat), - KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), - KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233dxs_init), + KOBJMETHOD(channel_setformat, via8233dxs_setformat), + KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), + KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233dxs); static kobj_method_t via8233msgd_methods[] = { - KOBJMETHOD(channel_init, via8233msgd_init), - KOBJMETHOD(channel_setformat, via8233msgd_setformat), - KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), - KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233msgd_init), + KOBJMETHOD(channel_setformat, via8233msgd_setformat), + KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), + KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233msgd); @@ -712,55 +958,56 @@ static void via_intr(void *p) { struct via_info *via = p; + uint32_t ptrigger = 0, rtrigger = 0; int i, reg, stat; - /* Poll playback channels */ snd_mtxlock(via->lock); + if (via->polling != 0) { + snd_mtxunlock(via->lock); + return; + } + /* Poll playback channels */ for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { - if (via->pch[i].channel == NULL) + if (via->pch[i].channel == NULL || via->pch[i].active == 0) continue; reg = via->pch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || - !(stat & SGD_STATUS_ACTIVE))) { - via_wr(via, - via->pch[i].rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | - SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | - SGD_CONTROL_I_FLAG, 1); - } + !(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); + ptrigger |= 1 << i; } } - /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { - if (via->rch[i].channel == NULL) + if (via->rch[i].channel == NULL || via->rch[i].active == 0) continue; reg = via->rch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || - !(stat & SGD_STATUS_ACTIVE))) { - via_wr(via, - via->rch[i].rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | - SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | - SGD_CONTROL_I_FLAG, 1); - } + !(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); + rtrigger |= 1 << i; } } snd_mtxunlock(via->lock); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + if (ptrigger & (1 << i)) + chn_intr(via->pch[i].channel); + } + for (i = 0; i < NWRCHANS; i++) { + if (rtrigger & (1 << i)) + chn_intr(via->rch[i].channel); + } } /* @@ -772,33 +1019,33 @@ via_probe(device_t dev) switch(pci_get_devid(dev)) { case VIA8233_PCI_ID: switch(pci_get_revid(dev)) { - case VIA8233_REV_ID_8233PRE: + case VIA8233_REV_ID_8233PRE: device_set_desc(dev, "VIA VT8233 (pre)"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233C: device_set_desc(dev, "VIA VT8233C"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233: device_set_desc(dev, "VIA VT8233"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233A: device_set_desc(dev, "VIA VT8233A"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8235: device_set_desc(dev, "VIA VT8235"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8251: device_set_desc(dev, "VIA VT8251"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ - return BUS_PROBE_DEFAULT; - } + return (BUS_PROBE_DEFAULT); + } } - return ENXIO; + return (ENXIO); } static void @@ -811,7 +1058,7 @@ dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) static int via_chip_init(device_t dev) { - u_int32_t data, cnt; + uint32_t data, cnt; /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); @@ -819,32 +1066,32 @@ via_chip_init(device_t dev) if ((data & VIA_PCI_ACLINK_C00_READY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ /* Assert low */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); /* Wait T_rst_low */ - DELAY(100); + DELAY(100); /* Assert high */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); /* Wait T_rst2clk */ DELAY(5); /* Assert low */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); } else { /* Warm reset */ /* Force no sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); DELAY(100); /* Sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); /* Wait T_sync_high */ DELAY(5); /* Force no sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); /* Wait T_sync2clk */ DELAY(5); } @@ -855,13 +1102,12 @@ via_chip_init(device_t dev) /* Wait for codec to become ready (largest reported delay 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); - if (data & VIA_PCI_ACLINK_C00_READY) { - return 0; - } + if (data & VIA_PCI_ACLINK_C00_READY) + return (0); DELAY(5000); } device_printf(dev, "primary codec not ready (cnt = 0x%02x)\n", cnt); - return ENXIO; + return (ENXIO); } static int @@ -870,14 +1116,24 @@ 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; + int nsegs; uint32_t revid; if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); - return ENXIO; + return (ENXIO); } via->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + callout_init(&via->poll_timer, CALLOUT_MPSAFE); + via->poll_ticks = 1; + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "polling", &i) == 0 && i != 0) + via->polling = 1; + else + via->polling = 0; + pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); @@ -891,76 +1147,31 @@ via_attach(device_t dev) via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); - via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); - via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!via->irq || - snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!via->irq || + snd_setup_intr(dev, via->irq, INTR_MPSAFE, + via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } - /* DMA tag for buffers */ - 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*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/NULL, - /*lockarg*/NULL, &via->parent_dmat) != 0) { - device_printf(dev, "unable to create dma tag\n"); - goto bad; - } - - /* - * DMA tag for SGD table. The 686 uses scatter/gather DMA and - * requires a list in memory of work to do. We need only 16 bytes - * for this list, and it is wasteful to allocate 16K. - */ - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, - /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, - /*highaddr*/BUS_SPACE_MAXADDR, - /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/NSEGS * sizeof(struct via_dma_op), - /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/NULL, - /*lockarg*/NULL, &via->sgd_dmat) != 0) { - device_printf(dev, "unable to create dma tag\n"); - goto bad; - } - - if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, - BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) - goto bad; - if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, - NSEGS * sizeof(struct via_dma_op), dma_cb, via, 0)) - goto bad; - - if (via_chip_init(dev)) - goto bad; - - via->codec = AC97_CREATE(dev, via, via_ac97); - if (!via->codec) - goto bad; - - mixer_init(dev, ac97_getmixerclass(), via->codec); - - via->codec_caps = ac97_getextcaps(via->codec); - - /* Try to set VRA without generating an error, VRM not reqrd yet */ - if (via->codec_caps & - (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { - u_int16_t ext = ac97_getextmode(via->codec); - ext |= (via->codec_caps & - (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); - ext &= ~AC97_EXTCAP_DRA; - ac97_setextmode(via->codec, ext); - } - - snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", - rman_get_start(via->reg), rman_get_start(via->irq),PCM_KLDSTRING(snd_via8233)); + via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + via->blkcnt = via->bufsz / i; + i = 0; + while (via->blkcnt >> i) + i++; + via->blkcnt = 1 << (i - 1); + if (via->blkcnt < VIA_SEGS_MIN) + via->blkcnt = VIA_SEGS_MIN; + else if (via->blkcnt > VIA_SEGS_MAX) + via->blkcnt = VIA_SEGS_MAX; + + } else + via->blkcnt = VIA_SEGS_DEFAULT; revid = pci_get_revid(dev); @@ -985,8 +1196,8 @@ via_attach(device_t dev) */ via_dxs_disabled = 1; } else if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "via_dxs_disabled", - &via_dxs_disabled) == 0) + 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; @@ -996,12 +1207,12 @@ via_attach(device_t dev) via_sgd_chnum = 1; } else { if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "via_dxs_channels", - &via_dxs_chnum) != 0) + 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) + device_get_unit(dev), "via_sgd_channels", + &via_sgd_chnum) != 0) via_sgd_chnum = NMSGDCHANS; } if (via_dxs_chnum > NDXSCHANS) @@ -1018,11 +1229,74 @@ via_attach(device_t dev) 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) + device_get_unit(dev), "via_dxs_src", &via_dxs_src) == 0) via->dxs_src = (via_dxs_src > 0) ? 1 : 0; else via->dxs_src = 0; + + nsegs = (via_dxs_chnum + via_sgd_chnum) * via->blkcnt; + + /* DMA tag for buffers */ + 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*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + /* + * DMA tag for SGD table. The 686 uses scatter/gather DMA and + * requires a list in memory of work to do. We need only 16 bytes + * for this list, and it is wasteful to allocate 16K. + */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/nsegs * sizeof(struct via_dma_op), + /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->sgd_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, + BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) + goto bad; + if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, + nsegs * sizeof(struct via_dma_op), dma_cb, via, 0)) + goto bad; + + if (via_chip_init(dev)) + goto bad; + + via->codec = AC97_CREATE(dev, via, via_ac97); + if (!via->codec) + goto bad; + + mixer_init(dev, ac97_getmixerclass(), via->codec); + + via->codec_caps = ac97_getextcaps(via->codec); + + /* Try to set VRA without generating an error, VRM not reqrd yet */ + if (via->codec_caps & + (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { + uint16_t ext = ac97_getextmode(via->codec); + ext |= (via->codec_caps & + (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); + ext &= ~AC97_EXTCAP_DRA; + ac97_setextmode(via->codec, ext); + } + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", + rman_get_start(via->reg), rman_get_start(via->irq), + PCM_KLDSTRING(snd_via8233)); + /* Register */ if (pcm_register(dev, via, via_dxs_chnum + via_sgd_chnum, NWRCHANS)) goto bad; @@ -1035,24 +1309,32 @@ via_attach(device_t dev) if (via_dxs_chnum > 0) via_init_sysctls(dev); device_printf(dev, "<VIA DXS %sabled: DXS%s %d / SGD %d / REC %d>\n", - (via_dxs_chnum > 0) ? "En" : "Dis", - (via->dxs_src) ? "(SRC)" : "", - via_dxs_chnum, via_sgd_chnum, NWRCHANS); + (via_dxs_chnum > 0) ? "En" : "Dis", (via->dxs_src) ? "(SRC)" : "", + via_dxs_chnum, via_sgd_chnum, NWRCHANS); pcm_setstatus(dev, status); - return 0; + return (0); bad: - if (via->codec) ac97_destroy(via->codec); - if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); - if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); - if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); - if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); - if (via->sgd_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; + if (via->codec) + ac97_destroy(via->codec); + if (via->reg) + bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); + if (via->ih) + bus_teardown_intr(dev, via->irq, via->ih); + if (via->irq) + bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); + if (via->parent_dmat) + bus_dma_tag_destroy(via->parent_dmat); + if (via->sgd_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); } static int @@ -1062,7 +1344,8 @@ via_detach(device_t dev) struct via_info *via = 0; r = pcm_unregister(dev); - if (r) return r; + if (r) + return (r); via = pcm_getdevinfo(dev); bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); @@ -1073,7 +1356,7 @@ via_detach(device_t dev) bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); - return 0; + return (0); } diff --git a/sys/dev/sound/pcm/ac97.c b/sys/dev/sound/pcm/ac97.c index 9b3380b..46ceef0 100644 --- a/sys/dev/sound/pcm/ac97.c +++ b/sys/dev/sound/pcm/ac97.c @@ -28,6 +28,8 @@ #include <dev/sound/pcm/ac97.h> #include <dev/sound/pcm/ac97_patch.h> +#include <dev/pci/pcivar.h> + #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); @@ -791,7 +793,7 @@ struct ac97_info * ac97_create(device_t dev, void *devinfo, kobj_class_t cls) { struct ac97_info *codec; - int eapd_inv; + int eapdinv; codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT | M_ZERO); if (codec == NULL) @@ -811,8 +813,8 @@ ac97_create(device_t dev, void *devinfo, kobj_class_t cls) 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) + "eapdinv", &eapdinv) == 0) { + if (eapdinv != 0) codec->flags |= AC97_F_EAPD_INV; } return codec; @@ -842,11 +844,66 @@ ac97_getflags(struct ac97_info *codec) /* -------------------------------------------------------------------- */ +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_ac97_eapd(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 + +static void +ac97_init_sysctl(struct ac97_info *codec) +{ +#ifdef SND_DYNSYSCTL + u_int16_t orig, val; + + if (codec == NULL || codec->dev == NULL) + return; + 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; + SYSCTL_ADD_PROC(device_get_sysctl_ctx(codec->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(codec->dev)), + OID_AUTO, "eapd", CTLTYPE_INT | CTLFLAG_RW, + codec, sizeof(codec), sysctl_hw_snd_ac97_eapd, + "I", "AC97 External Amplifier"); +#endif +} + static int ac97mix_init(struct snd_mixer *m) { struct ac97_info *codec = mix_getdevinfo(m); struct snddev_info *d; + u_int32_t subvendor; u_int32_t i, mask; if (codec == NULL) @@ -857,20 +914,30 @@ ac97mix_init(struct snd_mixer *m) switch (codec->id) { case 0x41445374: /* AD1981B */ -#if 0 - mask = 0; - if (codec->mix[SOUND_MIXER_OGAIN].enable) - mask |= SOUND_MASK_OGAIN; - if (codec->mix[SOUND_MIXER_PHONEOUT].enable) - mask |= SOUND_MASK_PHONEOUT; - /* Tie ogain/phone to master volume */ - if (codec->mix[SOUND_MIXER_VOLUME].enable) - mix_setparentchild(m, SOUND_MIXER_VOLUME, mask); - else { - mix_setparentchild(m, SOUND_MIXER_VOLUME, mask); - mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + subvendor = (u_int32_t)pci_get_subdevice(codec->dev) << 16; + subvendor |= (u_int32_t)pci_get_subvendor(codec->dev) & + 0x0000ffff; + /* IBM Thinkcentre */ + if (subvendor == 0x02d91014) { + /* Enable headphone jack sensing */ + ac97_wrcd(codec, 0x72, ac97_rdcd(codec, 0x72) | + 0x0800); + mask = 0; + if (codec->mix[SOUND_MIXER_OGAIN].enable) + mask |= SOUND_MASK_OGAIN; + if (codec->mix[SOUND_MIXER_PHONEOUT].enable) + mask |= SOUND_MASK_PHONEOUT; + /* Tie ogain/phone to master volume */ + if (codec->mix[SOUND_MIXER_VOLUME].enable) + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + else { + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + mix_setrealdev(m, SOUND_MIXER_VOLUME, + SOUND_MIXER_NONE); + } } -#endif break; case 0x434d4941: /* CMI9738 */ case 0x434d4961: /* CMI9739 */ @@ -906,6 +973,9 @@ ac97mix_init(struct snd_mixer *m) for (i = 0; i < 32; i++) mask |= codec->mix[i].recidx? 1 << i : 0; mix_setrecdevs(m, mask); + + ac97_init_sysctl(codec); + return 0; } @@ -975,5 +1045,3 @@ ac97_getmixerclass(void) { return &ac97mixer_class; } - - diff --git a/sys/dev/sound/pcm/ac97.h b/sys/dev/sound/pcm/ac97.h index 8266fb5..f73dc76 100644 --- a/sys/dev/sound/pcm/ac97.h +++ b/sys/dev/sound/pcm/ac97.h @@ -82,7 +82,6 @@ #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) @@ -105,4 +104,3 @@ u_int16_t ac97_getcaps(struct ac97_info *codec); u_int16_t ac97_rdcd(struct ac97_info *codec, int reg); void ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val); - diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index 11c1bfb..e12765e 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -254,10 +254,7 @@ sndbuf_clear(struct snd_dbuf *b, unsigned int length) if (length > b->bufsize) length = b->bufsize; - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; + data = sndbuf_zerodata(b->fmt); i = sndbuf_getfreeptr(b); p = sndbuf_getbuf(b); @@ -278,18 +275,8 @@ sndbuf_clear(struct snd_dbuf *b, unsigned int length) void sndbuf_fillsilence(struct snd_dbuf *b) { - int i; - u_char data, *p; - - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; - - i = 0; - p = sndbuf_getbuf(b); - while (i < b->bufsize) - p[i++] = data; + if (b->bufsize > 0) + memset(sndbuf_getbuf(b), sndbuf_zerodata(b->fmt), b->bufsize); b->rp = 0; b->rl = b->bufsize; } @@ -543,6 +530,52 @@ sndbuf_updateprevtotal(struct snd_dbuf *b) b->prev_total = b->total; } +unsigned int +snd_xbytes(unsigned int v, unsigned int from, unsigned int to) +{ + unsigned int w, x, y; + + if (from == to) + return v; + + if (from == 0 || to == 0 || v == 0) + return 0; + + x = from; + y = to; + while (y != 0) { + w = x % y; + x = y; + y = w; + } + from /= x; + to /= x; + + return (unsigned int)(((u_int64_t)v * to) / from); +} + +unsigned int +sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to) +{ + if (from == NULL || to == NULL || v == 0) + return 0; + + return snd_xbytes(v, sndbuf_getbps(from) * sndbuf_getspd(from), + sndbuf_getbps(to) * sndbuf_getspd(to)); +} + +u_int8_t +sndbuf_zerodata(u_int32_t fmt) +{ + if (fmt & AFMT_SIGNED) + return (0x00); + else if (fmt & AFMT_MU_LAW) + return (0x7f); + else if (fmt & AFMT_A_LAW) + return (0x55); + return (0x80); +} + /************************************************************/ /** @@ -624,15 +657,20 @@ sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) { + unsigned int cnt; + KASSERT(count > 0, ("can't feed 0 bytes")); if (sndbuf_getfree(to) < count) return EINVAL; - count = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); - if (count) - sndbuf_acquire(to, to->tmpbuf, count); - /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + do { + cnt = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); + if (cnt) + sndbuf_acquire(to, to->tmpbuf, cnt); + /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + count -= cnt; + } while (count && cnt); return 0; } @@ -682,12 +720,8 @@ sndbuf_clearshadow(struct snd_dbuf *b) KASSERT(b != NULL, ("b is a null pointer")); KASSERT(b->sl >= 0, ("illegal shadow length")); - if ((b->shadbuf != NULL) && (b->sl > 0)) { - if (b->fmt & AFMT_SIGNED) - memset(b->shadbuf, 0x00, b->sl); - else - memset(b->shadbuf, 0x80, b->sl); - } + if ((b->shadbuf != NULL) && (b->sl > 0)) + memset(b->shadbuf, sndbuf_zerodata(b->fmt), b->sl); } #ifdef OSSV4_EXPERIMENT diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h index 07724be..67e08a7 100644 --- a/sys/dev/sound/pcm/buffer.h +++ b/sys/dev/sound/pcm/buffer.h @@ -107,6 +107,9 @@ unsigned int sndbuf_getreadyptr(struct snd_dbuf *b); unsigned int sndbuf_getblocks(struct snd_dbuf *b); unsigned int sndbuf_getprevblocks(struct snd_dbuf *b); unsigned int sndbuf_gettotal(struct snd_dbuf *b); +unsigned int snd_xbytes(unsigned int v, unsigned int from, unsigned int to); +unsigned int sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to); +u_int8_t sndbuf_zerodata(u_int32_t fmt); void sndbuf_updateprevtotal(struct snd_dbuf *b); int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count); diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 43ef365..d88f0e2 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -41,32 +41,60 @@ SND_DECLARE_FILE("$FreeBSD$"); #define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) +#define BUF_PARENT(c, b) \ + (((c) != NULL && (c)->parentchannel != NULL && \ + (c)->parentchannel->bufhard != NULL) ? \ + (c)->parentchannel->bufhard : (b)) + /* #define DEB(x) x */ -static int chn_targetirqrate = 32; -TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate); +int report_soft_formats = 1; +SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, + &report_soft_formats, 1, "report software-emulated formats"); + +int chn_latency = CHN_LATENCY_DEFAULT; +TUNABLE_INT("hw.snd.latency", &chn_latency); static int -sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) { int err, val; - val = chn_targetirqrate; + val = chn_latency; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 16 || val > 512) + if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) err = EINVAL; else - chn_targetirqrate = val; + chn_latency = val; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); -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"); +SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency, "I", + "buffering latency (0=low ... 10=high)"); + +int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; +TUNABLE_INT("hw.snd.latency_profile", &chn_latency_profile); + +static int +sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_latency_profile; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) + err = EINVAL; + else + chn_latency_profile = val; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", + "buffering latency profile (0=aggresive 1=safe)"); /** * @brief Channel sync group lock @@ -173,7 +201,7 @@ chn_wakeup(struct pcm_channel *c) } } - wakeup(bs); + wakeup_one(bs); } static int @@ -220,13 +248,23 @@ chn_dmaupdate(struct pcm_channel *c) 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 (snd_verbose > 2 && (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; } @@ -270,9 +308,11 @@ chn_wrfeed(struct pcm_channel *c) 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)); + 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; /* @@ -313,10 +353,10 @@ chn_wrintr(struct pcm_channel *c) int chn_write(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, newsize, count, sz; + int ret, newsize, count, sz; struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int t, x, togo, p; CHN_LOCKASSERT(c); /* @@ -356,14 +396,13 @@ chn_write(struct pcm_channel *c, struct uio *buf) */ ret = EAGAIN; } else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - timeout = 1; - ret = chn_sleep(c, "pcmwr", timeout); + ret = chn_sleep(c, "pcmwr", c->timeout); if (ret == EWOULDBLOCK) { - count -= timeout; + count -= c->timeout; ret = 0; + } else if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; + return ret; } else if (ret == 0) count = hz; } @@ -378,7 +417,7 @@ chn_write(struct pcm_channel *c, struct uio *buf) * unlock-uiomove-lock sequence. */ togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && togo > 0) { p = sndbuf_getfreeptr(bs); t = MIN(togo, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); @@ -389,7 +428,7 @@ chn_write(struct pcm_channel *c, struct uio *buf) x = sndbuf_acquire(bs, NULL, t); } ret = 0; - if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) + if (!(c->flags & CHN_F_TRIGGERED)) chn_start(c, 0); } } @@ -411,7 +450,7 @@ chn_rddump(struct pcm_channel *c, unsigned int cnt) CHN_LOCKASSERT(c); #if 0 - static uint32_t kk = 0; + static u_int32_t kk = 0; printf("%u: dumping %d bytes\n", ++kk, cnt); #endif c->xruns++; @@ -501,10 +540,10 @@ chn_rdintr(struct pcm_channel *c) int chn_read(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, sz, count; - struct snd_dbuf *bs = c->bufsoft; + int ret, sz, count; + struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int t, x, togo, p; CHN_LOCKASSERT(c); if (!(c->flags & CHN_F_TRIGGERED)) @@ -522,7 +561,7 @@ chn_read(struct pcm_channel *c, struct uio *buf) * unlock-uiomove-lock sequence. */ togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && togo > 0) { p = sndbuf_getreadyptr(bs); t = MIN(togo, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); @@ -534,20 +573,18 @@ chn_read(struct pcm_channel *c, struct uio *buf) } ret = 0; } else { - if (c->flags & CHN_F_NBIO) { + if (c->flags & CHN_F_NBIO) ret = EWOULDBLOCK; - } else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - ret = chn_sleep(c, "pcmrd", timeout); + else { + ret = chn_sleep(c, "pcmrd", c->timeout); if (ret == EWOULDBLOCK) { - count -= timeout; + count -= c->timeout; ret = 0; - } else { + } else if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; + return ret; + } else count = hz; - } - } } } @@ -584,27 +621,49 @@ chn_start(struct pcm_channel *c, int force) if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return EINVAL; - i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); - j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b); - if (force || (i >= j)) { - c->flags |= CHN_F_TRIGGERED; - /* - * if we're starting because a vchan started, don't feed any data - * or it becomes impossible to start vchans synchronised with the - * first one. the hardbuf should be empty so we top it up with - * silence to give it something to chew. the real data will be - * 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); + if (force) { + i = 1; + j = 0; + } else { + if (c->direction == PCMDIR_REC) { + i = sndbuf_getfree(bs); + j = sndbuf_getready(b); + } else { + struct snd_dbuf *pb; + + i = sndbuf_getready(bs); + + pb = BUF_PARENT(c, b); + j = min(sndbuf_xbytes(sndbuf_getsize(pb), pb, bs), + sndbuf_getsize(bs)); } + if (snd_verbose > 3 && SLIST_EMPTY(&c->children)) + printf("%s: PCMDIR_%s (%s) threshold i=%d j=%d\n", + __func__, + (c->direction == PCMDIR_PLAY) ? "PLAY" : "REC", + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + i, j); + } + + if (i >= j) { + c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); + c->feedcount = (c->flags & CHN_F_CLOSING) ? 2 : 0; + c->interrupts = 0; c->xruns = 0; + if (c->direction == PCMDIR_PLAY && c->parentchannel == NULL) { + chn_wrfeed(c); + if (snd_verbose > 3) + printf("%s: %s starting! (%s) (ready=%d " + "force=%d i=%d j=%d intrtimeout=%u)\n", + __func__, + (c->flags & CHN_F_HAS_VCHAN) ? + "VCHAN" : "HW", + (c->flags & CHN_F_CLOSING) ? "closing" : + "running", + sndbuf_getready(b), + force, i, j, c->timeout); + } chn_trigger(c, PCMTRIG_START); return 0; } @@ -631,34 +690,91 @@ chn_resetbuf(struct pcm_channel *c) int chn_sync(struct pcm_channel *c, int threshold) { - u_long rdy; - int ret; - struct snd_dbuf *bs = c->bufsoft; + int ret, count, hcount, minflush, resid, residp; + struct snd_dbuf *b, *bs; CHN_LOCKASSERT(c); + if (c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) + return 0; + + if (c->direction != PCMDIR_PLAY) + return EINVAL; + + bs = c->bufsoft; + /* if we haven't yet started and nothing is buffered, else start*/ if (!(c->flags & CHN_F_TRIGGERED)) { if (sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret) return ret; - } else { + } else return 0; - } } - for (;;) { - rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); - if (rdy <= threshold) { - ret = chn_sleep(c, "pcmsyn", 1); - if (ret == ERESTART || ret == EINTR) { - DEB(printf("chn_sync: tsleep returns %d\n", ret)); - return -1; - } - } else + b = BUF_PARENT(c, c->bufhard); + + threshold += sndbuf_getready(b); + minflush = sndbuf_xbytes(threshold, b, bs); + minflush -= minflush % sndbuf_getbps(bs); + + if (minflush > 0) { + threshold = min(minflush, sndbuf_getfree(bs)); + sndbuf_clear(bs, threshold); + sndbuf_acquire(bs, NULL, threshold); + minflush -= threshold; + } + + resid = sndbuf_getready(bs); + residp = resid; + count = sndbuf_xbytes(minflush + resid, bs, b) / sndbuf_getblksz(b); + hcount = count; + ret = 0; + + while (count > 0 && resid > 0) { + ret = chn_sleep(c, "pcmsyn", c->timeout); + if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; break; - } + } + if (ret == 0 || ret == EWOULDBLOCK) { + resid = sndbuf_getready(bs); + if (resid == residp) { + --count; + if (snd_verbose > 3) + printf("%s: [stalled] timeout=%d " + "count=%d hcount=%d " + "resid=%d minflush=%d\n", + __func__, c->timeout, count, + hcount, resid, minflush); + } else if (resid < residp && count < hcount) { + ++count; + if (snd_verbose > 3) + printf("%s: [resume] timeout=%d " + "count=%d hcount=%d " + "resid=%d minflush=%d\n", + __func__, c->timeout, count, + hcount, resid, minflush); + } + if (minflush > 0 && sndbuf_getfree(bs) > 0) { + threshold = min(minflush, + sndbuf_getfree(bs)); + sndbuf_clear(bs, threshold); + sndbuf_acquire(bs, NULL, threshold); + resid = sndbuf_getready(bs); + minflush -= threshold; + } + residp = resid; + } + } + + if (snd_verbose > 3) + printf("%s: timeout=%d count=%d hcount=%d resid=%d residp=%d " + "minflush=%d ret=%d\n", + __func__, c->timeout, count, hcount, resid, residp, + minflush, ret); + return 0; } @@ -724,48 +840,14 @@ chn_abort(struct pcm_channel *c) int chn_flush(struct pcm_channel *c) { - int ret, count, resid, resid_p; struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); - /* if we haven't yet started and nothing is buffered, else start*/ - if (!(c->flags & CHN_F_TRIGGERED)) { - if (sndbuf_getready(bs) > 0) { - ret = chn_start(c, 1); - if (ret) - return ret; - } else { - return 0; - } - } - c->flags |= CHN_F_CLOSING; - resid = sndbuf_getready(bs) + sndbuf_getready(b); - resid_p = resid; - count = 10; - ret = 0; - while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { - /* still pending output data. */ - ret = chn_sleep(c, "pcmflu", hz / 10); - if (ret == EWOULDBLOCK) - ret = 0; - if (ret == 0) { - resid = sndbuf_getready(bs) + sndbuf_getready(b); - if (resid == resid_p) - count--; - if (resid > resid_p) - DEB(printf("chn_flush: buffer length increasind %d -> %d\n", resid_p, resid)); - resid_p = resid; - } - } - if (count == 0) - DEB(printf("chn_flush: timeout, hw %d, sw %d\n", - sndbuf_getready(b), sndbuf_getready(bs))); - + chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); @@ -786,14 +868,141 @@ fmtvalid(u_int32_t fmt, u_int32_t *fmtlist) 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) { int hwspd, r; CHN_LOCKASSERT(c); + c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; + c->timeout = 1; c->xruns = 0; r = CHANNEL_RESET(c->methods, c->devinfo); @@ -818,7 +1027,7 @@ chn_reset(struct pcm_channel *c, u_int32_t fmt) #endif } if (r == 0) - r = chn_setblocksize(c, 0, 0); + r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); @@ -839,6 +1048,8 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) bs = NULL; c->devinfo = NULL; c->feeder = NULL; + c->latency = -1; + c->timeout = 1; ret = ENOMEM; b = sndbuf_create(c->dev, c->name, "primary", c); @@ -981,6 +1192,355 @@ chn_setvolume(struct pcm_channel *c, int left, int right) return 0; } +static u_int32_t +round_pow2(u_int32_t v) +{ + u_int32_t ret; + + if (v < 2) + v = 2; + ret = 0; + while (v >> ret) + ret++; + ret = 1 << (ret - 1); + while (ret < v) + ret <<= 1; + return ret; +} + +/* + * 4Front call it DSP Policy, while we call it "Latency Profile". The idea + * is to keep 2nd buffer short so that it doesn't cause long queue during + * buffer transfer. + * + * Latency reference table for 48khz stereo 16bit: (PLAY) + * + * +---------+------------+-----------+------------+ + * | Latency | Blockcount | Blocksize | Buffersize | + * +---------+------------+-----------+------------+ + * | 0 | 2 | 64 | 128 | + * +---------+------------+-----------+------------+ + * | 1 | 4 | 128 | 512 | + * +---------+------------+-----------+------------+ + * | 2 | 8 | 512 | 4096 | + * +---------+------------+-----------+------------+ + * | 3 | 16 | 512 | 8192 | + * +---------+------------+-----------+------------+ + * | 4 | 32 | 512 | 16384 | + * +---------+------------+-----------+------------+ + * | 5 | 32 | 1024 | 32768 | + * +---------+------------+-----------+------------+ + * | 6 | 16 | 2048 | 32768 | + * +---------+------------+-----------+------------+ + * | 7 | 8 | 4096 | 32768 | + * +---------+------------+-----------+------------+ + * | 8 | 4 | 8192 | 32768 | + * +---------+------------+-----------+------------+ + * | 9 | 2 | 16384 | 32768 | + * +---------+------------+-----------+------------+ + * | 10 | 2 | 32768 | 65536 | + * +---------+------------+-----------+------------+ + * + * Recording need a different reference table. All we care is + * gobbling up everything within reasonable buffering threshold. + * + * Latency reference table for 48khz stereo 16bit: (REC) + * + * +---------+------------+-----------+------------+ + * | Latency | Blockcount | Blocksize | Buffersize | + * +---------+------------+-----------+------------+ + * | 0 | 512 | 32 | 16384 | + * +---------+------------+-----------+------------+ + * | 1 | 256 | 64 | 16384 | + * +---------+------------+-----------+------------+ + * | 2 | 128 | 128 | 16384 | + * +---------+------------+-----------+------------+ + * | 3 | 64 | 256 | 16384 | + * +---------+------------+-----------+------------+ + * | 4 | 32 | 512 | 16384 | + * +---------+------------+-----------+------------+ + * | 5 | 32 | 1024 | 32768 | + * +---------+------------+-----------+------------+ + * | 6 | 16 | 2048 | 32768 | + * +---------+------------+-----------+------------+ + * | 7 | 8 | 4096 | 32768 | + * +---------+------------+-----------+------------+ + * | 8 | 4 | 8192 | 32768 | + * +---------+------------+-----------+------------+ + * | 9 | 2 | 16384 | 32768 | + * +---------+------------+-----------+------------+ + * | 10 | 2 | 32768 | 65536 | + * +---------+------------+-----------+------------+ + * + * Calculations for other data rate are entirely based on these reference + * tables. For normal operation, Latency 5 seems give the best, well + * balanced performance for typical workload. Anything below 5 will + * eat up CPU to keep up with increasing context switches because of + * shorter buffer space and usually require the application to handle it + * aggresively through possibly real time programming technique. + * + */ +#define CHN_LATENCY_PBLKCNT_REF \ + {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ + {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} +#define CHN_LATENCY_PBUFSZ_REF \ + {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ + {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} + +#define CHN_LATENCY_RBLKCNT_REF \ + {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ + {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} +#define CHN_LATENCY_RBUFSZ_REF \ + {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ + {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} + +#define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ + +static int +chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, + u_int32_t max, int *rblksz, int *rblkcnt) +{ + u_int32_t bufsz; + int blksz, blkcnt; + static int pblkcnts[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] = + CHN_LATENCY_PBLKCNT_REF; + static int pbufszs[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] = + CHN_LATENCY_PBUFSZ_REF; + static int rblkcnts[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] = + CHN_LATENCY_RBLKCNT_REF; + static int rbufszs[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] = + CHN_LATENCY_RBUFSZ_REF; + + if (CHN_LATENCY_MIN != 0 || CHN_LATENCY_MAX != 10 || + latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || + bps < 1 || datarate < 1 || + !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { + if (rblksz != NULL) + *rblksz = CHN_2NDBUFMAXSIZE >> 1; + if (rblkcnt != NULL) + *rblkcnt = 2; + printf("%s: FAILED dir=%d latency=%d bps=%d " + "datarate=%u max=%u\n", + __func__, dir, latency, bps, datarate, max); + return CHN_2NDBUFMAXSIZE; + } + + if (dir == PCMDIR_PLAY) { + blkcnt = pblkcnts[chn_latency_profile][latency]; + bufsz = pbufszs[chn_latency_profile][latency]; + } else { + blkcnt = rblkcnts[chn_latency_profile][latency]; + bufsz = rbufszs[chn_latency_profile][latency]; + } + bufsz = snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate); + if (bufsz > max) + bufsz = max; + if (bufsz < 32) + bufsz = 32; + blksz = bufsz >> blkcnt; + blksz -= blksz % bps; + while (blksz < 16 || blksz < bps) + blksz += bps; + while ((blksz << blkcnt) > bufsz && blkcnt > 1) + blkcnt--; + if (rblksz != NULL) + *rblksz = blksz; + if (rblkcnt != NULL) + *rblkcnt = 1 << blkcnt; + + return blksz << blkcnt; +} + +/* + * Note that there is no strict requirement to align blksz to the + * nearest ^2, except for hardware CHANNEL_SETBLOCKSIZE. If the application + * trying to act smarter and requesting for specific blksz/blkcnt, so be it. + */ +static int +chn_resizebuf(struct pcm_channel *c, int latency, + int blkcnt, int blksz) +{ + struct snd_dbuf *b, *bs, *pb; + int sblksz, sblkcnt, hblksz, limit = 1; + int ret; + + CHN_LOCKASSERT(c); + + if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED) || + !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) + return EINVAL; + + if (latency == -1) { + c->latency = -1; + latency = chn_latency; + } else if (latency == -2) { + latency = c->latency; + if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) + latency = chn_latency; + } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) + return EINVAL; + else { + c->latency = latency; + limit = 0; + } + + bs = c->bufsoft; + b = c->bufhard; + + if (!(blksz == 0 || blkcnt == -1) && + (blksz < 16 || blksz < sndbuf_getbps(bs) || blkcnt < 2 || + (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) + return EINVAL; + + chn_calclatency(c->direction, latency, sndbuf_getbps(bs), + sndbuf_getbps(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, + &sblksz, &sblkcnt); + + if (blksz == 0 || blkcnt == -1) { + if (blkcnt == -1) + c->flags &= ~CHN_F_HAS_SIZE; + if (c->flags & CHN_F_HAS_SIZE) { + blksz = sndbuf_getblksz(bs); + blkcnt = sndbuf_getblkcnt(bs); + } + } else + c->flags |= CHN_F_HAS_SIZE; + + if (c->flags & CHN_F_HAS_SIZE) { + /* + * The application has requested their own blksz/blkcnt. + * Just obey with it, and let them toast alone. We can + * clamp it to the nearest latency profile, but that would + * defeat the purpose of having custom control. The least + * we can do is round it to the nearest ^2 and align it. + */ + sblksz = round_pow2(blksz); + sblksz -= sblksz % sndbuf_getbps(bs); + sblkcnt = blkcnt; + while (sblksz < 16 || sblksz < sndbuf_getbps(bs)) + sblksz += sndbuf_getbps(bs); + if (snd_verbose > 3 && !(blksz == 0 || blkcnt == -1)) + printf("%s: requested blksz=%d blkcnt=%d -> %d/%d\n", + __func__, blksz, blkcnt, sblksz, sblkcnt); + limit = 0; + } + + if (c->parentchannel != NULL) { + pb = BUF_PARENT(c, NULL); + CHN_UNLOCK(c); + chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); + CHN_LOCK(c); + limit = (limit != 0 && pb != NULL) ? + sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; + c->timeout = c->parentchannel->timeout; + } else { + if (c->flags & CHN_F_HAS_SIZE) { + hblksz = sndbuf_xbytes(sblksz, bs, b); + if (snd_verbose > 3) + printf("%s: sblksz=%d -> hblksz=%d\n", + __func__, sblksz, hblksz); + } else + chn_calclatency(c->direction, latency, + sndbuf_getbps(b), + sndbuf_getbps(b) * sndbuf_getspd(b), + CHN_2NDBUFMAXSIZE, &hblksz, NULL); + + hblksz = round_pow2(hblksz); + if ((hblksz << 1) > sndbuf_getmaxsize(b)) + hblksz = sndbuf_getmaxsize(b) >> 1; + hblksz -= hblksz % sndbuf_getbps(b); + while (hblksz < 16 || hblksz < sndbuf_getbps(b)) + hblksz += sndbuf_getbps(b); + +#if 0 + hblksz = sndbuf_getmaxsize(b) >> 1; +#endif + CHN_UNLOCK(c); + sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, + c->devinfo, hblksz)); + CHN_LOCK(c); + + if (!SLIST_EMPTY(&c->children)) { + /* + * Virtual channels underneath. Set the biggest + * possible value for their mixing space. + */ + sblksz = CHN_2NDBUFMAXSIZE >> 1; + sblksz -= sblksz % sndbuf_getbps(bs); + sblkcnt = 2; + limit = 0; + } else if (limit != 0) + limit = sndbuf_xbytes(sndbuf_getsize(b), b, bs); + + /* + * Interrupt timeout + */ + c->timeout = ((u_int64_t)hz * sndbuf_getblksz(b)) / + ((u_int64_t)sndbuf_getspd(b) * sndbuf_getbps(b)); + if (c->timeout < 1) + c->timeout = 1; + c->timeout <<= 1; + } + + if (limit > CHN_2NDBUFMAXSIZE) + limit = CHN_2NDBUFMAXSIZE; + + hblksz = sblksz; + while ((sblksz * sblkcnt) < limit) { + sblksz += hblksz; + if ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { + sblksz -= hblksz; + break; + } + } + + if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || + sndbuf_getsize(bs) != (sblkcnt * sblksz)) { + ret = sndbuf_remalloc(bs, sblkcnt, sblksz); + if (ret != 0) { + printf("%s: Failed: %d %d\n", __func__, + sblkcnt, sblksz); + return ret; + } + } + + /* + * OSSv4 docs: "By default OSS will set the low water level equal + * to the fragment size which is optimal in most cases." + */ + c->lw = sndbuf_getblksz(bs); + chn_resetbuf(c); + + if (snd_verbose > 3) + printf("%s: PCMDIR_%s (%s) timeout=%u " + "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", + __func__, (c->direction == PCMDIR_REC) ? "REC" : "PLAY", + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + c->timeout, + sndbuf_getsize(b), sndbuf_getblksz(b), + sndbuf_getblkcnt(b), + sndbuf_getsize(bs), sndbuf_getblksz(bs), + sndbuf_getblkcnt(bs), limit); + + return 0; +} + +int +chn_setlatency(struct pcm_channel *c, int latency) +{ + CHN_LOCKASSERT(c); + /* Destroy blksz/blkcnt, enforce latency profile. */ + return chn_resizebuf(c, latency, -1, 0); +} + +int +chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) +{ + CHN_LOCKASSERT(c); + /* Destroy latency profile, enforce blksz/blkcnt */ + return chn_resizebuf(c, -1, blkcnt, blksz); +} + static int chn_tryspeed(struct pcm_channel *c, int speed) { @@ -1012,7 +1572,7 @@ chn_tryspeed(struct pcm_channel *c, int speed) /* * Used to be 500. It was too big! */ - if (delta > 25) + if (delta > feeder_rate_round) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); @@ -1022,10 +1582,6 @@ chn_tryspeed(struct pcm_channel *c, int speed) if (r) goto out; - r = chn_setblocksize(c, 0, 0); - if (r) - goto out; - if (!(c->feederflags & (1 << FEEDER_RATE))) goto out; @@ -1050,6 +1606,8 @@ out: sndbuf_getfmt(b)); if (!r) sndbuf_setfmt(bs, c->format); + if (!r) + r = chn_resizebuf(c, -2, 0, 0); DEB(printf("setspeed done, r = %d\n", r)); return r; } else @@ -1063,7 +1621,9 @@ chn_setspeed(struct pcm_channel *c, int speed) r = chn_tryspeed(c, speed); if (r) { - DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); + if (snd_verbose > 3) + printf("Failed to set speed %d falling back to %d\n", + speed, oldspeed); r = chn_tryspeed(c, oldspeed); } return r; @@ -1101,190 +1661,14 @@ chn_setformat(struct pcm_channel *c, u_int32_t fmt) r = chn_tryformat(c, fmt); if (r) { - DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); + if (snd_verbose > 3) + printf("Format change 0x%08x failed, reverting to 0x%08x\n", + fmt, oldfmt); chn_tryformat(c, oldfmt); } 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, 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))); - return EINVAL; - } - c->flags |= CHN_F_SETBLOCKSIZE; - - ret = 0; - DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); - 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)) { /* 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; - } else { /* use previously defined value */ - blkcnt = sndbuf_getblkcnt(bs); - blksz = sndbuf_getblksz(bs); - } - } 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; - ret = 0; - c->flags |= CHN_F_HAS_SIZE; - } - - 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; - RANGE(irqhz, 16, 512); - - 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); - if (sndbuf_getsize(bs) > maxsize) - maxsize = sndbuf_getsize(bs); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - CHN_UNLOCK(c); - sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); - CHN_LOCK(c); - - /* Decrease the size of bufsoft after decreasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (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) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - /* - * OSSv4 docs: "By default OSS will set the low water level equal - * to the fragment size which is optimal in most cases." - */ - c->lw = sndbuf_getblksz(bs); - - 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", - __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b), reqblksz, - 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; -} - int chn_trigger(struct pcm_channel *c, int go) { @@ -1372,6 +1756,7 @@ chn_buildfeeder(struct pcm_channel *c) struct pcm_feederdesc desc; u_int32_t tmp[2], type, flags, hwfmt, *fmtlist; int err; + char fmtstr[AFMTSTR_MAXSZ]; CHN_LOCKASSERT(c); while (chn_removefeeder(c) == 0); @@ -1393,7 +1778,7 @@ chn_buildfeeder(struct pcm_channel *c) } else { if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; - desc.in = 0; + desc.in = c->format; } else { DEB(printf("can't decide which feeder type to use!\n")); return EOPNOTSUPP; @@ -1432,7 +1817,42 @@ chn_buildfeeder(struct pcm_channel *c) 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)); @@ -1441,7 +1861,7 @@ chn_buildfeeder(struct pcm_channel *c) } DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in)); - tmp[0] = fc->desc->in; + tmp[0] = desc.in; tmp[1] = 0; if (chn_fmtchain(c, tmp) == 0) { DEB(printf("failed\n")); @@ -1450,7 +1870,7 @@ chn_buildfeeder(struct pcm_channel *c) } 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)); @@ -1475,7 +1895,7 @@ chn_buildfeeder(struct pcm_channel *c) sndbuf_setfmt(c->bufhard, hwfmt); if ((flags & (1 << FEEDER_VOLUME))) { - uint32_t parent = SOUND_MIXER_NONE; + u_int32_t parent = SOUND_MIXER_NONE; int vol, left, right; vol = 100 | (100 << 8); @@ -1553,20 +1973,10 @@ chn_notify(struct pcm_channel *c, u_int32_t flags) */ } if (flags & CHN_N_BLOCKSIZE) { - int blksz; /* - * scan the children, find the lowest blocksize and use that - * for the hard blocksize + * Set to default latency profile */ - blksz = sndbuf_getmaxsize(c->bufhard) / 2; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - if (sndbuf_getblksz(child->bufhard) < blksz) - blksz = sndbuf_getblksz(child->bufhard); - CHN_UNLOCK(child); - } - chn_setblocksize(c, 2, blksz); + chn_setlatency(c, chn_latency); } if (flags & CHN_N_TRIGGER) { int nrun; diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index d6426f7..3d4f537 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -79,6 +79,7 @@ struct pcm_channel { u_int32_t align; int volume; + int latency; u_int32_t speed; u_int32_t format; u_int32_t flags; @@ -86,7 +87,8 @@ struct pcm_channel { u_int32_t blocks; int direction; - unsigned int interrupts, xruns; + unsigned int interrupts, xruns, feedcount; + unsigned int timeout; struct snd_dbuf *bufhard, *bufsoft; struct snddev_info *parentsnddev; struct pcm_channel *parentchannel; @@ -142,6 +144,7 @@ int chn_setvolume(struct pcm_channel *c, int left, int right); int chn_setspeed(struct pcm_channel *c, int speed); int chn_setformat(struct pcm_channel *c, u_int32_t fmt); int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); +int chn_setlatency(struct pcm_channel *c, int latency); int chn_trigger(struct pcm_channel *c, int go); int chn_getptr(struct pcm_channel *c); struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); @@ -181,6 +184,30 @@ int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); 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); + +extern int chn_latency; +extern int chn_latency_profile; +extern int report_soft_formats; + #define PCMDIR_VIRTUAL 2 #define PCMDIR_PLAY 1 #define PCMDIR_REC -1 @@ -218,6 +245,17 @@ int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); #define CHN_N_BLOCKSIZE 0x00000008 #define CHN_N_TRIGGER 0x00000010 +#define CHN_LATENCY_MIN 0 +#define CHN_LATENCY_MAX 10 +#define CHN_LATENCY_DEFAULT 5 +#define CHN_POLICY_MIN CHN_LATENCY_MIN +#define CHN_POLICY_MAX CHN_LATENCY_MAX +#define CHN_POLICY_DEFAULT CHN_LATENCY_DEFAULT + +#define CHN_LATENCY_PROFILE_MIN 0 +#define CHN_LATENCY_PROFILE_MAX 1 +#define CHN_LATENCY_PROFILE_DEFAULT CHN_LATENCY_PROFILE_MAX + /* * This should be large enough to hold all pcm data between * tsleeps in chn_{read,write} at the highest sample rate. diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 56f50a7..940c3fd 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -215,10 +215,12 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) 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 (flags & FWRITE) - return EINVAL; chnum = PCMCHAN(i_dev); break; @@ -768,7 +770,7 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); - chn_sync(wrch, sndbuf_getsize(wrch->bufsoft) - 4); + chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; @@ -1085,12 +1087,11 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * case SNDCTL_DSP_GETODELAY: if (wrch) { - struct snd_dbuf *b = wrch->bufhard; struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ - *arg_i = sndbuf_getready(b) + sndbuf_getready(bs); + *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; @@ -2131,31 +2132,29 @@ dsp_oss_syncstart(int sg_id) static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) { - int fragln, fragsz, maxfrags, ret; + int ret; + + if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) + return EIO; /* Default: success */ ret = 0; - /* Scale policy [0..10] to fragment size [2^4..2^16]. */ - fragln = policy; - RANGE(fragln, 0, 10); - fragln += 4; - fragsz = 1 << fragln; - - maxfrags = CHN_2NDBUFMAXSIZE / fragsz; - if (rdch) { CHN_LOCK(rdch); - ret = chn_setblocksize(rdch, maxfrags, fragsz); + ret = chn_setlatency(rdch, policy); CHN_UNLOCK(rdch); } if (wrch && ret == 0) { CHN_LOCK(wrch); - ret = chn_setblocksize(wrch, maxfrags, fragsz); + ret = chn_setlatency(wrch, policy); CHN_UNLOCK(wrch); } + if (ret) + ret = EIO; + return ret; } diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c index 7143902..0c94319 100644 --- a/sys/dev/sound/pcm/fake.c +++ b/sys/dev/sound/pcm/fake.c @@ -142,10 +142,11 @@ fkchan_setup(device_t dev) c->parentsnddev = d; /* * Fake channel is such a blessing in disguise. Using this, - * we can keep track prefered virtual channel speed without + * 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; diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c index b70bd58..4f0de4f 100644 --- a/sys/dev/sound/pcm/feeder.c +++ b/sys/dev/sound/pcm/feeder.c @@ -35,6 +35,11 @@ MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); #define MAXFEEDERS 256 #undef FEEDER_DEBUG +int feeder_buffersize = FEEDBUFSZ; +TUNABLE_INT("hw.snd.feeder_buffersize", &feeder_buffersize); +SYSCTL_INT(_hw_snd, OID_AUTO, feeder_buffersize, CTLFLAG_RD, + &feeder_buffersize, FEEDBUFSZ, "feeder buffer size"); + struct feedertab_entry { SLIST_ENTRY(feedertab_entry) link; struct feeder_class *feederclass; @@ -72,6 +77,53 @@ feeder_register(void *p) SLIST_INSERT_HEAD(&feedertab, fte, link); feedercnt++; + /* initialize global variables */ + + if (snd_verbose < 0 || snd_verbose > 3) + snd_verbose = 1; + + if (snd_unit < 0 || snd_unit > PCMMAXDEV) + snd_unit = 0; + + if (snd_maxautovchans < 0 || + snd_maxautovchans > SND_MAXVCHANS) + snd_maxautovchans = 0; + + if (chn_latency < CHN_LATENCY_MIN || + chn_latency > CHN_LATENCY_MAX) + chn_latency = CHN_LATENCY_DEFAULT; + + if (chn_latency_profile < CHN_LATENCY_PROFILE_MIN || + chn_latency_profile > CHN_LATENCY_PROFILE_MAX) + chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; + + if (feeder_buffersize < FEEDBUFSZ_MIN || + feeder_buffersize > FEEDBUFSZ_MAX) + feeder_buffersize = FEEDBUFSZ; + + if (feeder_rate_min < FEEDRATE_MIN || + feeder_rate_max < FEEDRATE_MIN || + feeder_rate_min > FEEDRATE_MAX || + feeder_rate_max > FEEDRATE_MAX || + !(feeder_rate_min < feeder_rate_max)) { + feeder_rate_min = FEEDRATE_RATEMIN; + feeder_rate_max = FEEDRATE_RATEMAX; + } + + if (feeder_rate_round < FEEDRATE_ROUNDHZ_MIN || + feeder_rate_round > FEEDRATE_ROUNDHZ_MAX) + feeder_rate_round = FEEDRATE_ROUNDHZ; + + if (bootverbose) + printf("%s: snd_unit=%d snd_maxautovchans=%d " + "latency=%d feeder_buffersize=%d " + "feeder_rate_min=%d feeder_rate_max=%d " + "feeder_rate_round=%d\n", + __func__, snd_unit, snd_maxautovchans, + chn_latency, feeder_buffersize, + feeder_rate_min, feeder_rate_max, + feeder_rate_round); + /* we've got our root feeder so don't veto pcm loading anymore */ pcm_veto_load = 0; @@ -259,122 +311,152 @@ chainok(struct pcm_feeder *test, struct pcm_feeder *stop) return 1; } -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 pcm_feeder *try, *ret; - - DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); - if (fmtvalid(source->desc->out, to)) { - DEB(printf("got it\n")); - return source; - } - - if (maxdepth < 0) - return 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) { - try = feeder_create(fte->feederclass, fte->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); */ +/* + * 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 - return NULL; -} +/* + * 14bit format scoring + * -------------------- + * + * 13 12 11 10 9 8 2 1 0 offset + * +---+---+---+---+---+---+-------------+---+---+ + * | X | X | X | X | X | X | X X X X X X | X | X | + * +---+---+---+---+---+---+-------------+---+---+ + * | | | | | | | | | + * | | | | | | | | +--> signed? + * | | | | | | | | + * | | | | | | | +------> bigendian? + * | | | | | | | + * | | | | | | +---------------> total channels + * | | | | | | + * | | | | | +------------------------> AFMT_A_LAW + * | | | | | + * | | | | +----------------------------> AFMT_MU_LAW + * | | | | + * | | | +--------------------------------> AFMT_8BIT + * | | | + * | | +------------------------------------> AFMT_16BIT + * | | + * | +----------------------------------------> AFMT_24BIT + * | + * +--------------------------------------------> AFMT_32BIT + */ +#define score_signeq(s1, s2) (((s1) & 0x1) == ((s2) & 0x1)) +#define score_endianeq(s1, s2) (((s1) & 0x2) == ((s2) & 0x2)) +#define score_cheq(s1, s2) (((s1) & 0xfc) == ((s2) & 0xfc)) +#define score_val(s1) ((s1) & 0x3f00) +#define score_cse(s1) ((s1) & 0x7f) -int +u_int32_t chn_fmtscore(u_int32_t fmt) { - if (fmt & AFMT_32BIT) - return 60; - if (fmt & AFMT_24BIT) - return 50; - if (fmt & AFMT_16BIT) - return 40; - if (fmt & (AFMT_U8|AFMT_S8)) - return 30; - if (fmt & AFMT_MU_LAW) - return 20; + 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) - return 10; - return 0; + 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; } -u_int32_t -chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) +static u_int32_t +chn_fmtbestfunc(u_int32_t fmt, u_int32_t *fmts, int cheq) { - u_int32_t best; - int i, score, score2, oldscore; + 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 (oldscore == 0 || (score2 == score) || - (score2 > oldscore && score2 < score) || - (score2 < oldscore && score2 > score) || - (oldscore < score && score2 > oldscore)) { - best = fmts[i]; - oldscore = score2; + 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_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) { - u_int32_t best; - int i, score, score2, oldscore; + return chn_fmtbestfunc(fmt, fmts, 0); +} - best = 0; - score = chn_fmtscore(fmt); - oldscore = 0; - for (i = 0; fmts[i] != 0; i++) { - if ((fmt & AFMT_STEREO) == (fmts[i] & AFMT_STEREO)) { - score2 = chn_fmtscore(fmts[i]); - if (oldscore == 0 || (score2 == score) || - (score2 > oldscore && score2 < score) || - (score2 < oldscore && score2 > score) || - (oldscore < score && score2 > oldscore)) { - best = fmts[i]; - oldscore = score2; - } - } - } - return best; +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; - int score, score1, score2; + 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) { + if (best1 != 0 && best2 != 0 && best1 != best2) { if (fmt & AFMT_STEREO) return best1; else { - score = chn_fmtscore(fmt); - score1 = chn_fmtscore(best1); - score2 = chn_fmtscore(best2); + 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) @@ -389,6 +471,184 @@ chn_fmtbest(u_int32_t fmt, u_int32_t *fmts) 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, *ftebest; + struct pcm_feeder *try, *ret; + uint32_t fl, qout, qsrc, qdst; + int qtype; + + 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)) { + 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; + 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; + 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; +} + u_int32_t chn_fmtchain(struct pcm_channel *c, u_int32_t *to) { @@ -401,13 +661,15 @@ chn_fmtchain(struct pcm_channel *c, u_int32_t *to) 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 (fmtvalid(to[0], from)) - from = to; - else { + if (from[1] != 0) { best = chn_fmtbest(to[0], from); if (best != 0) { tmpfrom[0] = best; @@ -420,49 +682,60 @@ chn_fmtchain(struct pcm_channel *c, u_int32_t *to) tmpfrom[1] = 0; from = tmpfrom; if (to[1] != 0) { - if (fmtvalid(tmpfrom[0], to)) { - tmpto[0] = tmpfrom[0]; + best = chn_fmtbest(from[0], to); + if (best != 0) { + tmpto[0] = best; tmpto[1] = 0; to = tmpto; - } else { - best = chn_fmtbest(tmpfrom[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; @@ -521,28 +794,78 @@ static int feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) { struct snd_dbuf *src = source; - int l; - u_int8_t x; + int l, offset; KASSERT(count > 0, ("feed_root: count == 0")); /* count &= ~((1 << ch->align) - 1); */ KASSERT(count > 0, ("feed_root: aligned count == 0 (align = %d)", ch->align)); + if (++ch->feedcount == 0) + ch->feedcount = 2; + l = min(count, sndbuf_getready(src)); - sndbuf_dispose(src, buffer, l); /* When recording only return as much data as available */ - if (ch->direction == PCMDIR_REC) + if (ch->direction == PCMDIR_REC) { + sndbuf_dispose(src, buffer, l); return l; + } -/* - if (l < count) - printf("appending %d bytes\n", count - l); -*/ - x = (sndbuf_getfmt(src) & AFMT_SIGNED)? 0 : 0x80; - while (l < count) - buffer[l++] = x; + offset = count - l; + + if (offset > 0) { + if (snd_verbose > 3) + printf("%s: (%s) %spending %d bytes " + "(count=%d l=%d feed=%d)\n", + __func__, + (ch->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + (ch->feedcount == 1) ? "pre" : "ap", + offset, count, l, ch->feedcount); + + if (ch->feedcount == 1) { + if (offset > 0) + memset(buffer, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (l > 0) + sndbuf_dispose(src, buffer + offset, l); + else + ch->feedcount--; + } else { + if (l > 0) + sndbuf_dispose(src, buffer, l); + if (offset > 0) { +#if 1 + memset(buffer + l, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (!(ch->flags & CHN_F_CLOSING)) + ch->xruns++; +#else + if (l < 1 || (ch->flags & CHN_F_CLOSING)) { + memset(buffer + l, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (!(ch->flags & CHN_F_CLOSING)) + ch->xruns++; + } else { + int cp, tgt; + + tgt = l; + while (offset > 0) { + cp = min(l, offset); + memcpy(buffer + tgt, buffer, cp); + offset -= cp; + tgt += cp; + } + ch->xruns++; + } +#endif + } + } + } else if (l > 0) + sndbuf_dispose(src, buffer, l); return count; } @@ -561,8 +884,3 @@ static struct feeder_class feeder_root_class = { }; SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); - - - - - diff --git a/sys/dev/sound/pcm/feeder.h b/sys/dev/sound/pcm/feeder.h index 545fd24..a3985c0 100644 --- a/sys/dev/sound/pcm/feeder.h +++ b/sys/dev/sound/pcm/feeder.h @@ -53,7 +53,7 @@ struct pcm_feeder { void feeder_register(void *p); struct feeder_class *feeder_getclass(struct pcm_feederdesc *desc); -int chn_fmtscore(u_int32_t fmt); +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); @@ -72,11 +72,11 @@ static struct feeder_class feeder ## _class = { \ .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 -#define FEEDER_MIXER 3 +#define FEEDER_MIXER 3 #define FEEDER_RATE 4 #define FEEDER_FILTER 5 #define FEEDER_VOLUME 6 @@ -85,4 +85,27 @@ SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _cl #define FEEDRATE_SRC 1 #define FEEDRATE_DST 2 +#define FEEDRATE_RATEMIN 1 +#define FEEDRATE_RATEMAX 2016000 /* 48000 * 42 */ +#define FEEDRATE_MIN 1 +#define FEEDRATE_MAX 0x7fffff /* sign 24bit ~ 8ghz ! */ + +#define FEEDRATE_ROUNDHZ 25 +#define FEEDRATE_ROUNDHZ_MIN 0 +#define FEEDRATE_ROUNDHZ_MAX 500 + +/* + * Default buffer size for feeder processing. + * + * Big = less sndbuf_feed(), more memory usage. + * Small = aggresive sndbuf_feed() (perhaps too much), less memory usage. + */ +#define FEEDBUFSZ 16384 +#define FEEDBUFSZ_MIN 2048 +#define FEEDBUFSZ_MAX 131072 + +extern int feeder_rate_min; +extern int feeder_rate_max; +extern int feeder_rate_round; +extern int feeder_buffersize; diff --git a/sys/dev/sound/pcm/feeder_fmt.c b/sys/dev/sound/pcm/feeder_fmt.c index 17cd030..0ccc7b9 100644 --- a/sys/dev/sound/pcm/feeder_fmt.c +++ b/sys/dev/sound/pcm/feeder_fmt.c @@ -43,12 +43,13 @@ SND_DECLARE_FILE("$FreeBSD$"); MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); -#define FEEDBUFSZ 8192 -#define FEEDBUF24SZ 8190 - #define FMT_TRACE(x...) /* printf(x) */ #define FMT_TEST(x, y...) /* if (x) FMT_TRACE(y) */ -#define FMT_ALIGNBYTE(x) /* x */ + +struct feed_fmt_info { + int bps, bufsz; + uint8_t *buffer; +}; /* * Sign inverted ulaw/alaw -> 8 table @@ -194,33 +195,48 @@ static uint8_t u8_to_alaw_tbl[] = { }; static int -feed_table_u8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_table_8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int j, k = FEEDER_FEED(f->source, c, b, count, source); + int j, sign, k; uint8_t *tbl = (uint8_t *)f->data; + if (count < 1) + return 0; + k = FEEDER_FEED(f->source, c, b, count, source); j = k; + sign = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; while (j > 0) { j--; - b[j] = tbl[b[j]] ^ 0x80; + b[j] = tbl[b[j]] ^ sign; } return k; } static int -feed_table_s16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_table_16(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); + int i, j, sign, k; uint8_t *tbl = (uint8_t *)f->data; + if (count < 2) + return 0; + k = FEEDER_FEED(f->source, c, b, count >> 1, source); i = k; k <<= 1; j = k; - while (i > 0) { - b[--j] = tbl[b[--i]]; - b[--j] = 0; + 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; } @@ -229,188 +245,286 @@ static int feed_table_xlaw(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int j, k = FEEDER_FEED(f->source, c, b, count, source); + int j, sign, k; uint8_t *tbl = (uint8_t *)f->data; + if (count < 1) + return 0; + k = FEEDER_FEED(f->source, c, b, count, source); j = k; + sign = (f->desc->in & AFMT_SIGNED) ? 0x80 : 0x00; while (j > 0) { j--; - b[j] = tbl[b[j]]; + b[j] = tbl[b[j] ^ sign]; } return k; } -static struct pcm_feederdesc feeder_ulawtou8_desc[] = { +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_ulawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_u8), +static kobj_method_t feeder_ulawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), {0, 0} }; -FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_s8_tbl); +FEEDER_DECLARE(feeder_ulawto8, 0, ulaw_to_s8_tbl); -static struct pcm_feederdesc feeder_alawtou8_desc[] = { +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_alawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_u8), +static kobj_method_t feeder_alawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), {0, 0} }; -FEEDER_DECLARE(feeder_alawtou8, 0, alaw_to_s8_tbl); +FEEDER_DECLARE(feeder_alawto8, 0, alaw_to_s8_tbl); -static struct pcm_feederdesc feeder_ulawtos16le_desc[] = { +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_ulawtos16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_s16le), +static kobj_method_t feeder_ulawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), {0, 0} }; -FEEDER_DECLARE(feeder_ulawtos16le, 0, ulaw_to_s8_tbl); +FEEDER_DECLARE(feeder_ulawto16, 0, ulaw_to_s8_tbl); -static struct pcm_feederdesc feeder_alawtos16le_desc[] = { +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_alawtos16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_s16le), +static kobj_method_t feeder_alawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), {0, 0} }; -FEEDER_DECLARE(feeder_alawtos16le, 0, alaw_to_s8_tbl); +FEEDER_DECLARE(feeder_alawto16, 0, alaw_to_s8_tbl); -static struct pcm_feederdesc feeder_u8toulaw_desc[] = { +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_u8toulaw_methods[] = { +static kobj_method_t feeder_8toulaw_methods[] = { KOBJMETHOD(feeder_feed, feed_table_xlaw), {0, 0} }; -FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw_tbl); +FEEDER_DECLARE(feeder_8toulaw, 0, u8_to_ulaw_tbl); -static struct pcm_feederdesc feeder_u8toalaw_desc[] = { +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_u8toalaw_methods[] = { +static kobj_method_t feeder_8toalaw_methods[] = { KOBJMETHOD(feeder_feed, feed_table_xlaw), {0, 0} }; -FEEDER_DECLARE(feeder_u8toalaw, 0, u8_to_alaw_tbl); +FEEDER_DECLARE(feeder_8toalaw, 0, u8_to_alaw_tbl); /* - * Conversion rules:- - * 1. BE -> LE - * 2. if fmt == u8 , u8 -> s8 (economical) - * 3. Xle -> 16le - * 4. if fmt != u8 && fmt == u16le , u16le -> s16le - * 4. s16le mono -> s16le stereo - * * All conversion done in byte level to preserve endianess. */ static int -feed_common_init(struct pcm_feeder *f) +feed_fmt_init(struct pcm_feeder *f) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT|M_ZERO); - if (f->data == NULL) + struct feed_fmt_info *info; + + info = malloc(sizeof(*info), M_FMTFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + + info->bps = 1; + info->bps <<= (f->desc->in & AFMT_STEREO) ? 1 : 0; + if (f->desc->in & AFMT_16BIT) + info->bps <<= 1; + else if (f->desc->in & AFMT_24BIT) + info->bps *= 3; + else if (f->desc->in & AFMT_32BIT) + info->bps <<= 2; + info->bufsz = feeder_buffersize - (feeder_buffersize % info->bps); + + info->buffer = malloc(info->bufsz, M_FMTFEEDER, M_NOWAIT | M_ZERO); + if (info->buffer == NULL) { + free(info, M_FMTFEEDER); return ENOMEM; + } + + f->data = info; + return 0; } static int -feed_common_free(struct pcm_feeder *f) +feed_fmt_free(struct pcm_feeder *f) { - if (f->data) - free(f->data, M_FMTFEEDER); + struct feed_fmt_info *info = f->data; + + if (info) { + if (info->buffer) + free(info->buffer, M_FMTFEEDER); + free(info, M_FMTFEEDER); + } f->data = NULL; 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_8to16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_8to16(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); + int i, j, k; + int sign; + if (count < 2) + return 0; + k = FEEDER_FEED(f->source, c, b, count >> 1, source); i = k; k <<= 1; j = k; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = 0; + 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 k; } -static struct pcm_feederdesc feeder_8to16le_desc[] = { +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_8to16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_8to16le), +static kobj_method_t feeder_8to16_methods[] = { + KOBJMETHOD(feeder_feed, feed_8to16), {0, 0} }; -FEEDER_DECLARE(feeder_8to16le, 0, NULL); +FEEDER_DECLARE(feeder_8to16, 0, NULL); static int -feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_16to8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; + if (count < 1) + return 0; k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = k >> 1; - while (i > 0) { - b[--j] = src[--i]; - i--; + 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_16leto8_desc[] = { +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_S16_LE, AFMT_S8, 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_16leto8_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_16leto8), +static kobj_method_t feeder_16to8_methods[] = { + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_16to8), {0, 0} }; -FEEDER_DECLARE(feeder_16leto8, 0, NULL); +FEEDER_DECLARE(feeder_16to8, 0, NULL); static int -feed_16leto24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_16to24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; + int i, j, k, sign; + if (count < 3) + return 0; k = (count / 3) << 1; k = FEEDER_FEED(f->source, c, b, k, source); if (k < 2) { @@ -419,146 +533,370 @@ feed_16leto24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, return 0; } FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = (k >> 1) * 3; k = j; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[--i]; - b[--j] = 0; + 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_16leto24le_desc[] = { +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_16leto24le_methods[] = { - KOBJMETHOD(feeder_feed, feed_16leto24le), +static kobj_method_t feeder_16to24_methods[] = { + KOBJMETHOD(feeder_feed, feed_16to24), {0, 0} }; -FEEDER_DECLARE(feeder_16leto24le, 0, NULL); +FEEDER_DECLARE(feeder_16to24, 0, NULL); static int -feed_24leto16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_24to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; - k = (count * 3) >> 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); + if (count < 2) + return 0; + k = (count >> 1) * 3; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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__); - FMT_ALIGNBYTE(k -= k % 3); + k -= k % 3; i = (k / 3) << 1; j = i; - while (i > 0) { - b[--i] = src[--k]; - b[--i] = src[--k]; - k--; + 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_24leto16le_desc[] = { +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_24leto16le_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_24leto16le), +static kobj_method_t feeder_24to16_methods[] = { + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_24to16), {0, 0} }; -FEEDER_DECLARE(feeder_24leto16le, 1, NULL); +FEEDER_DECLARE(feeder_24to16, 1, NULL); static int -feed_16leto32le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_16to32(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); + int i, j, sign, k; + + if (count < 4) + return 0; + k = FEEDER_FEED(f->source, c, b, (count & ~3) >> 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__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = k << 1; k = j; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[--i]; - b[--j] = 0; - b[--j] = 0; + 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_16leto32le_desc[] = { +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_16leto32le_methods[] = { - KOBJMETHOD(feeder_feed, feed_16leto32le), +static kobj_method_t feeder_16to32_methods[] = { + KOBJMETHOD(feeder_feed, feed_16to32), {0, 0} }; -FEEDER_DECLARE(feeder_16leto32le, 0, NULL); +FEEDER_DECLARE(feeder_16to32, 0, NULL); static int -feed_32leto16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_32to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (count < 2) + return 0; + k = (count & ~1) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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__); - FMT_ALIGNBYTE(k &= ~3); + k &= ~3; i = k; k >>= 1; j = k; - while (i > 0) { - b[--j] = src[--i]; - b[--j] = src[--i]; - i -= 2; + 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_32leto16le_desc[] = { +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_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_32to16), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to16, 1, NULL); + +static int +feed_24to32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + + if (count < 4) + return 0; + k = FEEDER_FEED(f->source, c, b, (count >> 2) * 3, 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; +} +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_32leto16le_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_32leto16le), +static kobj_method_t feeder_24to32_methods[] = { + KOBJMETHOD(feeder_feed, feed_24to32), {0, 0} }; -FEEDER_DECLARE(feeder_32leto16le, 1, NULL); +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) +{ + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; + + if (count < 3) + return 0; + k = (count / 3) << 2; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_32to24), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to24, 1, NULL); /* * Bit conversion end */ @@ -570,8 +908,11 @@ 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); + int i, j, k; + if (count < 2) + return 0; + k = FEEDER_FEED(f->source, c, b, count >> 1, source); i = k; j = k << 1; while (i > 0) { @@ -583,6 +924,8 @@ feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 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}, + {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[] = { @@ -595,19 +938,22 @@ static int 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 >> 1, source); + int i, j, k; uint8_t l, m; + if (count < 4) + return 0; + k = FEEDER_FEED(f->source, c, b, (count & ~3) >> 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__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = k << 1; - while (i > 0) { + while (i > 1) { l = b[--i]; m = b[--i]; b[--j] = l; @@ -634,19 +980,22 @@ static int feed_monotostereo24(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); + int i, j, k; uint8_t l, m, n; + if (count < 6) + return 0; + k = FEEDER_FEED(f->source, c, b, ((count / 6) * 6) >> 1, 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__); - FMT_ALIGNBYTE(k -= k % 3); + k -= k % 3; i = k; j = k << 1; - while (i > 0) { + while (i > 2) { l = b[--i]; m = b[--i]; n = b[--i]; @@ -676,19 +1025,22 @@ static int feed_monotostereo32(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); + int i, j, k; uint8_t l, m, n, o; + if (count < 8) + return 0; + k = FEEDER_FEED(f->source, c, b, (count & ~7) >> 1, 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__); - FMT_ALIGNBYTE(k &= ~3); + k &= ~3; i = k; j = k << 1; - while (i > 0) { + while (i > 3) { l = b[--i]; m = b[--i]; n = b[--i]; @@ -727,18 +1079,21 @@ static int feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; + if (count < 1) + return 0; k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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 8bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k >> 1; j = i; while (i > 0) { @@ -750,11 +1105,13 @@ feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 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}, + {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_common_init), - KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono8), {0, 0} }; @@ -764,21 +1121,24 @@ static int feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (count < 2) + return 0; + k = (count & ~1) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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__); - FMT_ALIGNBYTE(k &= ~3); + k &= ~3; i = k >> 1; j = i; - while (i > 0) { + while (i > 1) { k -= 2; b[--i] = src[--k]; b[--i] = src[--k]; @@ -793,8 +1153,8 @@ static struct pcm_feederdesc feeder_stereotomono16_desc[] = { {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono16_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono16), {0, 0} }; @@ -804,21 +1164,24 @@ static int feed_stereotomono24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); + if (count < 3) + return 0; + k = ((count / 3) * 3) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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__); - FMT_ALIGNBYTE(k -= k % 6); + k -= k % 6; i = k >> 1; j = i; - while (i > 0) { + while (i > 2) { k -= 3; b[--i] = src[--k]; b[--i] = src[--k]; @@ -834,8 +1197,8 @@ static struct pcm_feederdesc feeder_stereotomono24_desc[] = { {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_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono24), {0, 0} }; @@ -845,21 +1208,24 @@ static int feed_stereotomono32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (count < 4) + return 0; + k = (count & ~3) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), 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__); - FMT_ALIGNBYTE(k &= ~7); + k &= ~7; i = k >> 1; j = i; - while (i > 0) { + while (i > 3) { k -= 4; b[--i] = src[--k]; b[--i] = src[--k]; @@ -876,8 +1242,8 @@ static struct pcm_feederdesc feeder_stereotomono32_desc[] = { {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_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono32), {0, 0} }; @@ -893,8 +1259,11 @@ 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); + int i, j; + if (count < 1) + return 0; + j = FEEDER_FEED(f->source, c, b, count, source); i = j; while (i > 0) b[--i] ^= 0x80; @@ -914,103 +1283,145 @@ static kobj_method_t feeder_sign8_methods[] = { FEEDER_DECLARE(feeder_sign8, 0, NULL); static int -feed_sign16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +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); + int i, j; + if (count < 2) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~1, source); 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__); - FMT_ALIGNBYTE(j &= ~1); + j &= ~1; i = j; - while (i > 0) { - b[--i] ^= 0x80; - i--; + 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_sign16le_desc[] = { +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_S16_LE, AFMT_U16_LE, 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_sign16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign16le), +static kobj_method_t feeder_sign16_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign16), {0, 0} }; -FEEDER_DECLARE(feeder_sign16le, 0, NULL); +FEEDER_DECLARE(feeder_sign16, 0, NULL); static int -feed_sign24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +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); + int i, j; + if (count < 3) + return 0; + j = FEEDER_FEED(f->source, c, b, (count / 3) * 3, 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__); - FMT_ALIGNBYTE(j -= j % 3); + j -= j % 3; i = j; - while (i > 0) { - b[--i] ^= 0x80; - i -= 2; + 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_sign24le_desc[] = { +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_sign24le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign24le), +static kobj_method_t feeder_sign24_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign24), {0, 0} }; -FEEDER_DECLARE(feeder_sign24le, 0, NULL); +FEEDER_DECLARE(feeder_sign24, 0, NULL); static int -feed_sign32le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_sign32(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); + int i, j; + if (count < 4) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~3, source); 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__); - FMT_ALIGNBYTE(j &= ~3); + j &= ~3; i = j; - while (i > 0) { - b[--i] ^= 0x80; - i -= 3; + 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_sign32le_desc[] = { +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_sign32le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign32le), +static kobj_method_t feeder_sign32_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign32), {0, 0} }; -FEEDER_DECLARE(feeder_sign32le, 0, NULL); +FEEDER_DECLARE(feeder_sign32, 0, NULL); /* * Sign conversion end. */ @@ -1022,18 +1433,21 @@ 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); + int i, j; uint8_t v; + if (count < 2) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~1, source); 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__); - FMT_ALIGNBYTE(j &= ~1); + j &= ~1; i = j; - while (i > 0) { + while (i > 1) { v = b[--i]; b[i] = b[i - 1]; b[--i] = v; @@ -1061,18 +1475,21 @@ 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); + int i, j; uint8_t v; + if (count < 3) + return 0; + j = FEEDER_FEED(f->source, c, b, (count / 3) * 3, 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__); - FMT_ALIGNBYTE(j -= j % 3); + j -= j % 3; i = j; - while (i > 0) { + while (i > 2) { v = b[--i]; b[i] = b[i - 2]; b[i -= 2] = v; @@ -1100,18 +1517,21 @@ 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); + int i, j; uint8_t l, m; + if (count < 4) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~3, source); 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__); - FMT_ALIGNBYTE(j &= ~3); + j &= ~3; i = j; - while (i > 0) { + while (i > 3) { l = b[--i]; m = b[--i]; b[i] = b[i - 1]; diff --git a/sys/dev/sound/pcm/feeder_rate.c b/sys/dev/sound/pcm/feeder_rate.c index b4496d1..a3fe99d 100644 --- a/sys/dev/sound/pcm/feeder_rate.c +++ b/sys/dev/sound/pcm/feeder_rate.c @@ -25,6 +25,15 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * + * 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: * ========== * @@ -68,30 +77,24 @@ SND_DECLARE_FILE("$FreeBSD$"); -#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ -#define RATE_TEST(x, y) /* if (!(x)) printf y */ -#define RATE_TRACE(x...) /* printf(x) */ +#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"); -#define FEEDBUFSZ 8192 -#define ROUNDHZ 25 -#define RATEMIN 4000 -/* 8000 * 138 or 11025 * 100 . This is insane, indeed! */ -#define RATEMAX 1102500 -#define MINGAIN 92 -#define MAXGAIN 96 - -#define FEEDRATE_CONVERT_64 0 -#define FEEDRATE_CONVERT_SCALE64 1 -#define FEEDRATE_CONVERT_SCALE32 2 -#define FEEDRATE_CONVERT_PLAIN 3 -#define FEEDRATE_CONVERT_FIXED 4 -#define FEEDRATE_CONVERT_OPTIMAL 5 -#define FEEDRATE_CONVERT_WORST 6 - -#define FEEDRATE_64_MAXROLL 32 -#define FEEDRATE_32_MAXROLL 16 +/* + * Don't overflow 32bit integer, since everything is done + * within 32bit arithmetic. + */ +#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)) + +struct feed_rate_info; + +typedef uint32_t (*feed_rate_converter)(struct feed_rate_info *, uint8_t *, uint32_t); struct feed_rate_info { uint32_t src, dst; /* rounded source / destination rates */ @@ -99,138 +102,148 @@ struct feed_rate_info { 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 */ + 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 */ - int32_t scale, roll; /* scale / roll factor */ - int16_t *buffer; - uint32_t (*convert)(struct feed_rate_info *, int16_t *, uint32_t); + uint8_t *buffer; + feed_rate_converter convert; }; -static uint32_t -feed_convert_64(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_scale64(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_scale32(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_plain(struct feed_rate_info *, int16_t *, uint32_t); - -int feeder_rate_ratemin = RATEMIN; -int feeder_rate_ratemax = RATEMAX; -/* - * See 'Feeder Scaling Type' below.. - */ -static int feeder_rate_scaling = FEEDRATE_CONVERT_OPTIMAL; -static int feeder_rate_buffersize = FEEDBUFSZ & ~1; +int feeder_rate_min = FEEDRATE_RATEMIN; +int feeder_rate_max = FEEDRATE_RATEMAX; +int feeder_rate_round = FEEDRATE_ROUNDHZ; -/* - * 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_scaling", &feeder_rate_scaling); -TUNABLE_INT("hw.snd.feeder_rate_buffersize", &feeder_rate_buffersize); +TUNABLE_INT("hw.snd.feeder_rate_min", &feeder_rate_min); +TUNABLE_INT("hw.snd.feeder_rate_max", &feeder_rate_max); +TUNABLE_INT("hw.snd.feeder_rate_round", &feeder_rate_round); static int -sysctl_hw_snd_feeder_rate_ratemin(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_min(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_ratemin; + val = feeder_rate_min; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 1 || val >= feeder_rate_ratemax) - err = EINVAL; + if (RATE_FACTOR_SAFE(val) && val < feeder_rate_max) + feeder_rate_min = val; else - feeder_rate_ratemin = val; - 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", ""); - -static int -sysctl_hw_snd_feeder_rate_ratemax(SYSCTL_HANDLER_ARGS) -{ - int err, val; - - val = feeder_rate_ratemax; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val <= feeder_rate_ratemin || val > 0x7fffff) err = EINVAL; - else - feeder_rate_ratemax = val; 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", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_min, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_min, "I", + "minimum allowable rate"); static int -sysctl_hw_snd_feeder_rate_scaling(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_max(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_scaling; + val = feeder_rate_max; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - /* - * Feeder Scaling Type - * =================== - * - * 1. Plain 64bit (high precision) - * 2. 64bit scaling (high precision, CPU friendly, but can - * cause gain up/down). - * 3. 32bit scaling (somehow can cause hz roundup, gain - * up/down). - * 4. Plain copy (default if src == dst. Except if src == dst, - * this is the worst / silly conversion method!). - * - * Sysctl options:- - * - * 0 - Plain 64bit - no fallback. - * 1 - 64bit scaling - no fallback. - * 2 - 32bit scaling - no fallback. - * 3 - Plain copy - no fallback. - * 4 - Fixed rate. Means that, choose optimal conversion method - * without causing hz roundup. - * 32bit scaling (as long as hz roundup does not occur), - * 64bit scaling, Plain 64bit. - * 5 - Optimal / CPU friendly (DEFAULT). - * 32bit scaling, 64bit scaling, Plain 64bit - * 6 - Optimal to worst, no 64bit arithmetic involved. - * 32bit scaling, Plain copy. - */ - if (val < FEEDRATE_CONVERT_64 || val > FEEDRATE_CONVERT_WORST) - err = EINVAL; + if (RATE_FACTOR_SAFE(val) && val > feeder_rate_min) + feeder_rate_max = val; else - feeder_rate_scaling = val; + err = EINVAL; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_scaling, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_scaling, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_max, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_max, "I", + "maximum allowable rate"); static int -sysctl_hw_snd_feeder_rate_buffersize(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_buffersize; + val = feeder_rate_round; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - /* - * Don't waste too much kernel space - */ - if (val < 2 || val > 65536) + if (val < FEEDRATE_ROUNDHZ_MIN || val > FEEDRATE_ROUNDHZ_MAX) err = EINVAL; else - feeder_rate_buffersize = val & ~1; + feeder_rate_round = val - (val % FEEDRATE_ROUNDHZ); return err; } -/* XXX: this should be settable by an user via a control tool, the sysadmin - needs a max and min sysctl to limit what an user can do */ -SYSCTL_PROC(_hw_snd, OID_AUTO, _feeder_rate_buffersize, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_buffersize, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I", + "sample rate converter rounding threshold"); + +#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 x, uint32_t y, uint32_t *gx, uint32_t *gy) +feed_speed_ratio(uint32_t src, uint32_t dst, uint32_t *gx, uint32_t *gy) { - uint32_t w, src = x, dst = y; + uint32_t w, x = src, y = dst; while (y != 0) { w = x % y; @@ -242,242 +255,112 @@ feed_speed_ratio(uint32_t x, uint32_t y, uint32_t *gx, uint32_t *gy) } static void -feed_scale_roll(uint32_t dst, int32_t *scale, int32_t *roll, int32_t max) -{ - int64_t k, tscale; - int32_t j, troll; - - *scale = *roll = -1; - for (j = MAXGAIN; j >= MINGAIN; j -= 3) { - for (troll = 0; troll < max; troll++) { - tscale = (1 << troll) / dst; - k = (tscale * dst * 100) >> troll; - if (k > j && k <= 100) { - *scale = tscale; - *roll = troll; - return; - } - } - } -} - -static int -feed_get_best_coef(uint32_t *src, uint32_t *dst, uint32_t *gx, uint32_t *gy, - int32_t *scale, int32_t *roll) -{ - uint32_t tsrc, tdst, sscale, dscale; - int32_t tscale, troll; - int i, j, hzmin, hzmax; - - *scale = *roll = -1; - for (i = 0; i < 2; i++) { - hzmin = (ROUNDHZ * i) + 1; - hzmax = hzmin + ROUNDHZ; - for (j = hzmin; j < hzmax; j++) { - tsrc = *src - (*src % j); - tdst = *dst; - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - for (j = hzmin; j < hzmax; j++) { - tsrc = *src - (*src % j); - tdst = *dst - (*dst % j); - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *dst = tdst; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - for (j = hzmin; j < hzmax; j++) { - tsrc = *src; - tdst = *dst - (*dst % j); - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *dst = tdst; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - } -coef_failed: - feed_speed_ratio(*src, *dst, gx, gy); - feed_scale_roll(*gy, scale, roll, FEEDRATE_32_MAXROLL); - return 0; -} - -static void feed_rate_reset(struct feed_rate_info *info) { - info->scale = -1; - info->roll = -1; - info->src = info->rsrc; - info->dst = info->rdst; - info->gx = 0; - info->gy = 0; + info->src = info->rsrc - (info->rsrc % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->dst = info->rdst - (info->rdst % + ((feeder_rate_round > 0) ? feeder_rate_round : 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; - int r = 0; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_rate_converter convert; + } convtbl[] = { + { AFMT_S8, PCM_8_BPS, feed_convert_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_convert_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_convert_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_convert_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_convert_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_convert_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_convert_s32be }, + /* unsigned */ + { AFMT_U8, PCM_8_BPS, feed_convert_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_convert_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_convert_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_convert_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_convert_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_convert_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_convert_u32be }, + { 0, 0, NULL }, + }; + uint32_t i; - info->pos = 2; - info->bpos = 4; - info->alpha = 0; - info->stray = 0; feed_rate_reset(info); - if (info->src == info->dst) { - /* - * No conversion ever needed. Just do plain copy. - */ - info->convert = feed_convert_plain; - info->gx = 1; - info->gy = 1; - } else { - switch (feeder_rate_scaling) { - case FEEDRATE_CONVERT_64: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_64; - break; - case FEEDRATE_CONVERT_SCALE64: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale == -1 || info->roll == -1) - return -1; - info->convert = feed_convert_scale64; - break; - case FEEDRATE_CONVERT_SCALE32: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r == 0) - return -1; - info->convert = feed_convert_scale32; - break; - case FEEDRATE_CONVERT_PLAIN: - feed_speed_ratio(info->src, info->dst, + + if (info->src != info->dst) + feed_speed_ratio(info->src, info->dst, &info->gx, &info->gy); - info->convert = feed_convert_plain; - break; - case FEEDRATE_CONVERT_FIXED: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0 && info->src == info->rsrc && - info->dst == info->rdst) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale != -1 && info->roll != -1) - info->convert = feed_convert_scale64; - else - info->convert = feed_convert_64; - } - break; - case FEEDRATE_CONVERT_OPTIMAL: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale != -1 && info->roll != -1) - info->convert = feed_convert_scale64; - else - info->convert = feed_convert_64; - } - break; - case FEEDRATE_CONVERT_WORST: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_plain; - } - break; - default: - return -1; - break; - } - /* No way! */ - if (info->gx == 0 || info->gy == 0) + + if (!(RATE_FACTOR_SAFE(info->gx) && RATE_FACTOR_SAFE(info->gy))) + return -1; + + for (i = 0; i < sizeof(convtbl) / sizeof(*convtbl); i++) { + if (convtbl[i].format == 0) return -1; - /* - * No need to interpolate/decimate, just do plain copy. - * This probably caused by Hz roundup. - */ - if (info->gx == info->gy) - info->convert = feed_convert_plain; + if ((f->desc->out & ~AFMT_STEREO) == convtbl[i].format) { + info->bps = convtbl[i].bps; + info->convert = convtbl[i].convert; + break; + } } + + /* + * No need to interpolate/decimate, just do plain copy. + */ + if (info->gx == info->gy) + info->convert = NULL; + + info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + info->pos = info->bps * info->channels; + info->bpos = info->pos << 1; + info->bufsz -= info->bufsz % info->pos; + + memset(info->buffer, sndbuf_zerodata(f->desc->out), info->bpos); + + 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->pos); + 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; - if (value < feeder_rate_ratemin || value > feeder_rate_ratemax) + if (value < feeder_rate_min || value > feeder_rate_max) return -1; - + switch (what) { - case FEEDRATE_SRC: - info->rsrc = value; - break; - case FEEDRATE_DST: - info->rdst = value; - break; - default: - return -1; + case FEEDRATE_SRC: + info->rsrc = value; + break; + case FEEDRATE_DST: + info->rdst = value; + break; + default: + return -1; } return feed_rate_setup(f); } @@ -487,16 +370,13 @@ feed_rate_get(struct pcm_feeder *f, int what) { struct feed_rate_info *info = f->data; - /* - * Return *real* src/dst rate. - */ switch (what) { - case FEEDRATE_SRC: - return info->rsrc; - case FEEDRATE_DST: - return info->rdst; - default: - return -1; + case FEEDRATE_SRC: + return info->rsrc; + case FEEDRATE_DST: + return info->rdst; + default: + return -1; } return -1; } @@ -506,14 +386,17 @@ feed_rate_init(struct pcm_feeder *f) { 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; /* * bufsz = sample from last cycle + conversion space */ - info->bufsz = 2 + feeder_rate_buffersize; - info->buffer = malloc(sizeof(*info->buffer) * info->bufsz, + info->bufsz_init = 8 + feeder_buffersize; + info->buffer = malloc(sizeof(*info->buffer) * info->bufsz_init, M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info->buffer == NULL) { free(info, M_RATEFEEDER); @@ -539,233 +422,88 @@ feed_rate_free(struct pcm_feeder *f) return 0; } -static uint32_t -feed_convert_64(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - int64_t x, alpha, distance; - uint32_t ret; - int32_t pos, bpos, gx, gy; - int16_t *src; - /* - * Plain, straight forward 64bit arith. No bit-magic applied here. - */ - ret = 0; - alpha = info->alpha; - gx = info->gx; - gy = info->gy; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x / gy; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x / gy; - if (ret == max) - break; - } - } - info->alpha = alpha; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_scale64(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - int64_t x, alpha, distance; - uint32_t ret; - int32_t pos, bpos, gx, gy, roll; - int16_t *src; - /* - * 64bit scaling. - */ - ret = 0; - roll = info->roll; - alpha = info->alpha * info->scale; - gx = info->gx * info->scale; - gy = info->gy * info->scale; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x >> roll; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x >> roll; - if (ret == max) - break; - } - } - info->alpha = alpha / info->scale; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_scale32(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - uint32_t ret; - int32_t x, pos, bpos, gx, gy, alpha, roll, distance; - int16_t *src; - /* - * 32bit scaling. - */ - ret = 0; - roll = info->roll; - alpha = info->alpha * info->scale; - gx = info->gx * info->scale; - gy = info->gy * info->scale; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x >> roll; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x >> roll; - if (ret == max) - break; - } - } - info->alpha = alpha / info->scale; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_plain(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - uint32_t ret; - int32_t pos, bpos, gx, gy, alpha; - int16_t *src; - /* - * Plain copy. - */ - ret = 0; - gx = info->gx; - gy = info->gy; - alpha = info->alpha; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - dst[ret++] = src[pos]; - dst[ret++] = src[pos + 1]; - if (ret == max) - break; - } - } - info->pos = pos; - info->alpha = alpha; - return ret; -} - -static int32_t +static int feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) + uint32_t count, void *source) { struct feed_rate_info *info = f->data; - uint32_t i; + uint32_t i, smpsz; int32_t fetch, slot; - int16_t *dst = (int16_t *)b; + + if (info->convert == NULL) + return FEEDER_FEED(f->source, c, b, count, source); + /* * This loop has been optimized to generalize both up / down * sampling without causing missing samples or excessive buffer - * feeding. + * 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. */ - RATE_TEST(count >= 4 && (count & 3) == 0, - ("%s: Count size not byte integral (%d)\n", __func__, count)); - if (count < 4) + 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 >>= 1; - count &= ~1; - slot = (((info->gx * (count >> 1)) + info->gy - info->alpha - 1) / info->gy) << 1; - RATE_TEST((slot & 1) == 0, ("%s: Slot count not sample integral (%d)\n", - __func__, slot)); + count -= count % smpsz; /* - * Optimize buffer feeding aggressively to ensure calculated slot - * can be fitted nicely into available buffer free space, hence - * avoiding multiple feeding. + * This slot count formula will stay here for the next million years + * to come. This is the key of our circular buffering precision. */ + 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 != 2 && info->bpos - info->pos == 2 && + 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. */ - info->buffer[0] = info->buffer[info->pos - 2]; - info->buffer[1] = info->buffer[info->pos - 1]; - info->buffer[2] = info->buffer[info->pos]; - info->buffer[3] = info->buffer[info->pos + 1]; - info->pos = 2; - info->bpos = 4; + 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) << 1; + 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 << 1) < fetch) - fetch = slot << 1; + if (slot < fetch) + fetch = slot; if (fetch > 0) { - RATE_ASSERT(((info->bpos << 1) - info->stray) >= 0 && - ((info->bpos << 1) - info->stray) < (info->bufsz << 1), + 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 << 1, (info->bpos << 1) - info->stray)); + info->bufsz, info->bpos - info->stray)); fetch = FEEDER_FEED(f->source, c, - (uint8_t *)(info->buffer) + (info->bpos << 1) - info->stray, + info->buffer + info->bpos - info->stray, fetch, source); info->stray = 0; if (fetch == 0) break; - RATE_TEST((fetch & 3) == 0, - ("%s: Fetch size not byte integral (%d)\n", + RATE_TEST((fetch % smpsz) == 0, + ("%s: Fetch size not sample integral (%d)\n", __func__, fetch)); - info->stray += fetch & 3; + info->stray += fetch % smpsz; RATE_TEST(info->stray == 0, ("%s: Stray bytes detected (%d)\n", __func__, info->stray)); - fetch >>= 1; - fetch &= ~1; + fetch -= fetch % smpsz; info->bpos += fetch; slot -= fetch; RATE_ASSERT(slot >= 0, @@ -779,7 +517,7 @@ feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, break; } if (info->pos == info->bpos) { - RATE_TEST(info->pos == 2, + RATE_TEST(info->pos == smpsz, ("%s: EOF while in progress\n", __func__)); break; } @@ -788,10 +526,10 @@ feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, info->pos, info->bpos)); RATE_ASSERT(info->pos < info->bpos, ("%s: Zero buffer!\n", __func__)); - RATE_ASSERT(((info->bpos - info->pos) & 1) == 0, + RATE_ASSERT(((info->bpos - info->pos) % smpsz) == 0, ("%s: Buffer not sample integral (%d)\n", __func__, info->bpos - info->pos)); - i += info->convert(info, dst + i, count - i); + 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)); @@ -802,31 +540,67 @@ feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, * interpolate using it. */ RATE_TEST(info->stray == 0, ("%s: [2] Stray bytes: %u\n", __func__, info->stray)); - info->buffer[0] = info->buffer[info->pos - 2]; - info->buffer[1] = info->buffer[info->pos - 1]; - info->bpos = 2; - info->pos = 2; + bcopy(info->buffer + info->pos - smpsz, info->buffer, + sizeof(*info->buffer) * smpsz); + info->bpos = smpsz; + info->pos = smpsz; } if (i == count) break; } -#if 0 - RATE_TEST(count == i, ("Expect: %u , Got: %u\n", count << 1, i << 1)); -#endif + + 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 i << 1; + + 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), - KOBJMETHOD(feeder_set, feed_rate_set), - KOBJMETHOD(feeder_get, feed_rate_get), - KOBJMETHOD(feeder_feed, feed_rate), + KOBJMETHOD(feeder_init, feed_rate_init), + KOBJMETHOD(feeder_free, feed_rate_free), + KOBJMETHOD(feeder_set, feed_rate_set), + KOBJMETHOD(feeder_get, feed_rate_get), + KOBJMETHOD(feeder_feed, feed_rate), {0, 0} }; + FEEDER_DECLARE(feeder_rate, 2, NULL); diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c index bb46ea0..544194c 100644 --- a/sys/dev/sound/pcm/feeder_volume.c +++ b/sys/dev/sound/pcm/feeder_volume.c @@ -31,48 +31,193 @@ SND_DECLARE_FILE("$FreeBSD$"); +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 bps; /* bytes-per-sample, regardless of + total channels */ + feed_volume_filter filter; + } voltbl[] = { + { AFMT_S8, PCM_8_BPS, feed_volume_filter_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_volume_filter_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_volume_filter_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_volume_filter_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_volume_filter_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_volume_filter_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_volume_filter_s32be }, + /* unsigned */ + { AFMT_U8, PCM_8_BPS, feed_volume_filter_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_volume_filter_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_volume_filter_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_volume_filter_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_volume_filter_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_volume_filter_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_volume_filter_u32be }, + { 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 & ~AFMT_STEREO) == voltbl[i].format) { + info->bps = voltbl[i].bps; + info->filter = voltbl[i].filter; + break; + } + } + + /* For now, this is mandatory! */ + info->channels = 2; + + return 0; +} + static int -feed_volume_s16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) +feed_volume_init(struct pcm_feeder *f) { - int i, j, k, vol[2]; - int16_t *buf; + 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); +} - k = FEEDER_FEED(f->source, c, b, count & ~1, source); - if (k < 2) { -#if 0 - device_printf(c->dev, "%s: Not enough data (Got: %d bytes)\n", +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; + if (count < smpsz) + return 0; + 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); -#endif return 0; } -#if 0 - if (k & 1) - device_printf(c->dev, "%s: Bytes not 16bit aligned.\n", __func__); -#endif - k &= ~1; - i = k >> 1; - buf = (int16_t *)b; - vol[0] = c->volume & 0x7f; - vol[1] = (c->volume >> 8) & 0x7f; - while (i > 0) { - i--; - j = (vol[i & 1] * buf[i]) / 100; - if (j > 32767) - j = 32767; - if (j < -32768) - j = -32768; - buf[i] = j; - } - return k; + 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_s16_desc[] = { - {FEEDER_VOLUME, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, +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_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_volume_s16), +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_s16, 2, NULL); +FEEDER_DECLARE(feeder_volume, 2, NULL); diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index d0f6946..3deded7 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -631,7 +631,7 @@ sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS) 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); @@ -663,9 +663,11 @@ mixer_hwvol_init(device_t dev) m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); #endif diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c index 5b07ece..32188ff 100644 --- a/sys/dev/sound/pcm/sndstat.c +++ b/sys/dev/sound/pcm/sndstat.c @@ -72,11 +72,11 @@ static int sndstat_files = 0; static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); -static int sndstat_verbose = 1; +int snd_verbose = 1; #ifdef USING_MUTEX -TUNABLE_INT("hw.snd.verbose", &sndstat_verbose); +TUNABLE_INT("hw.snd.verbose", &snd_verbose); #else -TUNABLE_INT_DECL("hw.snd.verbose", 1, sndstat_verbose); +TUNABLE_INT_DECL("hw.snd.verbose", 1, snd_verbose); #endif static int sndstat_prepare(struct sbuf *s); @@ -86,20 +86,20 @@ sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) { int error, verbose; - verbose = sndstat_verbose; + verbose = snd_verbose; error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req); if (error == 0 && req->newptr != NULL) { sx_xlock(&sndstat_lock); - if (verbose < 0 || verbose > 3) + if (verbose < 0 || verbose > 4) error = EINVAL; else - sndstat_verbose = verbose; + snd_verbose = verbose; sx_xunlock(&sndstat_lock); } return error; } SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_sndverbose, "I", ""); + 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level"); static int sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) @@ -300,7 +300,8 @@ sndstat_prepare(struct sbuf *s) 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); @@ -318,14 +319,14 @@ sndstat_prepare(struct sbuf *s) sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); sbuf_printf(s, " %s", ent->str); if (ent->handler) - ent->handler(s, ent->dev, sndstat_verbose); + ent->handler(s, ent->dev, snd_verbose); else sbuf_printf(s, " [no handler]"); sbuf_printf(s, "\n"); } } - if (sndstat_verbose >= 3 && sndstat_files > 0) { + if (snd_verbose >= 3 && sndstat_files > 0) { sbuf_printf(s, "\nFile Versions:\n"); SLIST_FOREACH(ent, &sndstat_devlist, link) { diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 5ab8343..719bd93 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -26,6 +26,7 @@ */ #include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/ac97.h> #include <dev/sound/pcm/vchan.h> #include <dev/sound/pcm/dsp.h> #include <sys/limits.h> @@ -59,22 +60,6 @@ struct unrhdr *pcmsg_unrhdr = NULL; static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); -struct sysctl_ctx_list * -snd_sysctl_tree(device_t dev) -{ - struct snddev_info *d = device_get_softc(dev); - - return &d->sysctl_tree; -} - -struct sysctl_oid * -snd_sysctl_tree_top(device_t dev) -{ - struct snddev_info *d = device_get_softc(dev); - - return d->sysctl_tree_top; -} - void * snd_mtxcreate(const char *desc, const char *type) { @@ -176,6 +161,11 @@ pcm_setvchans(struct snddev_info *d, int newcnt) pcm_inprog(d, 1); + if (d->playcount < 1) { + err = ENODEV; + goto setvchans_out; + } + if (!(d->flags & SD_F_AUTOVCHAN)) { err = EINVAL; goto setvchans_out; @@ -238,7 +228,7 @@ addok: ORPHAN_CDEVT(sce->dsp_devt) && ORPHAN_CDEVT(sce->dspW_devt) && ORPHAN_CDEVT(sce->audio_devt) && - ORPHAN_CDEVT(sce->dspr_devt)) + ORPHAN_CDEVT(sce->dspHW_devt)) goto remok; /* * Either we're busy, or our cdev @@ -392,7 +382,7 @@ sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) } /* XXX: do we need a way to let the user change the default unit? */ SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_default_unit, "I", ""); + 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); #endif static int @@ -419,7 +409,7 @@ sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", ""); + 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); struct pcm_channel * pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) @@ -556,6 +546,7 @@ pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) unsigned rdevcount; int device = device_get_unit(d->dev); size_t namelen; + char dtype; /* * Note it's confusing nomenclature. @@ -647,11 +638,20 @@ retry_chan_num_search_out: } #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) > 10) { /* ":dspXX.YYY" */ + if ((CHN_NAMELEN - namelen) > 11) { /* ":dspXX.TYYY" */ snprintf(ch->name + namelen, - CHN_NAMELEN - namelen, ":dsp%d.%d", - device, sce->chan_num); + CHN_NAMELEN - namelen, ":dsp%d.%c%d", + device, dtype, ch->num); } snd_mtxunlock(d->lock); @@ -673,11 +673,11 @@ retry_chan_num_search_out: 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; } @@ -770,7 +770,7 @@ pcm_setstatus(device_t dev, char *str) 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); @@ -869,18 +869,11 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) chn_init(d->fakechan, NULL, 0, 0); #ifdef SND_DYNSYSCTL - sysctl_ctx_init(&d->sysctl_tree); - d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, - SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, - device_get_nameunit(dev), CTLFLAG_RD, 0, ""); - if (d->sysctl_tree_top == NULL) { - sysctl_ctx_free(&d->sysctl_tree); - goto no; - } /* XXX: an user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "_buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); #endif if (numplay > 0) { d->flags |= SD_F_AUTOVCHAN; @@ -889,9 +882,6 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) sndstat_register(dev, d->status, sndstat_prepare_pcm); return 0; -no: - snd_mtxfree(d->lock); - return ENXIO; } int @@ -945,9 +935,9 @@ pcm_unregister(device_t dev) destroy_dev(sce->audio_devt); sce->audio_devt = NULL; } - if (sce->dspr_devt) { - destroy_dev(sce->dspr_devt); - sce->dspr_devt = NULL; + if (sce->dspHW_devt) { + destroy_dev(sce->dspHW_devt); + sce->dspHW_devt = NULL; } d->devcount--; ch = sce->channel; @@ -967,9 +957,11 @@ pcm_unregister(device_t dev) } #ifdef SND_DYNSYSCTL +#if 0 d->sysctl_tree_top = NULL; sysctl_ctx_free(&d->sysctl_tree); #endif +#endif #if 0 SLIST_FOREACH(sce, &d->channels, link) { @@ -1068,15 +1060,15 @@ sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) 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), + sbuf_printf(s, "overruns %d, feed %u, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, c->feedcount, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), sndbuf_getblkcnt(c->bufhard), sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), sndbuf_getblkcnt(c->bufsoft)); else - sbuf_printf(s, "underruns %d, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", - c->xruns, sndbuf_getready(c->bufsoft), + sbuf_printf(s, "underruns %d, feed %u, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, c->feedcount, sndbuf_getready(c->bufsoft), sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), sndbuf_getblkcnt(c->bufhard), sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index eeb5c20..b62b1a5 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -58,6 +58,7 @@ #include <machine/resource.h> #include <machine/bus.h> #include <sys/rman.h> +#include <sys/limits.h> #include <sys/mman.h> #include <sys/poll.h> #include <sys/sbuf.h> @@ -148,6 +149,281 @@ nomenclature: (((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) @@ -185,7 +461,7 @@ int fkchan_kill(struct pcm_channel *c); #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 @@ -194,6 +470,8 @@ int fkchan_kill(struct pcm_channel *c); extern int pcm_veto_load; extern int snd_unit; +extern int snd_maxautovchans; +extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; @@ -210,9 +488,6 @@ extern struct unrhdr *pcmsg_unrhdr; SYSCTL_DECL(_hw_snd); -struct sysctl_ctx_list *snd_sysctl_tree(device_t dev); -struct sysctl_oid *snd_sysctl_tree_top(device_t dev); - struct pcm_channel *pcm_getfakechan(struct snddev_info *d); 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); @@ -287,7 +562,7 @@ struct snddev_channel { struct cdev *dsp_devt; struct cdev *dspW_devt; struct cdev *audio_devt; - struct cdev *dspr_devt; + struct cdev *dspHW_devt; }; struct snddev_info { @@ -300,8 +575,6 @@ struct snddev_info { void *devinfo; device_t dev; char status[SND_STATUSLEN]; - struct sysctl_ctx_list sysctl_tree; - struct sysctl_oid *sysctl_tree_top; struct mtx *lock; struct cdev *mixer_dev; diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index b3fbd25..b210dca 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -1,5 +1,6 @@ /*- - * Copyright (c) 2001 Cameron Grant <cg@freebsd.org> + * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org> + * Copyright (c) 2006 Ariff Abdullah <ariff@FreeBSD.org> * 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 <dev/sound/pcm/sound.h> @@ -30,74 +34,210 @@ SND_DECLARE_FILE("$FreeBSD$"); +MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); + /* - * Default speed + * 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 bps; /* bytes-per-sample, regardless of + total channels */ + feed_vchan_mixer mix; + } vchan_mix_tbl[] = { + { AFMT_S8, PCM_8_BPS, feed_vchan_mix_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_vchan_mix_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_vchan_mix_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_vchan_mix_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_vchan_mix_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_vchan_mix_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_vchan_mix_s32be }, + /* unsigned */ + { AFMT_U8, PCM_8_BPS, feed_vchan_mix_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_vchan_mix_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_vchan_mix_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_vchan_mix_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_vchan_mix_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_vchan_mix_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_vchan_mix_u32be }, + { 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 & ~AFMT_STEREO) == vchan_mix_tbl[i].format) { + info->bps = vchan_mix_tbl[i].bps; + info->mix = vchan_mix_tbl[i].mix; + break; } - to[i] = x & 0x0000ffff; } + + info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + info->zero_sample = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; + + return 0; +} + +static int +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) +{ + struct feed_vchan_info *info = f->data; + + if (info) + free(info, M_VCHANFEEDER); + f->data = NULL; 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(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - /* we're going to abuse things a bit */ + struct feed_vchan_info *info = f->data; struct snd_dbuf *src = source; struct pcmchan_children *cce; struct pcm_channel *ch; - uint32_t sz; - int16_t *tmp, *dst; - unsigned int cnt, rcnt = 0; - - #if 0 - 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); - #endif + uint32_t cnt, rcnt = 0, sz; + uint8_t *tmp; + sz = sndbuf_getsize(src); if (sz < count) count = sz; - count &= ~1; - if (count < 2) + + sz = info->bps * info->channels; + count -= count % sz; + if (count < sz) return 0; - bzero(b, count); /* * we are going to use our source as a temporary buffer since it's @@ -105,35 +245,68 @@ feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32 * 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); + 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 >> 1); + 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); + CHN_UNLOCK(ch); } - return rcnt & ~1; + if (++c->feedcount == 0) + c->feedcount = 2; + + 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); /************************************************************/ @@ -165,7 +338,7 @@ vchan_free(kobj_t obj, void *data) } 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; @@ -180,15 +353,15 @@ vchan_setformat(kobj_t obj, void *data, u_int32_t format) ch->bps *= 3; else if (ch->fmt & AFMT_32BIT) ch->bps <<= 2; - CHN_UNLOCK(channel); + CHN_UNLOCK(channel); chn_notify(parent, CHN_N_FORMAT); - CHN_LOCK(channel); + 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; @@ -204,7 +377,7 @@ vchan_setspeed(kobj_t obj, void *data, u_int32_t 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; @@ -213,18 +386,27 @@ vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) int prate, crate; ch->blksz = blocksize; - /* CHN_UNLOCK(channel); */ + /* CHN_UNLOCK(channel); */ sndbuf_setblksz(channel->bufhard, blocksize); chn_notify(parent, CHN_N_BLOCKSIZE); - CHN_LOCK(parent); - /* CHN_LOCK(channel); */ + CHN_LOCK(parent); + /* CHN_LOCK(channel); */ crate = ch->spd * ch->bps; prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft); blocksize = sndbuf_getblksz(parent->bufsoft); - CHN_UNLOCK(parent); + 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; } @@ -240,9 +422,9 @@ vchan_trigger(kobj_t obj, void *data, int go) return 0; ch->run = (go == PCMTRIG_START)? 1 : 0; - CHN_UNLOCK(channel); + CHN_UNLOCK(channel); chn_notify(parent, CHN_N_TRIGGER); - CHN_LOCK(channel); + CHN_LOCK(channel); return 0; } @@ -251,24 +433,34 @@ static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; + uint32_t fmt; 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; } static kobj_method_t vchan_methods[] = { - KOBJMETHOD(channel_init, vchan_init), - KOBJMETHOD(channel_free, vchan_free), - KOBJMETHOD(channel_setformat, vchan_setformat), - KOBJMETHOD(channel_setspeed, vchan_setspeed), - KOBJMETHOD(channel_setblocksize, vchan_setblocksize), - KOBJMETHOD(channel_trigger, vchan_trigger), - KOBJMETHOD(channel_getcaps, vchan_getcaps), - { 0, 0 } + KOBJMETHOD(channel_init, vchan_init), + KOBJMETHOD(channel_free, vchan_free), + KOBJMETHOD(channel_setformat, vchan_setformat), + KOBJMETHOD(channel_setspeed, vchan_setspeed), + KOBJMETHOD(channel_setblocksize, vchan_setblocksize), + KOBJMETHOD(channel_trigger, vchan_trigger), + KOBJMETHOD(channel_getcaps, vchan_getcaps), + {0, 0} }; CHANNEL_DECLARE(vchan); @@ -280,7 +472,7 @@ static int sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; + struct snddev_channel *sce; struct pcm_channel *c, *ch = NULL, *fake; struct pcmchan_caps *caps; int err = 0; @@ -329,18 +521,20 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) } 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) { + if (newspd < 1 || newspd < feeder_rate_min || + newspd > feeder_rate_max) { 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 (feeder_rate_round) { + 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); @@ -348,7 +542,8 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) * 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))) { + if (!err && feeder_rate_round && + (ch->feederflags & (1 << FEEDER_RATE))) { newspd = sndbuf_getspd(ch->bufhard); err = chn_setspeed(ch, newspd); } @@ -367,6 +562,97 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) 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 */ @@ -374,11 +660,12 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) int vchan_create(struct pcm_channel *parent) { - struct snddev_info *d = parent->parentsnddev; + struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; struct pcm_channel *child, *fake; struct pcmchan_caps *parent_caps; - int err, first, speed = 0; + uint32_t vchanfmt = 0; + int err, first, speed = 0, r; if (!(parent->flags & CHN_F_BUSY)) return EBUSY; @@ -388,7 +675,7 @@ vchan_create(struct pcm_channel *parent) pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); if (!pce) { - CHN_LOCK(parent); + CHN_LOCK(parent); return ENOMEM; } @@ -396,7 +683,7 @@ vchan_create(struct pcm_channel *parent) child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); if (!child) { free(pce, M_DEVBUF); - CHN_LOCK(parent); + CHN_LOCK(parent); return ENODEV; } pce->channel = child; @@ -413,7 +700,7 @@ vchan_create(struct pcm_channel *parent) return err; } - CHN_LOCK(parent); + CHN_LOCK(parent); /* add us to our parent channel's children */ first = SLIST_EMPTY(&parent->children); SLIST_INSERT_HEAD(&parent->children, pce, link); @@ -424,23 +711,50 @@ vchan_create(struct pcm_channel *parent) if (parent_caps == NULL) err = EINVAL; - if (!err) - err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); + 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) { - fake = pcm_getfakechan(d); - if (fake != NULL) { - /* - * Avoid querying kernel hint, use saved value - * from fake channel. - */ + if (vchanfmt == 0) { + const char *vfmt; + CHN_UNLOCK(parent); - CHN_LOCK(fake); - speed = fake->speed; - CHN_UNLOCK(fake); + 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 @@ -448,7 +762,6 @@ vchan_create(struct pcm_channel *parent) * to set sane value via kernel hints or sysctl. */ if (speed < 1) { - int r; CHN_UNLOCK(parent); r = resource_int_value(device_get_name(parent->dev), device_get_unit(parent->dev), @@ -456,6 +769,9 @@ vchan_create(struct pcm_channel *parent) CHN_LOCK(parent); if (r != 0) { /* + * No saved value from fake channel, + * no hint, NOTHING. + * * Workaround for sb16 running * poorly at 45k / 49k. */ @@ -466,38 +782,45 @@ vchan_create(struct pcm_channel *parent) break; default: speed = VCHAN_DEFAULT_SPEED; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; break; } + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; } } - /* - * 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; + if (feeder_rate_round) { + /* + * 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 + * feeder_rate_min <-> feeder_rate_max. 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; + if (speed < feeder_rate_min) + speed = feeder_rate_min; + if (speed > feeder_rate_max) + speed = feeder_rate_max; 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))) { + if (!err && feeder_rate_round && + (parent->feederflags & (1 << FEEDER_RATE))) { speed = sndbuf_getspd(parent->bufhard); err = chn_setspeed(parent, speed); } @@ -509,6 +832,7 @@ vchan_create(struct pcm_channel *parent) CHN_UNLOCK(parent); CHN_LOCK(fake); fake->speed = speed; + fake->format = vchanfmt; CHN_UNLOCK(fake); CHN_LOCK(parent); } @@ -533,7 +857,7 @@ int vchan_destroy(struct pcm_channel *c) { struct pcm_channel *parent = c->parentchannel; - struct snddev_info *d = parent->parentsnddev; + struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; struct snddev_channel *sce; uint32_t spd; @@ -571,9 +895,9 @@ gotch: destroy_dev(sce->audio_devt); sce->audio_devt = NULL; } - if (sce->dspr_devt) { - destroy_dev(sce->dspr_devt); - sce->dspr_devt = NULL; + if (sce->dspHW_devt) { + destroy_dev(sce->dspHW_devt); + sce->dspHW_devt = NULL; } d->devcount--; break; @@ -606,16 +930,19 @@ vchan_initsys(device_t dev) #ifdef SND_DYNSYSCTL struct snddev_info *d; - d = device_get_softc(dev); - /* XXX: the user should be able to set this with a control tool, the - sysadmin needs a sysctl so set a max value for "vchan" and min+max - values for "vchanrate" to limit what an user can do */ - 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_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", ""); + d = device_get_softc(dev); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchanrate, "I", "virtual channel mixing speed/rate"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchanformat, "A", "virtual channel format"); #endif return 0; diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c index 9e4b7f1..29117f2 100644 --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -2517,7 +2517,7 @@ uaudio_query_devinfo(void *addr, mixer_devinfo_t *mi) break; default: mi->type = AUDIO_MIXER_VALUE; - strncpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); + strlcpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); mi->un.v.num_channels = mc->nchan; mi->un.v.delta = mc->delta; break; |