2006-07-15 19:36:28 +00:00
|
|
|
/*-
|
|
|
|
* Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
|
2007-09-12 07:43:43 +00:00
|
|
|
* Copyright (c) 2003-2007 Yuriy Tsibizov <yuriy.tsibizov@gfk.ru>
|
2006-07-15 19:36:28 +00:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* $FreeBSD$
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/bus.h>
|
|
|
|
#include <machine/bus.h>
|
|
|
|
#include <sys/rman.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/sbuf.h>
|
|
|
|
#include <sys/queue.h>
|
|
|
|
#include <sys/lock.h>
|
|
|
|
#include <sys/mutex.h>
|
|
|
|
#include <sys/sysctl.h>
|
2007-09-12 07:43:43 +00:00
|
|
|
#include <sys/kdb.h>
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
#include <dev/pci/pcireg.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
|
|
|
|
#include <machine/clock.h> /* for DELAY */
|
|
|
|
|
Sound Mega-commit. Expect further cleanup until code freeze.
For a slightly thorough explaination, please refer to
[1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html .
Summary of changes includes:
1 Volume Per-Channel (vpc). Provides private / standalone volume control
unique per-stream pcm channel without touching master volume / pcm.
Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for
backwards compatibility, SOUND_MIXER_PCM through the opened dsp device
instead of /dev/mixer. Special "bypass" mode is enabled through
/dev/mixer which will automatically detect if the adjustment is made
through /dev/mixer and forward its request to this private volume
controller. Changes to this volume object will not interfere with
other channels.
Requirements:
- SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which
require specific application modifications (preferred).
- No modifications required for using bypass mode, so applications
like mplayer or xmms should work out of the box.
Kernel hints:
- hint.pcm.%d.vpc (0 = disable vpc).
Kernel sysctls:
- hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer
bypass mode.
- hw.snd.vpc_autoreset (default: 1). By default, closing/opening
/dev/dsp will reset the volume back to 0 db gain/attenuation.
Setting this to 0 will preserve its settings across device
closing/opening.
- hw.snd.vpc_reset (default: 0). Panic/reset button to reset all
volume settings back to 0 db.
- hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value.
2 High quality fixed-point Bandlimited SINC sampling rate converter,
based on Julius O'Smith's Digital Audio Resampling -
http://ccrma.stanford.edu/~jos/resample/. It includes a filter design
script written in awk (the clumsiest joke I've ever written)
- 100% 32bit fixed-point, 64bit accumulator.
- Possibly among the fastest (if not fastest) of its kind.
- Resampling quality is tunable, either runtime or during kernel
compilation (FEEDER_RATE_PRESETS).
- Quality can be further customized during kernel compilation by
defining FEEDER_RATE_PRESETS in /etc/make.conf.
Kernel sysctls:
- hw.snd.feeder_rate_quality.
0 - Zero-order Hold (ZOH). Fastest, bad quality.
1 - Linear Interpolation (LINEAR). Slightly slower than ZOH,
better quality but still does not eliminate aliasing.
2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC
quality always start from 2 and above.
Rough quality comparisons:
- http://people.freebsd.org/~ariff/z_comparison/
3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be
directly fed into the hardware.
4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can
be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf.
5 Transparent/Adaptive Virtual Channel. Now you don't have to disable
vchans in order to make digital format pass through. It also makes
vchans more dynamic by choosing a better format/rate among all the
concurrent streams, which means that dev.pcm.X.play.vchanformat/rate
becomes sort of optional.
6 Exclusive Stream, with special open() mode O_EXCL. This will "mute"
other concurrent vchan streams and only allow a single channel with
O_EXCL set to keep producing sound.
Other Changes:
* most feeder_* stuffs are compilable in userland. Let's not
speculate whether we should go all out for it (save that for
FreeBSD 16.0-RELEASE).
* kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org>
* pull out channel mixing logic out of vchan.c and create its own
feeder_mixer for world justice.
* various refactoring here and there, for good or bad.
* activation of few more OSSv4 ioctls() (see [1] above).
* opt_snd.h for possible compile time configuration:
(mostly for debugging purposes, don't try these at home)
SND_DEBUG
SND_DIAGNOSTIC
SND_FEEDER_MULTIFORMAT
SND_FEEDER_FULL_MULTIFORMAT
SND_FEEDER_RATE_HP
SND_PCM_64
SND_OLDSTEREO
Manual page updates are on the way.
Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many
unsung / unnamed heroes.
2009-06-07 19:12:08 +00:00
|
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
|
|
#include "opt_snd.h"
|
|
|
|
#endif
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
#include <dev/sound/chip.h>
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
|
|
#include <dev/sound/pcm/ac97.h>
|
|
|
|
|
|
|
|
#include <dev/sound/pci/emu10kx.h>
|
|
|
|
|
|
|
|
/* hw flags */
|
|
|
|
#define HAS_51 0x0001
|
|
|
|
#define HAS_71 0x0002
|
|
|
|
#define HAS_AC97 0x0004
|
|
|
|
|
|
|
|
#define IS_EMU10K1 0x0008
|
|
|
|
#define IS_EMU10K2 0x0010
|
|
|
|
#define IS_CA0102 0x0020
|
|
|
|
#define IS_CA0108 0x0040
|
|
|
|
#define IS_UNKNOWN 0x0080
|
|
|
|
|
|
|
|
#define BROKEN_DIGITAL 0x0100
|
|
|
|
#define DIGITAL_ONLY 0x0200
|
|
|
|
|
|
|
|
#define IS_CARDBUS 0x0400
|
|
|
|
|
|
|
|
#define MODE_ANALOG 1
|
|
|
|
#define MODE_DIGITAL 2
|
|
|
|
#define SPDIF_MODE_PCM 1
|
|
|
|
#define SPDIF_MODE_AC3 2
|
|
|
|
|
|
|
|
#define MACS 0x0
|
|
|
|
#define MACS1 0x1
|
|
|
|
#define MACW 0x2
|
|
|
|
#define MACW1 0x3
|
|
|
|
#define MACINTS 0x4
|
|
|
|
#define MACINTW 0x5
|
|
|
|
#define ACC3 0x6
|
|
|
|
#define MACMV 0x7
|
|
|
|
#define ANDXOR 0x8
|
|
|
|
#define TSTNEG 0x9
|
|
|
|
#define LIMIT 0xA
|
|
|
|
#define LIMIT1 0xB
|
|
|
|
#define LOG 0xC
|
|
|
|
#define EXP 0xD
|
|
|
|
#define INTERP 0xE
|
|
|
|
#define SKIP 0xF
|
|
|
|
|
|
|
|
#define GPR(i) (sc->gpr_base+(i))
|
|
|
|
#define INP(i) (sc->input_base+(i))
|
|
|
|
#define OUTP(i) (sc->output_base+(i))
|
|
|
|
#define FX(i) (i)
|
|
|
|
#define FX2(i) (sc->efxc_base+(i))
|
|
|
|
#define DSP_CONST(i) (sc->dsp_zero+(i))
|
|
|
|
|
|
|
|
#define COND_NORMALIZED DSP_CONST(0x1)
|
|
|
|
#define COND_BORROW DSP_CONST(0x2)
|
|
|
|
#define COND_MINUS DSP_CONST(0x3)
|
|
|
|
#define COND_LESS_ZERO DSP_CONST(0x4)
|
|
|
|
#define COND_EQ_ZERO DSP_CONST(0x5)
|
|
|
|
#define COND_SATURATION DSP_CONST(0x6)
|
|
|
|
#define COND_NEQ_ZERO DSP_CONST(0x8)
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
#define DSP_ACCUM DSP_CONST(0x16)
|
|
|
|
#define DSP_CCR DSP_CONST(0x17)
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
/* Live! Inputs */
|
|
|
|
#define IN_AC97_L 0x00
|
|
|
|
#define IN_AC97_R 0x01
|
|
|
|
#define IN_AC97 IN_AC97_L
|
|
|
|
#define IN_SPDIF_CD_L 0x02
|
|
|
|
#define IN_SPDIF_CD_R 0x03
|
|
|
|
#define IN_SPDIF_CD IN_SPDIF_CD_L
|
|
|
|
#define IN_ZOOM_L 0x04
|
|
|
|
#define IN_ZOOM_R 0x05
|
|
|
|
#define IN_ZOOM IN_ZOOM_L
|
|
|
|
#define IN_TOSLINK_L 0x06
|
|
|
|
#define IN_TOSLINK_R 0x07
|
|
|
|
#define IN_TOSLINK IN_TOSLINK_L
|
|
|
|
#define IN_LINE1_L 0x08
|
|
|
|
#define IN_LINE1_R 0x09
|
|
|
|
#define IN_LINE1 IN_LINE1_L
|
|
|
|
#define IN_COAX_SPDIF_L 0x0a
|
|
|
|
#define IN_COAX_SPDIF_R 0x0b
|
|
|
|
#define IN_COAX_SPDIF IN_COAX_SPDIF_L
|
|
|
|
#define IN_LINE2_L 0x0c
|
|
|
|
#define IN_LINE2_R 0x0d
|
|
|
|
#define IN_LINE2 IN_LINE2_L
|
|
|
|
#define IN_0E 0x0e
|
|
|
|
#define IN_0F 0x0f
|
|
|
|
|
|
|
|
/* Outputs */
|
|
|
|
#define OUT_AC97_L 0x00
|
|
|
|
#define OUT_AC97_R 0x01
|
|
|
|
#define OUT_AC97 OUT_AC97_L
|
|
|
|
#define OUT_A_FRONT OUT_AC97
|
|
|
|
#define OUT_TOSLINK_L 0x02
|
|
|
|
#define OUT_TOSLINK_R 0x03
|
|
|
|
#define OUT_TOSLINK OUT_TOSLINK_L
|
|
|
|
#define OUT_D_CENTER 0x04
|
|
|
|
#define OUT_D_SUB 0x05
|
|
|
|
#define OUT_HEADPHONE_L 0x06
|
|
|
|
#define OUT_HEADPHONE_R 0x07
|
|
|
|
#define OUT_HEADPHONE OUT_HEADPHONE_L
|
|
|
|
#define OUT_REAR_L 0x08
|
|
|
|
#define OUT_REAR_R 0x09
|
|
|
|
#define OUT_REAR OUT_REAR_L
|
|
|
|
#define OUT_ADC_REC_L 0x0a
|
|
|
|
#define OUT_ADC_REC_R 0x0b
|
|
|
|
#define OUT_ADC_REC OUT_ADC_REC_L
|
|
|
|
#define OUT_MIC_CAP 0x0c
|
2007-01-06 18:59:35 +00:00
|
|
|
|
|
|
|
/* Live! 5.1 Digital, non-standart 5.1 (center & sub) outputs */
|
2006-07-15 19:36:28 +00:00
|
|
|
#define OUT_A_CENTER 0x11
|
|
|
|
#define OUT_A_SUB 0x12
|
|
|
|
|
|
|
|
/* Audigy Inputs */
|
|
|
|
#define A_IN_AC97_L 0x00
|
|
|
|
#define A_IN_AC97_R 0x01
|
|
|
|
#define A_IN_AC97 A_IN_AC97_L
|
|
|
|
#define A_IN_SPDIF_CD_L 0x02
|
|
|
|
#define A_IN_SPDIF_CD_R 0x03
|
|
|
|
#define A_IN_SPDIF_CD A_IN_SPDIF_CD_L
|
|
|
|
#define A_IN_O_SPDIF_L 0x04
|
|
|
|
#define A_IN_O_SPDIF_R 0x05
|
|
|
|
#define A_IN_O_SPDIF A_IN_O_SPDIF_L
|
|
|
|
#define A_IN_LINE2_L 0x08
|
|
|
|
#define A_IN_LINE2_R 0x09
|
|
|
|
#define A_IN_LINE2 A_IN_LINE2_L
|
|
|
|
#define A_IN_R_SPDIF_L 0x0a
|
|
|
|
#define A_IN_R_SPDIF_R 0x0b
|
|
|
|
#define A_IN_R_SPDIF A_IN_R_SPDIF_L
|
|
|
|
#define A_IN_AUX2_L 0x0c
|
|
|
|
#define A_IN_AUX2_R 0x0d
|
|
|
|
#define A_IN_AUX2 A_IN_AUX2_L
|
|
|
|
|
|
|
|
/* Audigiy Outputs */
|
|
|
|
#define A_OUT_D_FRONT_L 0x00
|
|
|
|
#define A_OUT_D_FRONT_R 0x01
|
|
|
|
#define A_OUT_D_FRONT A_OUT_D_FRONT_L
|
|
|
|
#define A_OUT_D_CENTER 0x02
|
|
|
|
#define A_OUT_D_SUB 0x03
|
|
|
|
#define A_OUT_D_SIDE_L 0x04
|
|
|
|
#define A_OUT_D_SIDE_R 0x05
|
|
|
|
#define A_OUT_D_SIDE A_OUT_D_SIDE_L
|
|
|
|
#define A_OUT_D_REAR_L 0x06
|
|
|
|
#define A_OUT_D_REAR_R 0x07
|
|
|
|
#define A_OUT_D_REAR A_OUT_D_REAR_L
|
|
|
|
|
|
|
|
/* on Audigy Platinum only */
|
|
|
|
#define A_OUT_HPHONE_L 0x04
|
|
|
|
#define A_OUT_HPHONE_R 0x05
|
|
|
|
#define A_OUT_HPHONE A_OUT_HPHONE_L
|
|
|
|
|
|
|
|
#define A_OUT_A_FRONT_L 0x08
|
|
|
|
#define A_OUT_A_FRONT_R 0x09
|
|
|
|
#define A_OUT_A_FRONT A_OUT_A_FRONT_L
|
|
|
|
#define A_OUT_A_CENTER 0x0a
|
|
|
|
#define A_OUT_A_SUB 0x0b
|
|
|
|
#define A_OUT_A_SIDE_L 0x0c
|
|
|
|
#define A_OUT_A_SIDE_R 0x0d
|
|
|
|
#define A_OUT_A_SIDE A_OUT_A_SIDE_L
|
|
|
|
#define A_OUT_A_REAR_L 0x0e
|
|
|
|
#define A_OUT_A_REAR_R 0x0f
|
|
|
|
#define A_OUT_A_REAR A_OUT_A_REAR_L
|
|
|
|
#define A_OUT_AC97_L 0x10
|
|
|
|
#define A_OUT_AC97_R 0x11
|
|
|
|
#define A_OUT_AC97 A_OUT_AC97_L
|
|
|
|
#define A_OUT_ADC_REC_L 0x16
|
|
|
|
#define A_OUT_ADC_REC_R 0x17
|
|
|
|
#define A_OUT_ADC_REC A_OUT_ADC_REC_L
|
|
|
|
|
|
|
|
#include "emu10k1-alsa%diked.h"
|
|
|
|
#include "p16v-alsa%diked.h"
|
|
|
|
#include "p17v-alsa%diked.h"
|
|
|
|
|
|
|
|
#define C_FRONT_L 0
|
|
|
|
#define C_FRONT_R 1
|
|
|
|
#define C_REC_L 2
|
|
|
|
#define C_REC_R 3
|
|
|
|
#define C_REAR_L 4
|
|
|
|
#define C_REAR_R 5
|
|
|
|
#define C_CENTER 6
|
|
|
|
#define C_SUB 7
|
|
|
|
#define C_SIDE_L 8
|
|
|
|
#define C_SIDE_R 9
|
|
|
|
#define NUM_CACHES 10
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
#define CDSPDIFMUTE 0
|
|
|
|
#define ANALOGMUTE 1
|
|
|
|
#define NUM_MUTE 2
|
2007-01-06 18:59:35 +00:00
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
#define EMU_MAX_GPR 512
|
|
|
|
#define EMU_MAX_IRQ_CONSUMERS 32
|
|
|
|
|
|
|
|
struct emu_voice {
|
|
|
|
int vnum;
|
2006-08-07 22:50:26 +00:00
|
|
|
unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1;
|
2006-07-15 19:36:28 +00:00
|
|
|
int speed;
|
|
|
|
int start;
|
|
|
|
int end;
|
|
|
|
int vol;
|
|
|
|
uint32_t buf;
|
|
|
|
void *vbuf;
|
|
|
|
struct emu_voice *slave;
|
|
|
|
uint32_t sa;
|
|
|
|
uint32_t ea;
|
2007-09-12 07:43:43 +00:00
|
|
|
uint32_t routing[8];
|
|
|
|
uint32_t amounts[8];
|
2006-07-15 19:36:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct emu_memblk {
|
|
|
|
SLIST_ENTRY(emu_memblk) link;
|
|
|
|
void *buf;
|
|
|
|
char owner[16];
|
|
|
|
bus_addr_t buf_addr;
|
|
|
|
uint32_t pte_start, pte_size;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct emu_mem {
|
2006-08-07 22:44:01 +00:00
|
|
|
uint8_t bmap[EMU_MAXPAGES / 8];
|
2006-07-15 19:36:28 +00:00
|
|
|
uint32_t *ptb_pages;
|
|
|
|
void *silent_page;
|
|
|
|
bus_addr_t silent_page_addr;
|
|
|
|
bus_addr_t ptb_pages_addr;
|
|
|
|
bus_dma_tag_t dmat;
|
2007-09-12 07:43:43 +00:00
|
|
|
struct emu_sc_info *card;
|
2006-07-15 19:36:28 +00:00
|
|
|
SLIST_HEAD(, emu_memblk) blocks;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* rm */
|
|
|
|
struct emu_rm {
|
|
|
|
struct emu_sc_info *card;
|
|
|
|
struct mtx gpr_lock;
|
|
|
|
signed int allocmap[EMU_MAX_GPR];
|
|
|
|
int num_gprs;
|
|
|
|
int last_free_gpr;
|
|
|
|
int num_used;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct emu_intr_handler {
|
|
|
|
void* softc;
|
|
|
|
uint32_t intr_mask;
|
|
|
|
uint32_t inte_mask;
|
|
|
|
uint32_t(*irq_func) (void *softc, uint32_t irq);
|
|
|
|
};
|
|
|
|
|
|
|
|
struct emu_sc_info {
|
|
|
|
struct mtx lock;
|
|
|
|
struct mtx rw; /* Hardware exclusive access lock */
|
|
|
|
|
|
|
|
/* Hardware and subdevices */
|
|
|
|
device_t dev;
|
2007-01-06 18:59:35 +00:00
|
|
|
device_t pcm[RT_COUNT];
|
2006-07-15 19:36:28 +00:00
|
|
|
device_t midi[2];
|
|
|
|
uint32_t type;
|
|
|
|
uint32_t rev;
|
|
|
|
|
|
|
|
bus_space_tag_t st;
|
|
|
|
bus_space_handle_t sh;
|
|
|
|
|
|
|
|
struct cdev *cdev; /* /dev/emu10k character device */
|
|
|
|
struct mtx emu10kx_lock;
|
|
|
|
int emu10kx_isopen;
|
|
|
|
struct sbuf emu10kx_sbuf;
|
|
|
|
int emu10kx_bufptr;
|
|
|
|
|
|
|
|
|
|
|
|
/* Resources */
|
|
|
|
struct resource *reg;
|
|
|
|
struct resource *irq;
|
|
|
|
void *ih;
|
|
|
|
|
|
|
|
/* IRQ handlers */
|
|
|
|
struct emu_intr_handler ihandler[EMU_MAX_IRQ_CONSUMERS];
|
|
|
|
|
|
|
|
/* Card HW configuration */
|
2007-09-12 07:43:43 +00:00
|
|
|
unsigned int mode; /* analog / digital */
|
2006-07-15 19:36:28 +00:00
|
|
|
unsigned int mchannel_fx;
|
|
|
|
unsigned int dsp_zero;
|
|
|
|
unsigned int code_base;
|
|
|
|
unsigned int code_size;
|
|
|
|
unsigned int gpr_base;
|
|
|
|
unsigned int num_gprs;
|
|
|
|
unsigned int input_base;
|
|
|
|
unsigned int output_base;
|
|
|
|
unsigned int efxc_base;
|
|
|
|
unsigned int opcode_shift;
|
|
|
|
unsigned int high_operand_shift;
|
|
|
|
unsigned int address_mask;
|
|
|
|
uint32_t is_emu10k1:1, is_emu10k2, is_ca0102, is_ca0108:1,
|
|
|
|
has_ac97:1, has_51:1, has_71:1,
|
2007-09-12 07:43:43 +00:00
|
|
|
enable_ir:1,
|
2006-07-15 19:36:28 +00:00
|
|
|
broken_digital:1, is_cardbus:1;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
signed int mch_disabled, mch_rec, dbg_level;
|
|
|
|
signed int num_inputs;
|
2006-07-15 19:36:28 +00:00
|
|
|
unsigned int num_outputs;
|
|
|
|
unsigned int num_fxbuses;
|
|
|
|
unsigned int routing_code_start;
|
|
|
|
unsigned int routing_code_end;
|
|
|
|
|
|
|
|
/* HW resources */
|
|
|
|
struct emu_voice voice[NUM_G]; /* Hardware voices */
|
|
|
|
uint32_t irq_mask[EMU_MAX_IRQ_CONSUMERS]; /* IRQ manager data */
|
|
|
|
int timer[EMU_MAX_IRQ_CONSUMERS]; /* timer */
|
|
|
|
int timerinterval;
|
|
|
|
struct emu_rm *rm;
|
|
|
|
struct emu_mem mem; /* memory */
|
|
|
|
|
|
|
|
/* Mixer */
|
|
|
|
int mixer_gpr[NUM_MIXERS];
|
|
|
|
int mixer_volcache[NUM_MIXERS];
|
|
|
|
int cache_gpr[NUM_CACHES];
|
2007-09-12 07:43:43 +00:00
|
|
|
int dummy_gpr;
|
|
|
|
int mute_gpr[NUM_MUTE];
|
2006-07-15 19:36:28 +00:00
|
|
|
struct sysctl_ctx_list *ctx;
|
|
|
|
struct sysctl_oid *root;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error);
|
|
|
|
static void* emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr);
|
|
|
|
static void emu_free(struct emu_mem *mem, void *dmabuf);
|
|
|
|
static void* emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char * owner);
|
|
|
|
static int emu_memfree(struct emu_mem *mem, void *membuf);
|
|
|
|
static int emu_memstart(struct emu_mem *mem, void *membuf);
|
|
|
|
|
|
|
|
/* /dev */
|
|
|
|
static int emu10kx_dev_init(struct emu_sc_info *sc);
|
|
|
|
static int emu10kx_dev_uninit(struct emu_sc_info *sc);
|
|
|
|
static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s);
|
|
|
|
|
|
|
|
static void emumix_set_mode(struct emu_sc_info *sc, int mode);
|
|
|
|
static void emumix_set_spdif_mode(struct emu_sc_info *sc, int mode);
|
|
|
|
static void emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol);
|
|
|
|
static void emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val);
|
|
|
|
static int sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS);
|
|
|
|
|
|
|
|
static int emu_rm_init(struct emu_sc_info *sc);
|
|
|
|
static int emu_rm_uninit(struct emu_sc_info *sc);
|
|
|
|
static int emu_rm_gpr_alloc(struct emu_rm *rm, int count);
|
|
|
|
|
2007-01-06 18:59:35 +00:00
|
|
|
static unsigned int emu_getcard(device_t dev);
|
2006-07-15 19:36:28 +00:00
|
|
|
static uint32_t emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size);
|
|
|
|
static void emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size);
|
|
|
|
static void emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data);
|
|
|
|
|
|
|
|
static void emu_vstop(struct emu_sc_info *sc, char channel, int enable);
|
|
|
|
|
|
|
|
static void emu_intr(void *p);
|
|
|
|
static void emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data);
|
|
|
|
static void emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc);
|
|
|
|
static void emu_initefx(struct emu_sc_info *sc);
|
|
|
|
|
|
|
|
static int emu_cardbus_init(struct emu_sc_info *sc);
|
2007-02-23 19:41:16 +00:00
|
|
|
static int emu_init(struct emu_sc_info *sc);
|
2006-07-15 19:36:28 +00:00
|
|
|
static int emu_uninit(struct emu_sc_info *sc);
|
|
|
|
|
|
|
|
static int emu_read_ivar(device_t bus __unused, device_t dev, int ivar_index, uintptr_t * result);
|
|
|
|
static int emu_write_ivar(device_t bus __unused, device_t dev __unused,
|
|
|
|
int ivar_index, uintptr_t value __unused);
|
|
|
|
|
|
|
|
static int emu_pci_probe(device_t dev);
|
|
|
|
static int emu_pci_attach(device_t dev);
|
|
|
|
static int emu_pci_detach(device_t dev);
|
|
|
|
static int emu_modevent(module_t mod __unused, int cmd, void *data __unused);
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
#ifdef SND_EMU10KX_DEBUG
|
|
|
|
|
|
|
|
#define EMU_MTX_DEBUG() do { \
|
|
|
|
if (mtx_owned(&sc->rw)) { \
|
|
|
|
printf("RW owned in %s line %d for %s\n", __func__, \
|
|
|
|
__LINE__ , device_get_nameunit(sc->dev)); \
|
|
|
|
printf("rw lock owned: %d\n", mtx_owned(&sc->rw)); \
|
|
|
|
printf("rw lock: value %x thread %x\n", \
|
|
|
|
((&sc->rw)->mtx_lock & ~MTX_FLAGMASK), \
|
|
|
|
(uintptr_t)curthread); \
|
|
|
|
printf("rw lock: recursed %d\n", mtx_recursed(&sc->rw));\
|
|
|
|
db_show_mtx(&sc->rw); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
#else
|
|
|
|
#define EMU_MTX_DEBUG() do { \
|
|
|
|
} while (0)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define EMU_RWLOCK() do { \
|
|
|
|
EMU_MTX_DEBUG(); \
|
|
|
|
mtx_lock(&(sc->rw)); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EMU_RWUNLOCK() do { \
|
|
|
|
mtx_unlock(&(sc->rw)); \
|
|
|
|
EMU_MTX_DEBUG(); \
|
|
|
|
} while (0)
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
/* Supported cards */
|
|
|
|
struct emu_hwinfo {
|
|
|
|
uint16_t vendor;
|
|
|
|
uint16_t device;
|
|
|
|
uint16_t subvendor;
|
|
|
|
uint16_t subdevice;
|
|
|
|
char SBcode[8];
|
|
|
|
char desc[32];
|
|
|
|
int flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct emu_hwinfo emu_cards[] = {
|
2007-01-06 18:59:35 +00:00
|
|
|
{0xffff, 0xffff, 0xffff, 0xffff, "BADCRD", "Not a compatible card", 0},
|
2006-07-15 19:36:28 +00:00
|
|
|
/* 0x0020..0x002f 4.0 EMU10K1 cards */
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x0020, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x0021, "CT4620", "SBLive!", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x002f, "CT????", "SBLive! mainboard implementation", HAS_AC97 | IS_EMU10K1},
|
|
|
|
|
|
|
|
/* (range unknown) 5.1 EMU10K1 cards */
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x100a, "CT????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
|
|
|
|
|
|
|
/* 0x80??..0x805? 4.0 EMU10K1 cards */
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8022, "CT4780", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8023, "CT4790", "SB PCI512", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8024, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8025, "CT????", "SBLive! Mainboard Implementation", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8026, "CT4830", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8027, "CT4832", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8028, "CT4760", "SBLive! OEM version", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8031, "CT4831", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8040, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8051, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1},
|
|
|
|
|
|
|
|
/* 0x8061..0x???? 5.1 EMU10K1 cards */
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8061, "SB????", "SBLive! Player 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
2007-09-12 07:43:43 +00:00
|
|
|
{0x1102, 0x0002, 0x1102, 0x8062, "CT4830", "SBLive! 1024", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
2006-07-15 19:36:28 +00:00
|
|
|
{0x1102, 0x0002, 0x1102, 0x8064, "SB????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8065, "SB0220", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8066, "CT4780", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x8067, "SB????", "SBLive!", HAS_AC97 | HAS_51 | IS_EMU10K1},
|
|
|
|
|
|
|
|
/* Generic SB Live! */
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x0000, "SB????", "SBLive! (Unknown model)", HAS_AC97 | IS_EMU10K1},
|
|
|
|
|
|
|
|
/* 0x0041..0x0043 EMU10K2 (some kind of Audigy) cards */
|
|
|
|
|
|
|
|
/* 0x0051..0x0051 5.1 CA0100-IAF cards */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x0051, "SB0090", "Audigy", HAS_AC97 | HAS_51 | IS_EMU10K2},
|
|
|
|
/* ES is CA0100-IDF chip that don't work in digital mode */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x0052, "SB0160", "Audigy ES", HAS_AC97 | HAS_71 | IS_EMU10K2 | BROKEN_DIGITAL},
|
|
|
|
/* 0x0053..0x005C 5.1 CA0101-NAF cards */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x0053, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x0058, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2},
|
|
|
|
|
|
|
|
/* 0x1002..0x1009 5.1 CA0102-IAT cards */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x1002, "SB????", "Audigy 2 Platinum", HAS_51 | IS_CA0102},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x1005, "SB????", "Audigy 2 Platinum EX", HAS_51 | IS_CA0102},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x1007, "SB0240", "Audigy 2", HAS_AC97 | HAS_51 | IS_CA0102},
|
|
|
|
|
|
|
|
/* 0x2001..0x2003 7.1 CA0102-ICT cards */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x2001, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x2002, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
|
2007-01-06 18:59:35 +00:00
|
|
|
/* XXX No reports about 0x2003 & 0x2004 cards */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x2003, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x2004, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x2005, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102},
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* (range unknown) 7.1 CA0102-xxx Audigy 4 cards */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x2007, "SB0380", "Audigy 4 Pro", HAS_AC97 | HAS_71 | IS_CA0102},
|
|
|
|
|
|
|
|
/* Generic Audigy or Audigy 2 */
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x0000, "SB????", "Audigy (Unknown model)", HAS_AC97 | HAS_51 | IS_EMU10K2},
|
|
|
|
|
|
|
|
/* We don't support CA0103-DAT (Audigy LS) cards */
|
|
|
|
/* There is NO CA0104-xxx cards */
|
|
|
|
/* There is NO CA0105-xxx cards */
|
|
|
|
/* We don't support CA0106-DAT (SB Live! 24 bit) cards */
|
|
|
|
/* There is NO CA0107-xxx cards */
|
|
|
|
|
|
|
|
/* 0x1000..0x1001 7.1 CA0108-IAT cards */
|
|
|
|
{0x1102, 0x0008, 0x1102, 0x1000, "SB????", "Audigy 2 LS", HAS_AC97 | HAS_51 | IS_CA0108 | DIGITAL_ONLY},
|
|
|
|
{0x1102, 0x0008, 0x1102, 0x1001, "SB0400", "Audigy 2 Value", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY},
|
|
|
|
{0x1102, 0x0008, 0x1102, 0x1021, "SB0610", "Audigy 4", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY},
|
|
|
|
|
|
|
|
{0x1102, 0x0008, 0x1102, 0x2001, "SB0530", "Audigy 2 ZS CardBus", HAS_AC97 | HAS_71 | IS_CA0108 | IS_CARDBUS},
|
|
|
|
|
|
|
|
{0x1102, 0x0008, 0x0000, 0x0000, "SB????", "Audigy 2 Value (Unknown model)", HAS_AC97 | HAS_51 | IS_CA0108},
|
|
|
|
};
|
|
|
|
/* Unsupported cards */
|
|
|
|
|
|
|
|
static struct emu_hwinfo emu_bad_cards[] = {
|
|
|
|
/* APS cards should be possible to support */
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x4001, "EMUAPS", "E-mu APS", 0},
|
|
|
|
{0x1102, 0x0002, 0x1102, 0x4002, "EMUAPS", "E-mu APS", 0},
|
|
|
|
{0x1102, 0x0004, 0x1102, 0x4001, "EMU???", "E-mu 1212m [4001]", 0},
|
|
|
|
/* Similar-named ("Live!" or "Audigy") cards on different chipsets */
|
|
|
|
{0x1102, 0x8064, 0x0000, 0x0000, "SB0100", "SBLive! 5.1 OEM", 0},
|
|
|
|
{0x1102, 0x0006, 0x0000, 0x0000, "SB0200", "DELL OEM SBLive! Value", 0},
|
|
|
|
{0x1102, 0x0007, 0x0000, 0x0000, "SB0310", "Audigy LS", 0},
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get best known information about device.
|
|
|
|
*/
|
2007-01-06 18:59:35 +00:00
|
|
|
static unsigned int
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_getcard(device_t dev)
|
|
|
|
{
|
|
|
|
uint16_t device;
|
|
|
|
uint16_t subdevice;
|
|
|
|
int n_cards;
|
2007-01-06 18:59:35 +00:00
|
|
|
unsigned int thiscard;
|
2006-07-15 19:36:28 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
device = pci_read_config(dev, PCIR_DEVICE, /* bytes */ 2);
|
|
|
|
subdevice = pci_read_config(dev, PCIR_SUBDEV_0, /* bytes */ 2);
|
|
|
|
|
|
|
|
n_cards = sizeof(emu_cards) / sizeof(struct emu_hwinfo);
|
2007-01-06 18:59:35 +00:00
|
|
|
thiscard = 0;
|
|
|
|
for (i = 1; i < n_cards; i++) {
|
2006-07-15 19:36:28 +00:00
|
|
|
if (device == emu_cards[i].device) {
|
|
|
|
if (subdevice == emu_cards[i].subdevice) {
|
|
|
|
thiscard = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (0x0000 == emu_cards[i].subdevice) {
|
|
|
|
thiscard = i;
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* don't break, we can get more specific card
|
|
|
|
* later in the list.
|
|
|
|
*/
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n_cards = sizeof(emu_bad_cards) / sizeof(struct emu_hwinfo);
|
|
|
|
for (i = 0; i < n_cards; i++) {
|
|
|
|
if (device == emu_bad_cards[i].device) {
|
|
|
|
if (subdevice == emu_bad_cards[i].subdevice) {
|
2007-01-06 18:59:35 +00:00
|
|
|
thiscard = 0;
|
2006-07-15 19:36:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (0x0000 == emu_bad_cards[i].subdevice) {
|
2007-01-06 18:59:35 +00:00
|
|
|
thiscard = 0;
|
2006-07-15 19:36:28 +00:00
|
|
|
break; /* we avoid all this cards */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (thiscard);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Base hardware interface are 32 (Audigy) or 64 (Audigy2) registers.
|
|
|
|
* Some of them are used directly, some of them provide pointer / data pairs.
|
|
|
|
*/
|
|
|
|
static uint32_t
|
|
|
|
emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size)
|
|
|
|
{
|
|
|
|
|
|
|
|
KASSERT(sc != NULL, ("emu_rd: NULL sc"));
|
|
|
|
switch (size) {
|
|
|
|
case 1:
|
|
|
|
return (bus_space_read_1(sc->st, sc->sh, regno));
|
|
|
|
case 2:
|
|
|
|
return (bus_space_read_2(sc->st, sc->sh, regno));
|
|
|
|
case 4:
|
|
|
|
return (bus_space_read_4(sc->st, sc->sh, regno));
|
|
|
|
}
|
|
|
|
return (0xffffffff);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size)
|
|
|
|
{
|
|
|
|
|
|
|
|
KASSERT(sc != NULL, ("emu_rd: NULL sc"));
|
|
|
|
switch (size) {
|
|
|
|
case 1:
|
|
|
|
bus_space_write_1(sc->st, sc->sh, regno, data);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
bus_space_write_2(sc->st, sc->sh, regno, data);
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
bus_space_write_4(sc->st, sc->sh, regno, data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* PTR / DATA interface. Access to EMU10Kx is made
|
|
|
|
* via (channel, register) pair. Some registers are channel-specific,
|
|
|
|
* some not.
|
|
|
|
*/
|
|
|
|
uint32_t
|
|
|
|
emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg)
|
|
|
|
{
|
|
|
|
uint32_t ptr, val, mask, size, offset;
|
|
|
|
|
|
|
|
ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK);
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
EMU_RWLOCK();
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_wr_nolock(sc, PTR, ptr, 4);
|
|
|
|
val = emu_rd_nolock(sc, DATA, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
EMU_RWUNLOCK();
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
/*
|
|
|
|
* XXX Some register numbers has data size and offset encoded in
|
|
|
|
* it to get only part of 32bit register. This use is not described
|
|
|
|
* in register name, be careful!
|
|
|
|
*/
|
|
|
|
if (reg & 0xff000000) {
|
|
|
|
size = (reg >> 24) & 0x3f;
|
|
|
|
offset = (reg >> 16) & 0x1f;
|
|
|
|
mask = ((1 << size) - 1) << offset;
|
|
|
|
val &= mask;
|
|
|
|
val >>= offset;
|
|
|
|
}
|
|
|
|
return (val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data)
|
|
|
|
{
|
|
|
|
uint32_t ptr, mask, size, offset;
|
2007-09-12 07:43:43 +00:00
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK);
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
EMU_RWLOCK();
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_wr_nolock(sc, PTR, ptr, 4);
|
|
|
|
/*
|
|
|
|
* XXX Another kind of magic encoding in register number. This can
|
|
|
|
* give you side effect - it will read previous data from register
|
|
|
|
* and change only required bits.
|
|
|
|
*/
|
|
|
|
if (reg & 0xff000000) {
|
|
|
|
size = (reg >> 24) & 0x3f;
|
|
|
|
offset = (reg >> 16) & 0x1f;
|
|
|
|
mask = ((1 << size) - 1) << offset;
|
|
|
|
data <<= offset;
|
|
|
|
data &= mask;
|
|
|
|
data |= emu_rd_nolock(sc, DATA, 4) & ~mask;
|
|
|
|
}
|
|
|
|
emu_wr_nolock(sc, DATA, data, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
EMU_RWUNLOCK();
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* PTR2 / DATA2 interface. Access to P16v is made
|
|
|
|
* via (channel, register) pair. Some registers are channel-specific,
|
|
|
|
* some not. This interface is supported by CA0102 and CA0108 chips only.
|
|
|
|
*/
|
|
|
|
uint32_t
|
|
|
|
emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg)
|
|
|
|
{
|
|
|
|
uint32_t val;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* XXX separate lock? */
|
|
|
|
EMU_RWLOCK();
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4);
|
|
|
|
val = emu_rd_nolock(sc, DATA2, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
EMU_RWUNLOCK();
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
return (val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data)
|
|
|
|
{
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
EMU_RWLOCK();
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4);
|
|
|
|
emu_wr_nolock(sc, DATA2, data, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
EMU_RWUNLOCK();
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* XXX CardBus interface. Not tested on any real hardware.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data)
|
|
|
|
{
|
|
|
|
uint32_t val;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* 0x38 is IPE3 (CD S/PDIF interrupt pending register) on CA0102. Seems
|
2006-07-15 19:36:28 +00:00
|
|
|
* to be some reg/value accessible kind of config register on CardBus
|
2007-09-12 07:43:43 +00:00
|
|
|
* CA0108, with value(?) in top 16 bit, address(?) in low 16
|
|
|
|
*/
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
val = emu_rd_nolock(sc, 0x38, 4);
|
|
|
|
emu_wr_nolock(sc, 0x38, data, 4);
|
|
|
|
val = emu_rd_nolock(sc, 0x38, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Direct hardware register access
|
2007-09-12 07:43:43 +00:00
|
|
|
* Assume that it is never used to access PTR-based registers and can run unlocked.
|
2006-07-15 19:36:28 +00:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size)
|
|
|
|
{
|
2007-09-12 07:43:43 +00:00
|
|
|
KASSERT(regno != PTR, ("emu_wr: attempt to write to PTR"));
|
|
|
|
KASSERT(regno != PTR2, ("emu_wr: attempt to write to PTR2"));
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
emu_wr_nolock(sc, regno, data, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size)
|
|
|
|
{
|
|
|
|
uint32_t rd;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
KASSERT(regno != DATA, ("emu_rd: attempt to read DATA"));
|
|
|
|
KASSERT(regno != DATA2, ("emu_rd: attempt to read DATA2"));
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
rd = emu_rd_nolock(sc, regno, size);
|
|
|
|
return (rd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Enabling IR MIDI messages is another kind of black magic. It just
|
|
|
|
* has to be made this way. It really do it.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
emu_enable_ir(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
uint32_t iocfg;
|
|
|
|
|
|
|
|
if (sc->is_emu10k2 || sc->is_ca0102) {
|
|
|
|
iocfg = emu_rd_nolock(sc, A_IOCFG, 2);
|
|
|
|
emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT2, 2);
|
|
|
|
DELAY(500);
|
|
|
|
emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, 2);
|
|
|
|
DELAY(500);
|
|
|
|
emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1, 2);
|
|
|
|
DELAY(100);
|
|
|
|
emu_wr_nolock(sc, A_IOCFG, iocfg, 2);
|
|
|
|
device_printf(sc->dev, "Audigy IR MIDI events enabled.\n");
|
|
|
|
sc->enable_ir = 1;
|
|
|
|
}
|
|
|
|
if (sc->is_emu10k1) {
|
|
|
|
iocfg = emu_rd_nolock(sc, HCFG, 4);
|
|
|
|
emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT2, 4);
|
|
|
|
DELAY(500);
|
|
|
|
emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT1 | HCFG_GPOUT2, 4);
|
|
|
|
DELAY(100);
|
|
|
|
emu_wr_nolock(sc, HCFG, iocfg, 4);
|
|
|
|
device_printf(sc->dev, "SB Live! IR MIDI events enabled.\n");
|
|
|
|
sc->enable_ir = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* emu_timer_ - HW timer managment
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
emu_timer_create(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
int i, timer;
|
|
|
|
|
|
|
|
timer = -1;
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
mtx_lock(&sc->lock);
|
2006-07-15 19:36:28 +00:00
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
|
|
|
|
if (sc->timer[i] == 0) {
|
|
|
|
sc->timer[i] = -1; /* disable it */
|
|
|
|
timer = i;
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_unlock(&sc->lock);
|
2006-07-15 19:36:28 +00:00
|
|
|
return (timer);
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_unlock(&sc->lock);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_timer_set(struct emu_sc_info *sc, int timer, int delay)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (timer < 0)
|
2007-01-06 18:59:35 +00:00
|
|
|
return (-1);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
RANGE(delay, 16, 1024);
|
2007-01-06 18:59:35 +00:00
|
|
|
RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_lock(&sc->lock);
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->timer[timer] = delay;
|
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
|
|
|
|
if (sc->timerinterval > sc->timer[i])
|
|
|
|
sc->timerinterval = sc->timer[i];
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* XXX */
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2);
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
return (timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_timer_enable(struct emu_sc_info *sc, int timer, int go)
|
|
|
|
{
|
|
|
|
uint32_t x;
|
|
|
|
int ena_int;
|
|
|
|
int i;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (timer < 0)
|
2007-01-06 18:59:35 +00:00
|
|
|
return (-1);
|
|
|
|
|
|
|
|
RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
mtx_lock(&sc->lock);
|
|
|
|
|
|
|
|
if ((go == 1) && (sc->timer[timer] < 0))
|
|
|
|
sc->timer[timer] = -sc->timer[timer];
|
|
|
|
if ((go == 0) && (sc->timer[timer] > 0))
|
|
|
|
sc->timer[timer] = -sc->timer[timer];
|
|
|
|
|
|
|
|
ena_int = 0;
|
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) {
|
|
|
|
if (sc->timerinterval > sc->timer[i])
|
|
|
|
sc->timerinterval = sc->timer[i];
|
|
|
|
if (sc->timer[i] > 0)
|
|
|
|
ena_int = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2);
|
|
|
|
|
|
|
|
if (ena_int == 1) {
|
|
|
|
x = emu_rd(sc, INTE, 4);
|
|
|
|
x |= INTE_INTERVALTIMERENB;
|
|
|
|
emu_wr(sc, INTE, x, 4);
|
|
|
|
} else {
|
|
|
|
x = emu_rd(sc, INTE, 4);
|
|
|
|
x &= ~INTE_INTERVALTIMERENB;
|
|
|
|
emu_wr(sc, INTE, x, 4);
|
|
|
|
}
|
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_timer_clear(struct emu_sc_info *sc, int timer)
|
|
|
|
{
|
2007-09-12 07:43:43 +00:00
|
|
|
if (timer < 0)
|
2007-01-06 18:59:35 +00:00
|
|
|
return (-1);
|
|
|
|
|
|
|
|
RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_timer_enable(sc, timer, 0);
|
|
|
|
|
|
|
|
mtx_lock(&sc->lock);
|
|
|
|
if (sc->timer[timer] != 0)
|
|
|
|
sc->timer[timer] = 0;
|
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
|
|
|
|
return (timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* emu_intr_ - HW interrupt handler managment
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
uint32_t x;
|
|
|
|
|
|
|
|
mtx_lock(&sc->lock);
|
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
|
|
|
|
if (sc->ihandler[i].inte_mask == 0) {
|
|
|
|
sc->ihandler[i].inte_mask = inte_mask;
|
|
|
|
sc->ihandler[i].intr_mask = intr_mask;
|
|
|
|
sc->ihandler[i].softc = isc;
|
|
|
|
sc->ihandler[i].irq_func = func;
|
|
|
|
x = emu_rd(sc, INTE, 4);
|
|
|
|
x |= inte_mask;
|
|
|
|
emu_wr(sc, INTE, x, 4);
|
|
|
|
mtx_unlock(&sc->lock);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1)
|
|
|
|
device_printf(sc->dev, "ihandle %d registered\n", i);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
return (i);
|
|
|
|
}
|
|
|
|
mtx_unlock(&sc->lock);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1)
|
|
|
|
device_printf(sc->dev, "ihandle not registered\n");
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_intr_unregister(struct emu_sc_info *sc, int hnumber)
|
|
|
|
{
|
|
|
|
uint32_t x;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mtx_lock(&sc->lock);
|
|
|
|
|
|
|
|
if (sc->ihandler[hnumber].inte_mask == 0) {
|
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
x = emu_rd(sc, INTE, 4);
|
|
|
|
x &= ~sc->ihandler[hnumber].inte_mask;
|
|
|
|
|
|
|
|
sc->ihandler[hnumber].inte_mask = 0;
|
|
|
|
sc->ihandler[hnumber].intr_mask = 0;
|
|
|
|
sc->ihandler[hnumber].softc = NULL;
|
|
|
|
sc->ihandler[hnumber].irq_func = NULL;
|
|
|
|
|
2007-10-12 06:03:46 +00:00
|
|
|
/* other interrupt handlers may use this INTE value */
|
2006-07-15 19:36:28 +00:00
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
|
|
|
|
if (sc->ihandler[i].inte_mask != 0)
|
|
|
|
x |= sc->ihandler[i].inte_mask;
|
|
|
|
|
|
|
|
emu_wr(sc, INTE, x, 4);
|
|
|
|
|
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
return (hnumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_intr(void *p)
|
|
|
|
{
|
|
|
|
struct emu_sc_info *sc = (struct emu_sc_info *)p;
|
|
|
|
uint32_t stat, ack;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
stat = emu_rd(sc, IPR, 4);
|
|
|
|
ack = 0;
|
|
|
|
if (stat == 0)
|
|
|
|
break;
|
|
|
|
emu_wr(sc, IPR, stat, 4);
|
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) {
|
|
|
|
if ((((sc->ihandler[i].intr_mask) & stat) != 0) &&
|
|
|
|
(((void *)sc->ihandler[i].irq_func) != NULL)) {
|
|
|
|
ack |= sc->ihandler[i].irq_func(sc->ihandler[i].softc,
|
|
|
|
(sc->ihandler[i].intr_mask) & stat);
|
|
|
|
}
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1)
|
|
|
|
if (stat & (~ack))
|
|
|
|
device_printf(sc->dev, "Unhandled interrupt: %08x\n", stat & (~ack));
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((sc->is_ca0102) || (sc->is_ca0108))
|
|
|
|
for (;;) {
|
|
|
|
stat = emu_rd(sc, IPR2, 4);
|
|
|
|
ack = 0;
|
|
|
|
if (stat == 0)
|
|
|
|
break;
|
|
|
|
emu_wr(sc, IPR2, stat, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1)
|
|
|
|
device_printf(sc->dev, "IPR2: %08x\n", stat);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
break; /* to avoid infinite loop. shoud be removed
|
|
|
|
* after completion of P16V interface. */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sc->is_ca0102)
|
|
|
|
for (;;) {
|
|
|
|
stat = emu_rd(sc, IPR3, 4);
|
|
|
|
ack = 0;
|
|
|
|
if (stat == 0)
|
|
|
|
break;
|
|
|
|
emu_wr(sc, IPR3, stat, 4);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1)
|
|
|
|
device_printf(sc->dev, "IPR3: %08x\n", stat);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
break; /* to avoid infinite loop. should be removed
|
|
|
|
* after completion of S/PDIF interface */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get data from private emu10kx structure for PCM buffer allocation.
|
|
|
|
* Used by PCM code only.
|
|
|
|
*/
|
|
|
|
bus_dma_tag_t
|
|
|
|
emu_gettag(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
return (sc->mem.dmat);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error)
|
|
|
|
{
|
|
|
|
bus_addr_t *phys = (bus_addr_t *) arg;
|
|
|
|
|
|
|
|
*phys = error ? 0 : (bus_addr_t) segs->ds_addr;
|
|
|
|
|
|
|
|
if (bootverbose) {
|
|
|
|
printf("emu10kx: setmap (%lx, %lx), nseg=%d, error=%d\n",
|
|
|
|
(unsigned long)segs->ds_addr, (unsigned long)segs->ds_len,
|
|
|
|
nseg, error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr)
|
|
|
|
{
|
|
|
|
void *dmabuf;
|
|
|
|
bus_dmamap_t map;
|
2007-09-12 07:43:43 +00:00
|
|
|
int error;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
*addr = 0;
|
2007-09-12 07:43:43 +00:00
|
|
|
if ((error = bus_dmamem_alloc(mem->dmat, &dmabuf, BUS_DMA_NOWAIT, &map))) {
|
|
|
|
if (mem->card->dbg_level > 2)
|
|
|
|
device_printf(mem->card->dev, "emu_malloc: failed to alloc DMA map: %d\n", error);
|
2006-07-15 19:36:28 +00:00
|
|
|
return (NULL);
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
|
|
|
if ((error = bus_dmamap_load(mem->dmat, map, dmabuf, sz, emu_setmap, addr, 0)) || !*addr) {
|
|
|
|
if (mem->card->dbg_level > 2)
|
|
|
|
device_printf(mem->card->dev, "emu_malloc: failed to load DMA memory: %d\n", error);
|
2006-07-15 19:36:28 +00:00
|
|
|
return (NULL);
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
return (dmabuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_free(struct emu_mem *mem, void *dmabuf)
|
|
|
|
{
|
|
|
|
bus_dmamem_free(mem->dmat, dmabuf, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char *owner)
|
|
|
|
{
|
|
|
|
uint32_t blksz, start, idx, ofs, tmp, found;
|
|
|
|
struct emu_memblk *blk;
|
|
|
|
void *membuf;
|
|
|
|
|
|
|
|
blksz = sz / EMUPAGESIZE;
|
|
|
|
if (sz > (blksz * EMUPAGESIZE))
|
|
|
|
blksz++;
|
2007-09-12 07:43:43 +00:00
|
|
|
if (blksz > EMU_MAX_BUFSZ / EMUPAGESIZE) {
|
|
|
|
if (mem->card->dbg_level > 2)
|
|
|
|
device_printf(mem->card->dev, "emu_memalloc: memory request tool large\n");
|
2006-08-07 22:44:01 +00:00
|
|
|
return (NULL);
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
/* find a free block in the bitmap */
|
|
|
|
found = 0;
|
|
|
|
start = 1;
|
2006-08-07 22:44:01 +00:00
|
|
|
while (!found && start + blksz < EMU_MAXPAGES) {
|
2006-07-15 19:36:28 +00:00
|
|
|
found = 1;
|
|
|
|
for (idx = start; idx < start + blksz; idx++)
|
|
|
|
if (mem->bmap[idx >> 3] & (1 << (idx & 7)))
|
|
|
|
found = 0;
|
|
|
|
if (!found)
|
|
|
|
start++;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
if (!found) {
|
|
|
|
if (mem->card->dbg_level > 2)
|
|
|
|
device_printf(mem->card->dev, "emu_memalloc: no free space in bitmap\n");
|
2006-07-15 19:36:28 +00:00
|
|
|
return (NULL);
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (blk == NULL) {
|
|
|
|
if (mem->card->dbg_level > 2)
|
|
|
|
device_printf(mem->card->dev, "emu_memalloc: buffer allocation failed\n");
|
2006-07-15 19:36:28 +00:00
|
|
|
return (NULL);
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
bzero(blk, sizeof(*blk));
|
|
|
|
membuf = emu_malloc(mem, sz, &blk->buf_addr);
|
|
|
|
*addr = blk->buf_addr;
|
|
|
|
if (membuf == NULL) {
|
2007-09-12 07:43:43 +00:00
|
|
|
if (mem->card->dbg_level > 2)
|
|
|
|
device_printf(mem->card->dev, "emu_memalloc: can't setup HW memory\n");
|
2006-07-15 19:36:28 +00:00
|
|
|
free(blk, M_DEVBUF);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
blk->buf = membuf;
|
|
|
|
blk->pte_start = start;
|
|
|
|
blk->pte_size = blksz;
|
|
|
|
strncpy(blk->owner, owner, 15);
|
|
|
|
blk->owner[15] = '\0';
|
|
|
|
ofs = 0;
|
|
|
|
for (idx = start; idx < start + blksz; idx++) {
|
|
|
|
mem->bmap[idx >> 3] |= 1 << (idx & 7);
|
|
|
|
tmp = (uint32_t) (u_long) ((uint8_t *) blk->buf_addr + ofs);
|
|
|
|
mem->ptb_pages[idx] = (tmp << 1) | idx;
|
|
|
|
ofs += EMUPAGESIZE;
|
|
|
|
}
|
|
|
|
SLIST_INSERT_HEAD(&mem->blocks, blk, link);
|
|
|
|
return (membuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_memfree(struct emu_mem *mem, void *membuf)
|
|
|
|
{
|
|
|
|
uint32_t idx, tmp;
|
|
|
|
struct emu_memblk *blk, *i;
|
|
|
|
|
|
|
|
blk = NULL;
|
|
|
|
SLIST_FOREACH(i, &mem->blocks, link) {
|
|
|
|
if (i->buf == membuf)
|
|
|
|
blk = i;
|
|
|
|
}
|
|
|
|
if (blk == NULL)
|
|
|
|
return (EINVAL);
|
|
|
|
SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link);
|
|
|
|
emu_free(mem, membuf);
|
|
|
|
tmp = (uint32_t) (mem->silent_page_addr) << 1;
|
|
|
|
for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) {
|
|
|
|
mem->bmap[idx >> 3] &= ~(1 << (idx & 7));
|
|
|
|
mem->ptb_pages[idx] = tmp | idx;
|
|
|
|
}
|
|
|
|
free(blk, M_DEVBUF);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_memstart(struct emu_mem *mem, void *membuf)
|
|
|
|
{
|
|
|
|
struct emu_memblk *blk, *i;
|
|
|
|
|
|
|
|
blk = NULL;
|
|
|
|
SLIST_FOREACH(i, &mem->blocks, link) {
|
|
|
|
if (i->buf == membuf)
|
|
|
|
blk = i;
|
|
|
|
}
|
|
|
|
if (blk == NULL)
|
|
|
|
return (-1);
|
|
|
|
return (blk->pte_start);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
emu_rate_to_pitch(uint32_t rate)
|
|
|
|
{
|
|
|
|
static uint32_t logMagTable[128] = {
|
|
|
|
0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
|
|
|
|
0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
|
|
|
|
0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
|
|
|
|
0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
|
|
|
|
0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
|
|
|
|
0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
|
|
|
|
0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
|
|
|
|
0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
|
|
|
|
0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
|
|
|
|
0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
|
|
|
|
0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
|
|
|
|
0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
|
|
|
|
0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
|
|
|
|
0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
|
|
|
|
0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
|
|
|
|
0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
|
|
|
|
};
|
|
|
|
static char logSlopeTable[128] = {
|
|
|
|
0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
|
|
|
|
0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
|
|
|
|
0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
|
|
|
|
0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
|
|
|
|
0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
|
|
|
|
0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
|
|
|
|
0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
|
|
|
|
0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
|
|
|
|
0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
|
|
|
|
0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
|
|
|
|
0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
|
|
|
|
0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
|
|
|
|
0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
|
|
|
|
0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
|
|
|
|
0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
|
|
|
|
};
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (rate == 0)
|
|
|
|
return (0);
|
|
|
|
rate *= 11185; /* Scale 48000 to 0x20002380 */
|
|
|
|
for (i = 31; i > 0; i--) {
|
|
|
|
if (rate & 0x80000000) { /* Detect leading "1" */
|
|
|
|
return (((uint32_t) (i - 15) << 20) +
|
|
|
|
logMagTable[0x7f & (rate >> 24)] +
|
|
|
|
(0x7f & (rate >> 17)) *
|
|
|
|
logSlopeTable[0x7f & (rate >> 24)]);
|
|
|
|
}
|
|
|
|
rate <<= 1;
|
|
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
emu_rate_to_linearpitch(uint32_t rate)
|
|
|
|
{
|
|
|
|
rate = (rate << 8) / 375;
|
|
|
|
return ((rate >> 1) + (rate & 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct emu_voice *
|
|
|
|
emu_valloc(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
struct emu_voice *v;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
v = NULL;
|
|
|
|
mtx_lock(&sc->lock);
|
|
|
|
for (i = 0; i < NUM_G && sc->voice[i].busy; i++);
|
|
|
|
if (i < NUM_G) {
|
|
|
|
v = &sc->voice[i];
|
|
|
|
v->busy = 1;
|
|
|
|
}
|
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
return (v);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_vfree(struct emu_sc_info *sc, struct emu_voice *v)
|
|
|
|
{
|
|
|
|
int i, r;
|
|
|
|
|
|
|
|
mtx_lock(&sc->lock);
|
|
|
|
for (i = 0; i < NUM_G; i++) {
|
|
|
|
if (v == &sc->voice[i] && sc->voice[i].busy) {
|
|
|
|
v->busy = 0;
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* XXX What we should do with mono channels?
|
|
|
|
* See -pcm.c emupchan_init for other side of
|
|
|
|
* this problem
|
|
|
|
*/
|
2006-07-15 19:36:28 +00:00
|
|
|
if (v->slave != NULL)
|
|
|
|
r = emu_memfree(&sc->mem, v->vbuf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mtx_unlock(&sc->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s,
|
|
|
|
uint32_t sz, struct snd_dbuf *b)
|
|
|
|
{
|
|
|
|
void *vbuf;
|
|
|
|
bus_addr_t tmp_addr;
|
|
|
|
|
|
|
|
vbuf = emu_memalloc(&sc->mem, sz, &tmp_addr, "vinit");
|
2007-09-12 07:43:43 +00:00
|
|
|
if (vbuf == NULL) {
|
|
|
|
if(sc->dbg_level > 2)
|
|
|
|
device_printf(sc->dev, "emu_memalloc returns NULL in enu_vinit\n");
|
2006-07-15 19:36:28 +00:00
|
|
|
return (ENOMEM);
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
if (b != NULL)
|
|
|
|
sndbuf_setup(b, vbuf, sz);
|
|
|
|
m->start = emu_memstart(&sc->mem, vbuf) * EMUPAGESIZE;
|
2007-09-12 07:43:43 +00:00
|
|
|
if (m->start < 0) {
|
|
|
|
if(sc->dbg_level > 2)
|
|
|
|
device_printf(sc->dev, "emu_memstart returns (-1) in enu_vinit\n");
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_memfree(&sc->mem, vbuf);
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
m->end = m->start + sz;
|
|
|
|
m->speed = 0;
|
|
|
|
m->b16 = 0;
|
|
|
|
m->stereo = 0;
|
|
|
|
m->running = 0;
|
|
|
|
m->ismaster = 1;
|
|
|
|
m->vol = 0xff;
|
|
|
|
m->buf = tmp_addr;
|
|
|
|
m->vbuf = vbuf;
|
|
|
|
m->slave = s;
|
|
|
|
if (s != NULL) {
|
|
|
|
s->start = m->start;
|
|
|
|
s->end = m->end;
|
|
|
|
s->speed = 0;
|
|
|
|
s->b16 = 0;
|
|
|
|
s->stereo = 0;
|
|
|
|
s->running = 0;
|
|
|
|
s->ismaster = 0;
|
|
|
|
s->vol = m->vol;
|
|
|
|
s->buf = m->buf;
|
|
|
|
s->vbuf = NULL;
|
|
|
|
s->slave = NULL;
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_vsetup(struct emu_voice *v, int fmt, int spd)
|
|
|
|
{
|
|
|
|
if (fmt) {
|
|
|
|
v->b16 = (fmt & AFMT_16BIT) ? 1 : 0;
|
Sound Mega-commit. Expect further cleanup until code freeze.
For a slightly thorough explaination, please refer to
[1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html .
Summary of changes includes:
1 Volume Per-Channel (vpc). Provides private / standalone volume control
unique per-stream pcm channel without touching master volume / pcm.
Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for
backwards compatibility, SOUND_MIXER_PCM through the opened dsp device
instead of /dev/mixer. Special "bypass" mode is enabled through
/dev/mixer which will automatically detect if the adjustment is made
through /dev/mixer and forward its request to this private volume
controller. Changes to this volume object will not interfere with
other channels.
Requirements:
- SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which
require specific application modifications (preferred).
- No modifications required for using bypass mode, so applications
like mplayer or xmms should work out of the box.
Kernel hints:
- hint.pcm.%d.vpc (0 = disable vpc).
Kernel sysctls:
- hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer
bypass mode.
- hw.snd.vpc_autoreset (default: 1). By default, closing/opening
/dev/dsp will reset the volume back to 0 db gain/attenuation.
Setting this to 0 will preserve its settings across device
closing/opening.
- hw.snd.vpc_reset (default: 0). Panic/reset button to reset all
volume settings back to 0 db.
- hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value.
2 High quality fixed-point Bandlimited SINC sampling rate converter,
based on Julius O'Smith's Digital Audio Resampling -
http://ccrma.stanford.edu/~jos/resample/. It includes a filter design
script written in awk (the clumsiest joke I've ever written)
- 100% 32bit fixed-point, 64bit accumulator.
- Possibly among the fastest (if not fastest) of its kind.
- Resampling quality is tunable, either runtime or during kernel
compilation (FEEDER_RATE_PRESETS).
- Quality can be further customized during kernel compilation by
defining FEEDER_RATE_PRESETS in /etc/make.conf.
Kernel sysctls:
- hw.snd.feeder_rate_quality.
0 - Zero-order Hold (ZOH). Fastest, bad quality.
1 - Linear Interpolation (LINEAR). Slightly slower than ZOH,
better quality but still does not eliminate aliasing.
2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC
quality always start from 2 and above.
Rough quality comparisons:
- http://people.freebsd.org/~ariff/z_comparison/
3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be
directly fed into the hardware.
4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can
be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf.
5 Transparent/Adaptive Virtual Channel. Now you don't have to disable
vchans in order to make digital format pass through. It also makes
vchans more dynamic by choosing a better format/rate among all the
concurrent streams, which means that dev.pcm.X.play.vchanformat/rate
becomes sort of optional.
6 Exclusive Stream, with special open() mode O_EXCL. This will "mute"
other concurrent vchan streams and only allow a single channel with
O_EXCL set to keep producing sound.
Other Changes:
* most feeder_* stuffs are compilable in userland. Let's not
speculate whether we should go all out for it (save that for
FreeBSD 16.0-RELEASE).
* kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org>
* pull out channel mixing logic out of vchan.c and create its own
feeder_mixer for world justice.
* various refactoring here and there, for good or bad.
* activation of few more OSSv4 ioctls() (see [1] above).
* opt_snd.h for possible compile time configuration:
(mostly for debugging purposes, don't try these at home)
SND_DEBUG
SND_DIAGNOSTIC
SND_FEEDER_MULTIFORMAT
SND_FEEDER_FULL_MULTIFORMAT
SND_FEEDER_RATE_HP
SND_PCM_64
SND_OLDSTEREO
Manual page updates are on the way.
Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many
unsung / unnamed heroes.
2009-06-07 19:12:08 +00:00
|
|
|
v->stereo = (AFMT_CHANNEL(fmt) > 1) ? 1 : 0;
|
2006-07-15 19:36:28 +00:00
|
|
|
if (v->slave != NULL) {
|
|
|
|
v->slave->b16 = v->b16;
|
|
|
|
v->slave->stereo = v->stereo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (spd) {
|
|
|
|
v->speed = spd;
|
|
|
|
if (v->slave != NULL)
|
|
|
|
v->slave->speed = v->speed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < 8; i++) {
|
2007-09-12 07:43:43 +00:00
|
|
|
v->routing[i] = rt->routing_left[i];
|
|
|
|
v->amounts[i] = rt->amounts_left[i];
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
if ((v->stereo) && (v->ismaster == 0))
|
|
|
|
for (i = 0; i < 8; i++) {
|
2007-09-12 07:43:43 +00:00
|
|
|
v->routing[i] = rt->routing_right[i];
|
|
|
|
v->amounts[i] = rt->amounts_right[i];
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((v->stereo) && (v->slave != NULL))
|
|
|
|
emu_vroute(sc, rt, v->slave);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v)
|
|
|
|
{
|
|
|
|
int s;
|
2007-09-12 07:43:43 +00:00
|
|
|
uint32_t start, val, silent_page;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0);
|
|
|
|
|
|
|
|
v->sa = v->start >> s;
|
|
|
|
v->ea = v->end >> s;
|
|
|
|
|
|
|
|
|
|
|
|
if (v->stereo) {
|
|
|
|
emu_wrptr(sc, v->vnum, CPF, CPF_STEREO_MASK);
|
|
|
|
} else {
|
|
|
|
emu_wrptr(sc, v->vnum, CPF, 0);
|
|
|
|
}
|
|
|
|
val = v->stereo ? 28 : 30;
|
|
|
|
val *= v->b16 ? 1 : 2;
|
|
|
|
start = v->sa + val;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->is_emu10k1) {
|
|
|
|
emu_wrptr(sc, v->vnum, FXRT, ((v->routing[3] << 12) |
|
|
|
|
(v->routing[2] << 8) |
|
|
|
|
(v->routing[1] << 4) |
|
|
|
|
(v->routing[0] << 0)) << 16);
|
|
|
|
} else {
|
|
|
|
emu_wrptr(sc, v->vnum, A_FXRT1, (v->routing[3] << 24) |
|
|
|
|
(v->routing[2] << 16) |
|
|
|
|
(v->routing[1] << 8) |
|
|
|
|
(v->routing[0] << 0));
|
|
|
|
emu_wrptr(sc, v->vnum, A_FXRT2, (v->routing[7] << 24) |
|
|
|
|
(v->routing[6] << 16) |
|
|
|
|
(v->routing[5] << 8) |
|
|
|
|
(v->routing[4] << 0));
|
|
|
|
emu_wrptr(sc, v->vnum, A_SENDAMOUNTS, (v->amounts[7] << 24) |
|
|
|
|
(v->amounts[6] << 26) |
|
|
|
|
(v->amounts[5] << 8) |
|
|
|
|
(v->amounts[4] << 0));
|
|
|
|
}
|
|
|
|
emu_wrptr(sc, v->vnum, PTRX, (v->amounts[0] << 8) | (v->amounts[1] << 0));
|
|
|
|
emu_wrptr(sc, v->vnum, DSL, v->ea | (v->amounts[3] << 24));
|
|
|
|
emu_wrptr(sc, v->vnum, PSST, v->sa | (v->amounts[2] << 24));
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
emu_wrptr(sc, v->vnum, CCCA, start | (v->b16 ? 0 : CCCA_8BITSELECT));
|
|
|
|
emu_wrptr(sc, v->vnum, Z1, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, Z2, 0);
|
|
|
|
|
|
|
|
silent_page = ((uint32_t) (sc->mem.silent_page_addr) << 1) | MAP_PTI_MASK;
|
|
|
|
emu_wrptr(sc, v->vnum, MAPA, silent_page);
|
|
|
|
emu_wrptr(sc, v->vnum, MAPB, silent_page);
|
|
|
|
|
|
|
|
emu_wrptr(sc, v->vnum, CVCF, CVCF_CURRENTFILTER_MASK);
|
|
|
|
emu_wrptr(sc, v->vnum, VTFT, VTFT_FILTERTARGET_MASK);
|
|
|
|
emu_wrptr(sc, v->vnum, ATKHLDM, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, DCYSUSM, DCYSUSM_DECAYTIME_MASK);
|
|
|
|
emu_wrptr(sc, v->vnum, LFOVAL1, 0x8000);
|
|
|
|
emu_wrptr(sc, v->vnum, LFOVAL2, 0x8000);
|
|
|
|
emu_wrptr(sc, v->vnum, FMMOD, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, TREMFRQ, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, FM2FRQ2, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, ENVVAL, 0x8000);
|
|
|
|
|
|
|
|
emu_wrptr(sc, v->vnum, ATKHLDV, ATKHLDV_HOLDTIME_MASK | ATKHLDV_ATTACKTIME_MASK);
|
|
|
|
emu_wrptr(sc, v->vnum, ENVVOL, 0x8000);
|
|
|
|
|
|
|
|
emu_wrptr(sc, v->vnum, PEFE_FILTERAMOUNT, 0x7f);
|
|
|
|
emu_wrptr(sc, v->vnum, PEFE_PITCHAMOUNT, 0);
|
|
|
|
if ((v->stereo) && (v->slave != NULL))
|
|
|
|
emu_vwrite(sc, v->slave);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_vstop(struct emu_sc_info *sc, char channel, int enable)
|
|
|
|
{
|
|
|
|
int reg;
|
|
|
|
|
|
|
|
reg = (channel & 0x20) ? SOLEH : SOLEL;
|
|
|
|
channel &= 0x1f;
|
|
|
|
reg |= 1 << 24;
|
|
|
|
reg |= channel << 16;
|
|
|
|
emu_wrptr(sc, 0, reg, enable);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go)
|
|
|
|
{
|
|
|
|
uint32_t pitch_target, initial_pitch;
|
|
|
|
uint32_t cra, cs, ccis;
|
|
|
|
uint32_t sample, i;
|
|
|
|
|
|
|
|
if (go) {
|
|
|
|
cra = 64;
|
|
|
|
cs = v->stereo ? 4 : 2;
|
|
|
|
ccis = v->stereo ? 28 : 30;
|
|
|
|
ccis *= v->b16 ? 1 : 2;
|
|
|
|
sample = v->b16 ? 0x00000000 : 0x80808080;
|
|
|
|
for (i = 0; i < cs; i++)
|
|
|
|
emu_wrptr(sc, v->vnum, CD0 + i, sample);
|
|
|
|
emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, CCR_READADDRESS, cra);
|
|
|
|
emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, ccis);
|
|
|
|
|
|
|
|
emu_wrptr(sc, v->vnum, IFATN, 0xff00);
|
|
|
|
emu_wrptr(sc, v->vnum, VTFT, 0xffffffff);
|
|
|
|
emu_wrptr(sc, v->vnum, CVCF, 0xffffffff);
|
|
|
|
emu_wrptr(sc, v->vnum, DCYSUSV, 0x00007f7f);
|
|
|
|
emu_vstop(sc, v->vnum, 0);
|
|
|
|
|
|
|
|
pitch_target = emu_rate_to_linearpitch(v->speed);
|
|
|
|
initial_pitch = emu_rate_to_pitch(v->speed) >> 8;
|
|
|
|
emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, pitch_target);
|
|
|
|
emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, pitch_target);
|
|
|
|
emu_wrptr(sc, v->vnum, IP, initial_pitch);
|
|
|
|
} else {
|
|
|
|
emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, 0);
|
|
|
|
emu_wrptr(sc, v->vnum, IFATN, 0xffff);
|
|
|
|
emu_wrptr(sc, v->vnum, VTFT, 0x0000ffff);
|
|
|
|
emu_wrptr(sc, v->vnum, CVCF, 0x0000ffff);
|
|
|
|
emu_wrptr(sc, v->vnum, IP, 0);
|
|
|
|
emu_vstop(sc, v->vnum, 1);
|
|
|
|
}
|
|
|
|
if ((v->stereo) && (v->slave != NULL))
|
|
|
|
emu_vtrigger(sc, v->slave, go);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_vpos(struct emu_sc_info *sc, struct emu_voice *v)
|
|
|
|
{
|
|
|
|
int s, ptr;
|
|
|
|
|
|
|
|
s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0);
|
|
|
|
ptr = (emu_rdptr(sc, v->vnum, CCCA_CURRADDR) - (v->start >> s)) << s;
|
|
|
|
return (ptr & ~0x0000001f);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* fx */
|
|
|
|
static void
|
|
|
|
emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data)
|
|
|
|
{
|
|
|
|
emu_wrptr(sc, 0, sc->code_base + pc, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc)
|
|
|
|
{
|
|
|
|
if ((*pc) + 1 > sc->code_size) {
|
|
|
|
device_printf(sc->dev, "DSP CODE OVERRUN: attept to write past code_size (pc=%d)\n", (*pc));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
emu_wrefx(sc, (*pc) * 2, (x << sc->high_operand_shift) | y);
|
|
|
|
emu_wrefx(sc, (*pc) * 2 + 1, (op << sc->opcode_shift) | (z << sc->high_operand_shift) | w);
|
|
|
|
(*pc)++;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS)
|
|
|
|
{
|
|
|
|
struct emu_sc_info *sc;
|
|
|
|
int mixer_id;
|
|
|
|
int new_vol;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
sc = arg1;
|
|
|
|
mixer_id = arg2;
|
|
|
|
|
|
|
|
new_vol = emumix_get_volume(sc, mixer_id);
|
2007-06-04 18:25:08 +00:00
|
|
|
err = sysctl_handle_int(oidp, &new_vol, 0, req);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
if (err || req->newptr == NULL)
|
|
|
|
return (err);
|
|
|
|
if (new_vol < 0 || new_vol > 100)
|
|
|
|
return (EINVAL);
|
|
|
|
emumix_set_volume(sc, mixer_id, new_vol);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_addefxmixer(struct emu_sc_info *sc, const char *mix_name, const int mix_id, uint32_t defvolume)
|
|
|
|
{
|
|
|
|
int volgpr;
|
2006-07-17 17:28:44 +00:00
|
|
|
char sysctl_name[32];
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
volgpr = emu_rm_gpr_alloc(sc->rm, 1);
|
|
|
|
emumix_set_fxvol(sc, volgpr, defvolume);
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* Mixer controls with NULL mix_name are handled
|
|
|
|
* by AC97 emulation code or PCM mixer.
|
|
|
|
*/
|
2006-07-15 19:36:28 +00:00
|
|
|
if (mix_name != NULL) {
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* Temporary sysctls should start with underscore,
|
2006-07-17 17:28:44 +00:00
|
|
|
* see freebsd-current mailing list, emu10kx driver
|
2007-09-12 07:43:43 +00:00
|
|
|
* discussion around 2006-05-24.
|
|
|
|
*/
|
2006-07-17 17:28:44 +00:00
|
|
|
snprintf(sysctl_name, 32, "_%s", mix_name);
|
2006-07-15 19:36:28 +00:00
|
|
|
SYSCTL_ADD_PROC(sc->ctx,
|
|
|
|
SYSCTL_CHILDREN(sc->root),
|
2006-07-17 17:28:44 +00:00
|
|
|
OID_AUTO, sysctl_name,
|
2006-07-15 19:36:28 +00:00
|
|
|
CTLTYPE_INT | CTLFLAG_RW, sc, mix_id,
|
2007-09-12 07:43:43 +00:00
|
|
|
sysctl_emu_mixer_control, "I", "");
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (volgpr);
|
|
|
|
}
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
static int
|
|
|
|
sysctl_emu_digitalswitch_control(SYSCTL_HANDLER_ARGS)
|
|
|
|
{
|
|
|
|
struct emu_sc_info *sc;
|
|
|
|
int new_val;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
sc = arg1;
|
|
|
|
|
|
|
|
new_val = (sc->mode == MODE_DIGITAL) ? 1 : 0;
|
|
|
|
err = sysctl_handle_int(oidp, &new_val, 0, req);
|
|
|
|
|
|
|
|
if (err || req->newptr == NULL)
|
|
|
|
return (err);
|
|
|
|
if (new_val < 0 || new_val > 1)
|
|
|
|
return (EINVAL);
|
|
|
|
|
|
|
|
switch (new_val) {
|
|
|
|
case 0:
|
|
|
|
emumix_set_mode(sc, MODE_ANALOG);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
emumix_set_mode(sc, MODE_DIGITAL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
emu_digitalswitch(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
/* XXX temporary? */
|
|
|
|
SYSCTL_ADD_PROC(sc->ctx,
|
|
|
|
SYSCTL_CHILDREN(sc->root),
|
|
|
|
OID_AUTO, "_digital",
|
|
|
|
CTLTYPE_INT | CTLFLAG_RW, sc, 0,
|
|
|
|
sysctl_emu_digitalswitch_control, "I", "Enable digital output");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate cache GPRs that will hold mixed output channels
|
2006-07-15 19:36:28 +00:00
|
|
|
* and clear it on every DSP run.
|
|
|
|
*/
|
|
|
|
#define EFX_CACHE(CACHE_IDX) do { \
|
|
|
|
sc->cache_gpr[CACHE_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \
|
|
|
|
emu_addefxop(sc, ACC3, \
|
|
|
|
GPR(sc->cache_gpr[CACHE_IDX]), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
&pc); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* Allocate GPR for volume control and route sound: OUT = OUT + IN * VOL */
|
|
|
|
#define EFX_ROUTE(TITLE, INP_NR, IN_GPR_IDX, OUT_CACHE_IDX, DEF) do { \
|
|
|
|
sc->mixer_gpr[IN_GPR_IDX] = emu_addefxmixer(sc, TITLE, IN_GPR_IDX, DEF); \
|
|
|
|
sc->mixer_volcache[IN_GPR_IDX] = DEF; \
|
|
|
|
emu_addefxop(sc, MACS, \
|
|
|
|
GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
|
|
|
|
GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
|
|
|
|
INP_NR, \
|
|
|
|
GPR(sc->mixer_gpr[IN_GPR_IDX]), \
|
|
|
|
&pc); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* allocate GPR, OUT = IN * VOL */
|
2007-09-12 07:43:43 +00:00
|
|
|
#define EFX_OUTPUT(TITLE, OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR, DEF) do { \
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->mixer_gpr[OUT_GPR_IDX] = emu_addefxmixer(sc, TITLE, OUT_GPR_IDX, DEF); \
|
|
|
|
sc->mixer_volcache[OUT_GPR_IDX] = DEF; \
|
|
|
|
emu_addefxop(sc, MACS, \
|
|
|
|
OUTP(OUTP_NR), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
|
|
|
|
GPR(sc->mixer_gpr[OUT_GPR_IDX]), \
|
|
|
|
&pc); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* like EFX_OUTPUT, but don't allocate mixer gpr */
|
2007-09-12 07:43:43 +00:00
|
|
|
#define EFX_OUTPUTD(OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR) do { \
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_addefxop(sc, MACS, \
|
|
|
|
OUTP(OUTP_NR), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
GPR(sc->cache_gpr[OUT_CACHE_IDX]), \
|
|
|
|
GPR(sc->mixer_gpr[OUT_GPR_IDX]), \
|
|
|
|
&pc); \
|
2007-09-12 07:43:43 +00:00
|
|
|
} while (0)
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* skip next OPCOUNT instructions if FLAG != 0 */
|
|
|
|
#define EFX_SKIP(OPCOUNT, FLAG_GPR) do { \
|
|
|
|
emu_addefxop(sc, MACS, \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
GPR(sc->mute_gpr[FLAG_GPR]), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
DSP_CONST(0), \
|
|
|
|
&pc); \
|
|
|
|
emu_addefxop(sc, SKIP, \
|
|
|
|
DSP_CCR, \
|
|
|
|
DSP_CCR, \
|
|
|
|
COND_NEQ_ZERO, \
|
|
|
|
OPCOUNT, \
|
|
|
|
&pc); \
|
|
|
|
} while (0)
|
2007-01-06 18:59:35 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
#define EFX_COPY(TO, FROM) do { \
|
|
|
|
emu_addefxop(sc, ACC3, \
|
|
|
|
TO, \
|
2007-01-06 18:59:35 +00:00
|
|
|
DSP_CONST(0), \
|
|
|
|
DSP_CONST(0), \
|
2007-09-12 07:43:43 +00:00
|
|
|
FROM, \
|
2007-01-06 18:59:35 +00:00
|
|
|
&pc); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
static void
|
|
|
|
emu_initefx(struct emu_sc_info *sc)
|
|
|
|
{
|
2007-01-06 18:59:35 +00:00
|
|
|
unsigned int i;
|
2006-07-15 19:36:28 +00:00
|
|
|
uint32_t pc;
|
|
|
|
|
|
|
|
/* stop DSP */
|
|
|
|
if (sc->is_emu10k1) {
|
|
|
|
emu_wrptr(sc, 0, DBG, EMU10K1_DBG_SINGLE_STEP);
|
|
|
|
} else {
|
|
|
|
emu_wrptr(sc, 0, A_DBG, A_DBG_SINGLE_STEP);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* code size is in instructions */
|
|
|
|
pc = 0;
|
2007-01-06 18:59:35 +00:00
|
|
|
for (i = 0; i < sc->code_size; i++) {
|
2006-07-15 19:36:28 +00:00
|
|
|
if (sc->is_emu10k1) {
|
|
|
|
emu_addefxop(sc, ACC3, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), &pc);
|
|
|
|
} else {
|
|
|
|
emu_addefxop(sc, SKIP, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0xf), DSP_CONST(0x0), &pc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* allocate GPRs for mute switches (EFX_SKIP). Mute by default */
|
|
|
|
for (i = 0; i < NUM_MUTE; i++) {
|
|
|
|
sc->mute_gpr[i] = emu_rm_gpr_alloc(sc->rm, 1);
|
|
|
|
emumix_set_gpr(sc, sc->mute_gpr[i], 1);
|
|
|
|
}
|
|
|
|
emu_digitalswitch(sc);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
pc = 0;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
2006-07-15 19:36:28 +00:00
|
|
|
* DSP code below is not good, because:
|
|
|
|
* 1. It can be written smaller, if it can use DSP accumulator register
|
|
|
|
* instead of cache_gpr[].
|
|
|
|
* 2. It can be more careful when volume is 100%, because in DSP
|
|
|
|
* x*0x7fffffff may not be equal to x !
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* clean outputs */
|
2007-01-06 18:59:35 +00:00
|
|
|
for (i = 0; i < 16 ; i++) {
|
|
|
|
emu_addefxop(sc, ACC3, OUTP(i), DSP_CONST(0), DSP_CONST(0), DSP_CONST(0), &pc);
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (sc->is_emu10k1) {
|
|
|
|
EFX_CACHE(C_FRONT_L);
|
|
|
|
EFX_CACHE(C_FRONT_R);
|
|
|
|
EFX_CACHE(C_REC_L);
|
|
|
|
EFX_CACHE(C_REC_R);
|
|
|
|
|
|
|
|
/* fx0 to front/record, 100%/muted by default */
|
|
|
|
EFX_ROUTE("pcm_front_l", FX(0), M_FX0_FRONT_L, C_FRONT_L, 100);
|
|
|
|
EFX_ROUTE("pcm_front_r", FX(1), M_FX1_FRONT_R, C_FRONT_R, 100);
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, FX(0), M_FX0_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, FX(1), M_FX1_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in0, from AC97 codec output */
|
|
|
|
EFX_ROUTE("ac97_front_l", INP(IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE("ac97_front_r", INP(IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE("ac97_rec_l", INP(IN_AC97_L), M_IN0_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE("ac97_rec_r", INP(IN_AC97_R), M_IN0_REC_R, C_REC_R, 0);
|
|
|
|
|
|
|
|
/* in1, from CD S/PDIF */
|
2007-09-12 07:43:43 +00:00
|
|
|
/* XXX EFX_SKIP 4 assumes that each EFX_ROUTE is one DSP op */
|
|
|
|
EFX_SKIP(4, CDSPDIFMUTE);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0);
|
|
|
|
|
|
|
|
if (sc->dbg_level > 0) {
|
|
|
|
/* in2, ZoomVide (???) */
|
|
|
|
EFX_ROUTE("zoom_front_l", INP(IN_ZOOM_L), M_IN2_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE("zoom_front_r", INP(IN_ZOOM_R), M_IN2_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE("zoom_rec_l", INP(IN_ZOOM_L), M_IN2_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE("zoom_rec_r", INP(IN_ZOOM_R), M_IN2_REC_R, C_REC_R, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* in3, TOSLink */
|
|
|
|
EFX_ROUTE(NULL, INP(IN_TOSLINK_L), M_IN3_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_TOSLINK_R), M_IN3_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_TOSLINK_L), M_IN3_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_TOSLINK_R), M_IN3_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
/* in4, LineIn */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE1_L), M_IN4_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE1_R), M_IN4_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE1_L), M_IN4_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE1_R), M_IN4_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in5, on-card S/PDIF */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_L), M_IN5_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_COAX_SPDIF_R), M_IN5_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in6, Line2 on Live!Drive */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE2_L), M_IN6_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE2_R), M_IN6_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE2_L), M_IN6_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(IN_LINE2_R), M_IN6_REC_R, C_REC_R, 0);
|
|
|
|
|
|
|
|
if (sc->dbg_level > 0) {
|
|
|
|
/* in7, unknown */
|
|
|
|
EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* analog and digital */
|
2006-07-15 19:36:28 +00:00
|
|
|
EFX_OUTPUT("master_front_l", C_FRONT_L, M_MASTER_FRONT_L, OUT_AC97_L, 100);
|
|
|
|
EFX_OUTPUT("master_front_r", C_FRONT_R, M_MASTER_FRONT_R, OUT_AC97_R, 100);
|
2007-09-12 07:43:43 +00:00
|
|
|
/* S/PDIF */
|
|
|
|
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_TOSLINK_L);
|
|
|
|
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_TOSLINK_R);
|
|
|
|
/* Headphones */
|
2007-01-06 18:59:35 +00:00
|
|
|
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_HEADPHONE_L);
|
|
|
|
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_HEADPHONE_R);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* rec output to "ADC" */
|
|
|
|
EFX_OUTPUT("master_rec_l", C_REC_L, M_MASTER_REC_L, OUT_ADC_REC_L, 100);
|
|
|
|
EFX_OUTPUT("master_rec_r", C_REC_R, M_MASTER_REC_R, OUT_ADC_REC_R, 100);
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (!(sc->mch_disabled)) {
|
|
|
|
/*
|
|
|
|
* Additional channel volume is controlled by mixer in
|
|
|
|
* emu_dspmixer_set() in -pcm.c
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* fx2/3 (pcm1) to rear */
|
|
|
|
EFX_CACHE(C_REAR_L);
|
|
|
|
EFX_CACHE(C_REAR_R);
|
|
|
|
EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100);
|
|
|
|
EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100);
|
|
|
|
|
|
|
|
EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, OUT_REAR_L, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, OUT_REAR_R, 100);
|
|
|
|
if (sc->has_51) {
|
|
|
|
/* fx4 (pcm2) to center */
|
|
|
|
EFX_CACHE(C_CENTER);
|
|
|
|
EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, OUT_D_CENTER, 100);
|
|
|
|
|
|
|
|
/* XXX in digital mode (default) this should be muted because
|
|
|
|
this output is shared with digital out */
|
|
|
|
EFX_SKIP(1, ANALOGMUTE);
|
|
|
|
EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, OUT_A_CENTER);
|
|
|
|
|
|
|
|
/* fx5 (pcm3) to sub */
|
|
|
|
EFX_CACHE(C_SUB);
|
|
|
|
EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, OUT_D_SUB, 100);
|
|
|
|
|
|
|
|
/* XXX in digital mode (default) this should be muted because
|
|
|
|
this output is shared with digital out */
|
|
|
|
EFX_SKIP(1, ANALOGMUTE);
|
|
|
|
EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, OUT_A_SUB);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* SND_EMU10KX_MULTICHANNEL_DISABLED */
|
|
|
|
EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_REAR_L, OUT_REAR_L, 57); /* 75%*75% */
|
|
|
|
EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_REAR_R, OUT_REAR_R, 57); /* 75%*75% */
|
2007-01-06 18:59:35 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
#if 0
|
|
|
|
/* XXX 5.1 does not work */
|
|
|
|
|
|
|
|
if (sc->has_51) {
|
|
|
|
/* (fx0+fx1)/2 to center */
|
|
|
|
EFX_CACHE(C_CENTER);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_L]),
|
|
|
|
&pc);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_R]),
|
|
|
|
&pc);
|
|
|
|
EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, OUT_D_CENTER, 100);
|
|
|
|
|
|
|
|
/* XXX in digital mode (default) this should be muted because
|
|
|
|
this output is shared with digital out */
|
|
|
|
EFX_SKIP(1, ANALOGMUTE);
|
|
|
|
EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, OUT_A_CENTER);
|
|
|
|
|
|
|
|
/* (fx0+fx1)/2 to sub */
|
|
|
|
EFX_CACHE(C_SUB);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_L]),
|
|
|
|
&pc);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_R]),
|
|
|
|
&pc);
|
|
|
|
/* XXX add lowpass filter here */
|
|
|
|
|
|
|
|
EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, OUT_D_SUB, 100);
|
|
|
|
|
|
|
|
/* XXX in digital mode (default) this should be muted because
|
|
|
|
this output is shared with digital out */
|
|
|
|
EFX_SKIP(1, ANALOGMUTE);
|
|
|
|
EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, OUT_A_SUB);
|
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
#endif
|
2007-09-12 07:43:43 +00:00
|
|
|
} /* !mch_disabled */
|
|
|
|
if (sc->mch_rec) {
|
|
|
|
/*
|
|
|
|
* MCH RECORDING , hight 16 slots. On 5.1 cards first 4 slots
|
|
|
|
* are used as outputs and already filled with data
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* XXX On Live! cards stream does not begin at zero offset.
|
|
|
|
* It can be HW, driver or sound buffering problem.
|
|
|
|
* Use sync substream (offset 0x3E) to let userland find
|
|
|
|
* correct data.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Substream map (in byte offsets, each substream is 2 bytes):
|
|
|
|
* 0x00..0x1E - outputs
|
|
|
|
* 0x20..0x3E - FX, inputs ans sync stream
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* First 2 channels (offset 0x20,0x22) are empty */
|
|
|
|
for(i = (sc->has_51 ? 2 : 0); i < 2; i++)
|
|
|
|
EFX_COPY(FX2(i), DSP_CONST(0));
|
|
|
|
|
|
|
|
/* PCM Playback monitoring, offset 0x24..0x2A */
|
|
|
|
for(i = 0; i < 4; i++)
|
|
|
|
EFX_COPY(FX2(i+2), FX(i));
|
|
|
|
|
|
|
|
/* Copy of some inputs, offset 0x2C..0x3C */
|
|
|
|
for(i = 0; i < 9; i++)
|
|
|
|
EFX_COPY(FX2(i+8), INP(i));
|
|
|
|
|
|
|
|
/* sync data (0xc0de, offset 0x3E) */
|
|
|
|
sc->dummy_gpr = emu_rm_gpr_alloc(sc->rm, 1);
|
|
|
|
emumix_set_gpr(sc, sc->dummy_gpr, 0xc0de0000);
|
|
|
|
|
|
|
|
EFX_COPY(FX2(15), GPR(sc->dummy_gpr));
|
|
|
|
} /* mch_rec */
|
2006-07-15 19:36:28 +00:00
|
|
|
} else /* emu10k2 and later */ {
|
|
|
|
EFX_CACHE(C_FRONT_L);
|
|
|
|
EFX_CACHE(C_FRONT_R);
|
|
|
|
EFX_CACHE(C_REC_L);
|
|
|
|
EFX_CACHE(C_REC_R);
|
|
|
|
|
|
|
|
/* fx0 to front/record, 100%/muted by default */
|
|
|
|
/*
|
|
|
|
* FRONT_[L|R] is controlled by AC97 emulation in
|
|
|
|
* emu_ac97_[read|write]_emulation in -pcm.c
|
|
|
|
*/
|
|
|
|
EFX_ROUTE(NULL, FX(0), M_FX0_FRONT_L, C_FRONT_L, 100);
|
|
|
|
EFX_ROUTE(NULL, FX(1), M_FX1_FRONT_R, C_FRONT_R, 100);
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, FX(0), M_FX0_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, FX(1), M_FX1_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in0, from AC97 codec output */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 100);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 100);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AC97_L), M_IN0_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AC97_R), M_IN0_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in1, from CD S/PDIF */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in2, optical & coax S/PDIF on AudigyDrive*/
|
2007-01-06 18:59:35 +00:00
|
|
|
/* XXX Should be muted when GPRSCS valid stream == 0 */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_L), M_IN2_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_R), M_IN2_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_L), M_IN2_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_O_SPDIF_R), M_IN2_REC_R, C_REC_R, 0);
|
|
|
|
|
|
|
|
if (sc->dbg_level > 0) {
|
|
|
|
/* in3, unknown */
|
|
|
|
EFX_ROUTE("in3_front_l", INP(0x6), M_IN3_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE("in3_front_r", INP(0x7), M_IN3_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE("in3_rec_l", INP(0x6), M_IN3_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE("in3_rec_r", INP(0x7), M_IN3_REC_R, C_REC_R, 0);
|
|
|
|
}
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
/* in4, LineIn 2 on AudigyDrive */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(A_IN_LINE2_L), M_IN4_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_LINE2_R), M_IN4_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_LINE2_L), M_IN4_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_LINE2_R), M_IN4_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in5, on-card S/PDIF */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_L), M_IN5_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_R_SPDIF_R), M_IN5_REC_R, C_REC_R, 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* in6, AUX2 on AudigyDrive */
|
2007-09-12 07:43:43 +00:00
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AUX2_L), M_IN6_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AUX2_R), M_IN6_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AUX2_L), M_IN6_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE(NULL, INP(A_IN_AUX2_R), M_IN6_REC_R, C_REC_R, 0);
|
|
|
|
|
|
|
|
if (sc->dbg_level > 0) {
|
|
|
|
/* in7, unknown */
|
|
|
|
EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0);
|
|
|
|
EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0);
|
|
|
|
EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0);
|
|
|
|
EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0);
|
|
|
|
}
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
/* front output to headphones and alog and digital *front */
|
|
|
|
/* volume controlled by AC97 emulation */
|
|
|
|
EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_FRONT_L, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_FRONT_R, 100);
|
|
|
|
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_FRONT_L);
|
|
|
|
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_FRONT_R);
|
|
|
|
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_HPHONE_L);
|
|
|
|
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_HPHONE_R);
|
|
|
|
|
|
|
|
/* rec output to "ADC" */
|
|
|
|
/* volume controlled by AC97 emulation */
|
|
|
|
EFX_OUTPUT(NULL, C_REC_L, M_MASTER_REC_L, A_OUT_ADC_REC_L, 100);
|
2006-08-07 22:56:41 +00:00
|
|
|
EFX_OUTPUT(NULL, C_REC_R, M_MASTER_REC_R, A_OUT_ADC_REC_R, 100);
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (!(sc->mch_disabled)) {
|
|
|
|
/*
|
|
|
|
* Additional channel volume is controlled by mixer in
|
|
|
|
* emu_dspmixer_set() in -pcm.c
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* fx2/3 (pcm1) to rear */
|
|
|
|
EFX_CACHE(C_REAR_L);
|
|
|
|
EFX_CACHE(C_REAR_R);
|
|
|
|
EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100);
|
|
|
|
EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100);
|
|
|
|
|
|
|
|
EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, A_OUT_A_REAR_L, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, A_OUT_A_REAR_R, 100);
|
|
|
|
EFX_OUTPUTD(C_REAR_L, M_MASTER_REAR_L, A_OUT_D_REAR_L);
|
|
|
|
EFX_OUTPUTD(C_REAR_R, M_MASTER_REAR_R, A_OUT_D_REAR_R);
|
|
|
|
|
|
|
|
/* fx4 (pcm2) to center */
|
|
|
|
EFX_CACHE(C_CENTER);
|
|
|
|
EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100);
|
2006-07-15 19:36:28 +00:00
|
|
|
#if 0
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* XXX in digital mode (default) this should be muted
|
|
|
|
* because this output is shared with digital out
|
|
|
|
*/
|
|
|
|
EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER);
|
2006-07-15 19:36:28 +00:00
|
|
|
#endif
|
2007-09-12 07:43:43 +00:00
|
|
|
/* fx5 (pcm3) to sub */
|
|
|
|
EFX_CACHE(C_SUB);
|
|
|
|
EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100);
|
2006-07-15 19:36:28 +00:00
|
|
|
#if 0
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* XXX in digital mode (default) this should be muted
|
|
|
|
* because this output is shared with digital out
|
|
|
|
*/
|
|
|
|
EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB);
|
2007-01-06 18:59:35 +00:00
|
|
|
#endif
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->has_71) {
|
|
|
|
/* XXX this will broke headphones on AudigyDrive */
|
|
|
|
/* fx6/7 (pcm4) to side */
|
|
|
|
EFX_CACHE(C_SIDE_L);
|
|
|
|
EFX_CACHE(C_SIDE_R);
|
|
|
|
EFX_ROUTE(NULL, FX(6), M_FX6_SIDE_L, C_SIDE_L, 100);
|
|
|
|
EFX_ROUTE(NULL, FX(7), M_FX7_SIDE_R, C_SIDE_R, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_SIDE_L, M_MASTER_SIDE_L, A_OUT_A_SIDE_L, 100);
|
|
|
|
EFX_OUTPUT(NULL, C_SIDE_R, M_MASTER_SIDE_R, A_OUT_A_SIDE_R, 100);
|
|
|
|
EFX_OUTPUTD(C_SIDE_L, M_MASTER_SIDE_L, A_OUT_D_SIDE_L);
|
|
|
|
EFX_OUTPUTD(C_SIDE_R, M_MASTER_SIDE_R, A_OUT_D_SIDE_R);
|
|
|
|
}
|
|
|
|
} else { /* mch_disabled */
|
|
|
|
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_REAR_L);
|
|
|
|
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_REAR_R);
|
|
|
|
|
|
|
|
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_REAR_L);
|
|
|
|
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_REAR_R);
|
|
|
|
|
|
|
|
if (sc->has_51) {
|
|
|
|
/* (fx0+fx1)/2 to center */
|
|
|
|
EFX_CACHE(C_CENTER);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_L]),
|
|
|
|
&pc);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
GPR(sc->cache_gpr[C_CENTER]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_R]),
|
|
|
|
&pc);
|
|
|
|
EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100);
|
|
|
|
|
|
|
|
/* XXX in digital mode (default) this should be muted because
|
|
|
|
this output is shared with digital out */
|
|
|
|
EFX_SKIP(1, ANALOGMUTE);
|
|
|
|
EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER);
|
|
|
|
|
|
|
|
/* (fx0+fx1)/2 to sub */
|
|
|
|
EFX_CACHE(C_SUB);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_SUB]),
|
|
|
|
GPR(sc->cache_gpr[C_SUB]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_L]),
|
|
|
|
&pc);
|
|
|
|
emu_addefxop(sc, MACS,
|
|
|
|
GPR(sc->cache_gpr[C_SUB]),
|
|
|
|
GPR(sc->cache_gpr[C_SUB]),
|
|
|
|
DSP_CONST(0xd), /* = 1/2 */
|
|
|
|
GPR(sc->cache_gpr[C_FRONT_R]),
|
|
|
|
&pc);
|
|
|
|
/* XXX add lowpass filter here */
|
|
|
|
|
|
|
|
EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100);
|
|
|
|
|
|
|
|
/* XXX in digital mode (default) this should be muted because
|
|
|
|
this output is shared with digital out */
|
|
|
|
EFX_SKIP(1, ANALOGMUTE);
|
|
|
|
EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB);
|
|
|
|
}
|
|
|
|
} /* mch_disabled */
|
|
|
|
if (sc->mch_rec) {
|
|
|
|
/* MCH RECORDING, high 32 slots */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stream map (in byte offsets):
|
|
|
|
* 0x00..0x3E - outputs
|
|
|
|
* 0x40..0x7E - FX, inputs
|
|
|
|
* each substream is 2 bytes.
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* XXX Audigy 2 Value cards (and, possibly,
|
|
|
|
* Audigy 4) write some unknown data in place of
|
|
|
|
* some outputs (offsets 0x20..0x3F) and one
|
|
|
|
* input (offset 0x7E).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* PCM Playback monitoring, offsets 0x40..0x5E */
|
|
|
|
for(i = 0; i < 16; i++)
|
|
|
|
EFX_COPY(FX2(i), FX(i));
|
|
|
|
|
|
|
|
/* Copy of all inputs, offsets 0x60..0x7E */
|
|
|
|
for(i = 0; i < 16; i++)
|
|
|
|
EFX_COPY(FX2(i+16), INP(i));
|
|
|
|
#if 0
|
|
|
|
/* XXX Audigy seems to work correct and does not need this */
|
|
|
|
/* sync data (0xc0de), offset 0x7E */
|
|
|
|
sc->dummy_gpr = emu_rm_gpr_alloc(sc->rm, 1);
|
|
|
|
emumix_set_gpr(sc, sc->dummy_gpr, 0xc0de0000);
|
|
|
|
EFX_COPY(FX2(31), GPR(sc->dummy_gpr));
|
2006-07-15 19:36:28 +00:00
|
|
|
#endif
|
2007-09-12 07:43:43 +00:00
|
|
|
} /* mch_rec */
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sc->routing_code_end = pc;
|
|
|
|
|
|
|
|
/* start DSP */
|
|
|
|
if (sc->is_emu10k1) {
|
|
|
|
emu_wrptr(sc, 0, DBG, 0);
|
|
|
|
} else {
|
|
|
|
emu_wrptr(sc, 0, A_DBG, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* /dev/em10kx */
|
|
|
|
static d_open_t emu10kx_open;
|
|
|
|
static d_close_t emu10kx_close;
|
|
|
|
static d_read_t emu10kx_read;
|
|
|
|
|
|
|
|
static struct cdevsw emu10kx_cdevsw = {
|
|
|
|
.d_open = emu10kx_open,
|
|
|
|
.d_close = emu10kx_close,
|
|
|
|
.d_read = emu10kx_read,
|
|
|
|
.d_name = "emu10kx",
|
|
|
|
.d_version = D_VERSION,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu10kx_open(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
struct emu_sc_info *sc;
|
|
|
|
|
|
|
|
sc = i_dev->si_drv1;
|
|
|
|
mtx_lock(&sc->emu10kx_lock);
|
|
|
|
if (sc->emu10kx_isopen) {
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
return (EBUSY);
|
|
|
|
}
|
|
|
|
sc->emu10kx_isopen = 1;
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
if (sbuf_new(&sc->emu10kx_sbuf, NULL, 4096, 0) == NULL) {
|
|
|
|
error = ENXIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
sc->emu10kx_bufptr = 0;
|
|
|
|
error = (emu10kx_prepare(sc, &sc->emu10kx_sbuf) > 0) ? 0 : ENOMEM;
|
|
|
|
out:
|
|
|
|
if (error) {
|
|
|
|
mtx_lock(&sc->emu10kx_lock);
|
|
|
|
sc->emu10kx_isopen = 0;
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
}
|
|
|
|
return (error);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu10kx_close(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused)
|
|
|
|
{
|
|
|
|
struct emu_sc_info *sc;
|
|
|
|
|
|
|
|
sc = i_dev->si_drv1;
|
|
|
|
|
|
|
|
mtx_lock(&sc->emu10kx_lock);
|
|
|
|
if (!(sc->emu10kx_isopen)) {
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
return (EBADF);
|
|
|
|
}
|
|
|
|
sbuf_delete(&sc->emu10kx_sbuf);
|
|
|
|
sc->emu10kx_isopen = 0;
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu10kx_read(struct cdev *i_dev, struct uio *buf, int flag __unused)
|
|
|
|
{
|
|
|
|
int l, err;
|
|
|
|
struct emu_sc_info *sc;
|
|
|
|
|
|
|
|
sc = i_dev->si_drv1;
|
|
|
|
mtx_lock(&sc->emu10kx_lock);
|
|
|
|
if (!(sc->emu10kx_isopen)) {
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
return (EBADF);
|
|
|
|
}
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
|
|
|
|
l = min(buf->uio_resid, sbuf_len(&sc->emu10kx_sbuf) - sc->emu10kx_bufptr);
|
|
|
|
err = (l > 0) ? uiomove(sbuf_data(&sc->emu10kx_sbuf) + sc->emu10kx_bufptr, l, buf) : 0;
|
|
|
|
sc->emu10kx_bufptr += l;
|
|
|
|
|
|
|
|
return (err);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
sbuf_printf(s, "FreeBSD EMU10Kx Audio Driver\n");
|
|
|
|
sbuf_printf(s, "\nHardware resource usage:\n");
|
|
|
|
sbuf_printf(s, "DSP General Purpose Registers: %d used, %d total\n", sc->rm->num_used, sc->rm->num_gprs);
|
|
|
|
sbuf_printf(s, "DSP Instruction Registers: %d used, %d total\n", sc->routing_code_end, sc->code_size);
|
|
|
|
sbuf_printf(s, "Card supports");
|
|
|
|
if (sc->has_ac97) {
|
|
|
|
sbuf_printf(s, " AC97 codec");
|
|
|
|
} else {
|
|
|
|
sbuf_printf(s, " NO AC97 codec");
|
|
|
|
}
|
|
|
|
if (sc->has_51) {
|
|
|
|
if (sc->has_71)
|
|
|
|
sbuf_printf(s, " and 7.1 output");
|
|
|
|
else
|
|
|
|
sbuf_printf(s, " and 5.1 output");
|
|
|
|
}
|
|
|
|
if (sc->is_emu10k1)
|
|
|
|
sbuf_printf(s, ", SBLive! DSP code");
|
|
|
|
if (sc->is_emu10k2)
|
|
|
|
sbuf_printf(s, ", Audigy DSP code");
|
|
|
|
if (sc->is_ca0102)
|
|
|
|
sbuf_printf(s, ", Audigy DSP code with Audigy2 hacks");
|
|
|
|
if (sc->is_ca0108)
|
|
|
|
sbuf_printf(s, ", Audigy DSP code with Audigy2Value hacks");
|
|
|
|
sbuf_printf(s, "\n");
|
|
|
|
if (sc->broken_digital)
|
|
|
|
sbuf_printf(s, "Digital mode unsupported\n");
|
|
|
|
sbuf_printf(s, "\nInstalled devices:\n");
|
2007-01-06 18:59:35 +00:00
|
|
|
for (i = 0; i < RT_COUNT; i++)
|
2006-07-15 19:36:28 +00:00
|
|
|
if (sc->pcm[i] != NULL)
|
|
|
|
if (device_is_attached(sc->pcm[i])) {
|
|
|
|
sbuf_printf(s, "%s on %s\n", device_get_desc(sc->pcm[i]), device_get_nameunit(sc->pcm[i]));
|
|
|
|
}
|
|
|
|
if (sc->midi[0] != NULL)
|
|
|
|
if (device_is_attached(sc->midi[0])) {
|
|
|
|
sbuf_printf(s, "EMU10Kx MIDI Interface\n");
|
|
|
|
sbuf_printf(s, "\tOn-card connector on %s\n", device_get_nameunit(sc->midi[0]));
|
|
|
|
}
|
|
|
|
if (sc->midi[1] != NULL)
|
|
|
|
if (device_is_attached(sc->midi[1])) {
|
|
|
|
sbuf_printf(s, "\tOn-Drive connector on %s\n", device_get_nameunit(sc->midi[1]));
|
|
|
|
}
|
|
|
|
if (sc->midi[0] != NULL)
|
|
|
|
if (device_is_attached(sc->midi[0])) {
|
|
|
|
sbuf_printf(s, "\tIR reciever MIDI events %s\n", sc->enable_ir ? "enabled" : "disabled");
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
sbuf_printf(s, "Card is in %s mode\n", (sc->mode == MODE_ANALOG) ? "analog" : "digital");
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
sbuf_finish(s);
|
|
|
|
return (sbuf_len(s));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* INIT & UNINIT */
|
|
|
|
static int
|
|
|
|
emu10kx_dev_init(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
int unit;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_init(&sc->emu10kx_lock, device_get_nameunit(sc->dev), "kxdevlock", 0);
|
2006-07-15 19:36:28 +00:00
|
|
|
unit = device_get_unit(sc->dev);
|
|
|
|
|
Sound Mega-commit. Expect further cleanup until code freeze.
For a slightly thorough explaination, please refer to
[1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html .
Summary of changes includes:
1 Volume Per-Channel (vpc). Provides private / standalone volume control
unique per-stream pcm channel without touching master volume / pcm.
Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for
backwards compatibility, SOUND_MIXER_PCM through the opened dsp device
instead of /dev/mixer. Special "bypass" mode is enabled through
/dev/mixer which will automatically detect if the adjustment is made
through /dev/mixer and forward its request to this private volume
controller. Changes to this volume object will not interfere with
other channels.
Requirements:
- SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which
require specific application modifications (preferred).
- No modifications required for using bypass mode, so applications
like mplayer or xmms should work out of the box.
Kernel hints:
- hint.pcm.%d.vpc (0 = disable vpc).
Kernel sysctls:
- hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer
bypass mode.
- hw.snd.vpc_autoreset (default: 1). By default, closing/opening
/dev/dsp will reset the volume back to 0 db gain/attenuation.
Setting this to 0 will preserve its settings across device
closing/opening.
- hw.snd.vpc_reset (default: 0). Panic/reset button to reset all
volume settings back to 0 db.
- hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value.
2 High quality fixed-point Bandlimited SINC sampling rate converter,
based on Julius O'Smith's Digital Audio Resampling -
http://ccrma.stanford.edu/~jos/resample/. It includes a filter design
script written in awk (the clumsiest joke I've ever written)
- 100% 32bit fixed-point, 64bit accumulator.
- Possibly among the fastest (if not fastest) of its kind.
- Resampling quality is tunable, either runtime or during kernel
compilation (FEEDER_RATE_PRESETS).
- Quality can be further customized during kernel compilation by
defining FEEDER_RATE_PRESETS in /etc/make.conf.
Kernel sysctls:
- hw.snd.feeder_rate_quality.
0 - Zero-order Hold (ZOH). Fastest, bad quality.
1 - Linear Interpolation (LINEAR). Slightly slower than ZOH,
better quality but still does not eliminate aliasing.
2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC
quality always start from 2 and above.
Rough quality comparisons:
- http://people.freebsd.org/~ariff/z_comparison/
3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be
directly fed into the hardware.
4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can
be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf.
5 Transparent/Adaptive Virtual Channel. Now you don't have to disable
vchans in order to make digital format pass through. It also makes
vchans more dynamic by choosing a better format/rate among all the
concurrent streams, which means that dev.pcm.X.play.vchanformat/rate
becomes sort of optional.
6 Exclusive Stream, with special open() mode O_EXCL. This will "mute"
other concurrent vchan streams and only allow a single channel with
O_EXCL set to keep producing sound.
Other Changes:
* most feeder_* stuffs are compilable in userland. Let's not
speculate whether we should go all out for it (save that for
FreeBSD 16.0-RELEASE).
* kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org>
* pull out channel mixing logic out of vchan.c and create its own
feeder_mixer for world justice.
* various refactoring here and there, for good or bad.
* activation of few more OSSv4 ioctls() (see [1] above).
* opt_snd.h for possible compile time configuration:
(mostly for debugging purposes, don't try these at home)
SND_DEBUG
SND_DIAGNOSTIC
SND_FEEDER_MULTIFORMAT
SND_FEEDER_FULL_MULTIFORMAT
SND_FEEDER_RATE_HP
SND_PCM_64
SND_OLDSTEREO
Manual page updates are on the way.
Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many
unsung / unnamed heroes.
2009-06-07 19:12:08 +00:00
|
|
|
sc->cdev = make_dev(&emu10kx_cdevsw, PCMMINOR(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit);
|
2006-07-15 19:36:28 +00:00
|
|
|
if (sc->cdev != NULL) {
|
|
|
|
sc->cdev->si_drv1 = sc;
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
return (ENXIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu10kx_dev_uninit(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
mtx_lock(&sc->emu10kx_lock);
|
|
|
|
if (sc->emu10kx_isopen) {
|
|
|
|
mtx_unlock(&sc->emu10kx_lock);
|
|
|
|
return (EBUSY);
|
|
|
|
}
|
|
|
|
if (sc->cdev)
|
|
|
|
destroy_dev(sc->cdev);
|
|
|
|
sc->cdev = 0;
|
|
|
|
|
|
|
|
mtx_destroy(&sc->emu10kx_lock);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* resource manager */
|
|
|
|
int
|
|
|
|
emu_rm_init(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int maxcount;
|
|
|
|
struct emu_rm *rm;
|
|
|
|
|
|
|
|
rm = malloc(sizeof(struct emu_rm), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (rm == NULL) {
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
sc->rm = rm;
|
|
|
|
rm->card = sc;
|
|
|
|
maxcount = sc->num_gprs;
|
|
|
|
rm->num_used = 0;
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_init(&(rm->gpr_lock), device_get_nameunit(sc->dev), "gpr alloc", MTX_DEF);
|
2006-07-15 19:36:28 +00:00
|
|
|
rm->num_gprs = (maxcount < EMU_MAX_GPR ? maxcount : EMU_MAX_GPR);
|
|
|
|
for (i = 0; i < rm->num_gprs; i++)
|
|
|
|
rm->allocmap[i] = 0;
|
2007-09-12 07:43:43 +00:00
|
|
|
/* pre-allocate gpr[0] */
|
|
|
|
rm->allocmap[0] = 1;
|
|
|
|
rm->last_free_gpr = 1;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emu_rm_uninit(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1) {
|
|
|
|
mtx_lock(&(sc->rm->gpr_lock));
|
|
|
|
for (i = 1; i < sc->rm->last_free_gpr; i++)
|
|
|
|
if (sc->rm->allocmap[i] > 0)
|
|
|
|
device_printf(sc->dev, "rm: gpr %d not free before uninit\n", i);
|
|
|
|
mtx_unlock(&(sc->rm->gpr_lock));
|
|
|
|
}
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
mtx_destroy(&(sc->rm->gpr_lock));
|
|
|
|
free(sc->rm, M_DEVBUF);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_rm_gpr_alloc(struct emu_rm *rm, int count)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
int allocated_gpr;
|
|
|
|
|
|
|
|
allocated_gpr = rm->num_gprs;
|
|
|
|
/* try fast way first */
|
|
|
|
mtx_lock(&(rm->gpr_lock));
|
|
|
|
if (rm->last_free_gpr + count <= rm->num_gprs) {
|
|
|
|
allocated_gpr = rm->last_free_gpr;
|
|
|
|
rm->last_free_gpr += count;
|
|
|
|
rm->allocmap[allocated_gpr] = count;
|
|
|
|
for (i = 1; i < count; i++)
|
|
|
|
rm->allocmap[allocated_gpr + i] = -(count - i);
|
|
|
|
} else {
|
|
|
|
/* longer */
|
|
|
|
i = 0;
|
|
|
|
allocated_gpr = rm->num_gprs;
|
|
|
|
while (i < rm->last_free_gpr - count) {
|
|
|
|
if (rm->allocmap[i] > 0) {
|
|
|
|
i += rm->allocmap[i];
|
|
|
|
} else {
|
|
|
|
allocated_gpr = i;
|
|
|
|
for (j = 1; j < count; j++) {
|
|
|
|
if (rm->allocmap[i + j] != 0)
|
|
|
|
allocated_gpr = rm->num_gprs;
|
|
|
|
}
|
|
|
|
if (allocated_gpr == i)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (allocated_gpr + count < rm->last_free_gpr) {
|
|
|
|
rm->allocmap[allocated_gpr] = count;
|
|
|
|
for (i = 1; i < count; i++)
|
|
|
|
rm->allocmap[allocated_gpr + i] = -(count - i);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (allocated_gpr == rm->num_gprs)
|
|
|
|
allocated_gpr = (-1);
|
|
|
|
if (allocated_gpr >= 0)
|
|
|
|
rm->num_used += count;
|
|
|
|
mtx_unlock(&(rm->gpr_lock));
|
|
|
|
return (allocated_gpr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mixer */
|
|
|
|
void
|
|
|
|
emumix_set_mode(struct emu_sc_info *sc, int mode)
|
|
|
|
{
|
|
|
|
uint32_t a_iocfg;
|
|
|
|
uint32_t hcfg;
|
|
|
|
uint32_t tmp;
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case MODE_DIGITAL:
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case MODE_ANALOG:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
hcfg = HCFG_AUDIOENABLE | HCFG_AUTOMUTE;
|
|
|
|
a_iocfg = 0;
|
|
|
|
|
|
|
|
if (sc->rev >= 6)
|
|
|
|
hcfg |= HCFG_JOYENABLE;
|
|
|
|
|
|
|
|
if (sc->is_emu10k1)
|
|
|
|
hcfg |= HCFG_LOCKTANKCACHE_MASK;
|
|
|
|
else
|
|
|
|
hcfg |= HCFG_CODECFORMAT_I2S | HCFG_JOYENABLE;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == MODE_DIGITAL) {
|
|
|
|
if (sc->broken_digital) {
|
2007-09-12 07:43:43 +00:00
|
|
|
device_printf(sc->dev, "Digital mode is reported as broken on this card.\n");
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
a_iocfg |= A_IOCFG_ENABLE_DIGITAL;
|
|
|
|
hcfg |= HCFG_GPOUT0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == MODE_ANALOG)
|
|
|
|
emumix_set_spdif_mode(sc, SPDIF_MODE_PCM);
|
|
|
|
|
|
|
|
if (sc->is_emu10k2)
|
|
|
|
a_iocfg |= 0x80; /* XXX */
|
|
|
|
|
|
|
|
if ((sc->is_ca0102) || (sc->is_ca0108))
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* Setting A_IOCFG_DISABLE_ANALOG will do opposite things
|
|
|
|
* on diffrerent cards.
|
|
|
|
* "don't disable analog outs" on Audigy 2 (ca0102/ca0108)
|
|
|
|
* "disable analog outs" on Audigy (emu10k2)
|
|
|
|
*/
|
|
|
|
a_iocfg |= A_IOCFG_DISABLE_ANALOG;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
if (sc->is_ca0108)
|
|
|
|
a_iocfg |= 0x20; /* XXX */
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* Mute analog center & subwoofer before mode change */
|
|
|
|
if (mode == MODE_DIGITAL)
|
|
|
|
emumix_set_gpr(sc, sc->mute_gpr[ANALOGMUTE], 1);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_wr(sc, HCFG, hcfg, 4);
|
|
|
|
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
|
|
|
|
tmp = emu_rd(sc, A_IOCFG, 2);
|
|
|
|
tmp = a_iocfg;
|
|
|
|
emu_wr(sc, A_IOCFG, tmp, 2);
|
|
|
|
}
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* Unmute if we have changed mode to analog. */
|
|
|
|
|
|
|
|
if (mode == MODE_ANALOG)
|
|
|
|
emumix_set_gpr(sc, sc->mute_gpr[ANALOGMUTE], 0);
|
|
|
|
|
|
|
|
sc->mode = mode;
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emumix_set_spdif_mode(struct emu_sc_info *sc, int mode)
|
|
|
|
{
|
|
|
|
uint32_t spcs;
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case SPDIF_MODE_PCM:
|
|
|
|
break;
|
|
|
|
case SPDIF_MODE_AC3:
|
|
|
|
device_printf(sc->dev, "AC3 mode does not work and disabled\n");
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
spcs = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
|
|
|
|
SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
|
|
|
|
SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 |
|
|
|
|
SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT;
|
|
|
|
|
|
|
|
mode = SPDIF_MODE_PCM;
|
|
|
|
|
|
|
|
emu_wrptr(sc, 0, SPCS0, spcs);
|
|
|
|
emu_wrptr(sc, 0, SPCS1, spcs);
|
|
|
|
emu_wrptr(sc, 0, SPCS2, spcs);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define L2L_POINTS 10
|
|
|
|
|
|
|
|
static int l2l_df[L2L_POINTS] = {
|
|
|
|
0x572C5CA, /* 100..90 */
|
|
|
|
0x3211625, /* 90..80 */
|
|
|
|
0x1CC1A76, /* 80..70 */
|
|
|
|
0x108428F, /* 70..60 */
|
|
|
|
0x097C70A, /* 60..50 */
|
|
|
|
0x0572C5C, /* 50..40 */
|
|
|
|
0x0321162, /* 40..30 */
|
|
|
|
0x01CC1A7, /* 30..20 */
|
|
|
|
0x0108428, /* 20..10 */
|
|
|
|
0x016493D /* 10..0 */
|
|
|
|
};
|
|
|
|
|
|
|
|
static int l2l_f[L2L_POINTS] = {
|
|
|
|
0x4984461A, /* 90 */
|
|
|
|
0x2A3968A7, /* 80 */
|
|
|
|
0x18406003, /* 70 */
|
|
|
|
0x0DEDC66D, /* 60 */
|
|
|
|
0x07FFFFFF, /* 50 */
|
|
|
|
0x04984461, /* 40 */
|
|
|
|
0x02A3968A, /* 30 */
|
|
|
|
0x01840600, /* 20 */
|
|
|
|
0x00DEDC66, /* 10 */
|
|
|
|
0x00000000 /* 0 */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
log2lin(int log_t)
|
|
|
|
{
|
|
|
|
int lin_t;
|
|
|
|
int idx, lin;
|
|
|
|
|
|
|
|
if (log_t <= 0) {
|
|
|
|
lin_t = 0x00000000;
|
|
|
|
return (lin_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (log_t >= 100) {
|
|
|
|
lin_t = 0x7fffffff;
|
|
|
|
return (lin_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = (L2L_POINTS - 1) - log_t / (L2L_POINTS);
|
|
|
|
lin = log_t % (L2L_POINTS);
|
|
|
|
lin_t = l2l_df[idx] * lin + l2l_f[idx];
|
|
|
|
return (lin_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol)
|
|
|
|
{
|
|
|
|
|
|
|
|
vol = log2lin(vol);
|
|
|
|
emumix_set_gpr(sc, gpr, vol);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val)
|
|
|
|
{
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->dbg_level > 1)
|
|
|
|
if (gpr == 0) {
|
|
|
|
device_printf(sc->dev, "Zero gpr write access\n");
|
|
|
|
#ifdef KDB
|
|
|
|
kdb_backtrace();
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
emu_wrptr(sc, 0, GPR(gpr), val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume)
|
|
|
|
{
|
|
|
|
|
|
|
|
RANGE(volume, 0, 100);
|
|
|
|
if (mixer_idx < NUM_MIXERS) {
|
|
|
|
sc->mixer_volcache[mixer_idx] = volume;
|
|
|
|
emumix_set_fxvol(sc, sc->mixer_gpr[mixer_idx], volume);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
emumix_get_volume(struct emu_sc_info *sc, int mixer_idx)
|
|
|
|
{
|
|
|
|
if ((mixer_idx < NUM_MIXERS) && (mixer_idx >= 0))
|
|
|
|
return (sc->mixer_volcache[mixer_idx]);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Init CardBus part */
|
|
|
|
static int
|
|
|
|
emu_cardbus_init(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX May not need this if we have IPR3 handler.
|
|
|
|
* Is it a real init calls, or IPR3 interrupt acknowledgments?
|
|
|
|
* Looks much like "(data << 16) | register".
|
|
|
|
*/
|
|
|
|
emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0000);
|
|
|
|
emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0001);
|
|
|
|
emu_wr_cbptr(sc, (0x00d0 << 16) | 0x005f);
|
|
|
|
emu_wr_cbptr(sc, (0x00d0 << 16) | 0x007f);
|
|
|
|
|
|
|
|
emu_wr_cbptr(sc, (0x0090 << 16) | 0x007f);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Probe and attach the card */
|
|
|
|
static int
|
2007-02-23 19:41:16 +00:00
|
|
|
emu_init(struct emu_sc_info *sc)
|
2006-07-15 19:36:28 +00:00
|
|
|
{
|
|
|
|
uint32_t ch, tmp;
|
|
|
|
uint32_t spdif_sr;
|
|
|
|
uint32_t ac97slot;
|
|
|
|
int def_mode;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* disable audio and lock cache */
|
|
|
|
emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4);
|
|
|
|
|
|
|
|
/* reset recording buffers */
|
|
|
|
emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE);
|
|
|
|
emu_wrptr(sc, 0, MICBA, 0);
|
|
|
|
emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE);
|
|
|
|
emu_wrptr(sc, 0, FXBA, 0);
|
|
|
|
emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE);
|
|
|
|
emu_wrptr(sc, 0, ADCBA, 0);
|
|
|
|
|
|
|
|
/* disable channel interrupt */
|
|
|
|
emu_wr(sc, INTE, INTE_INTERVALTIMERENB | INTE_SAMPLERATETRACKER | INTE_PCIERRORENABLE, 4);
|
|
|
|
emu_wrptr(sc, 0, CLIEL, 0);
|
|
|
|
emu_wrptr(sc, 0, CLIEH, 0);
|
|
|
|
emu_wrptr(sc, 0, SOLEL, 0);
|
|
|
|
emu_wrptr(sc, 0, SOLEH, 0);
|
|
|
|
|
|
|
|
/* disable P16V and S/PDIF interrupts */
|
|
|
|
if ((sc->is_ca0102) || (sc->is_ca0108))
|
|
|
|
emu_wr(sc, INTE2, 0, 4);
|
|
|
|
|
|
|
|
if (sc->is_ca0102)
|
|
|
|
emu_wr(sc, INTE3, 0, 4);
|
|
|
|
|
|
|
|
/* init phys inputs and outputs */
|
|
|
|
ac97slot = 0;
|
|
|
|
if (sc->has_51)
|
|
|
|
ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE;
|
|
|
|
if (sc->has_71)
|
|
|
|
ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE | AC97SLOT_REAR_LEFT | AC97SLOT_REAR_RIGHT;
|
|
|
|
if (sc->is_emu10k2)
|
|
|
|
ac97slot |= 0x40;
|
|
|
|
emu_wrptr(sc, 0, AC97SLOT, ac97slot);
|
|
|
|
|
|
|
|
if (sc->is_emu10k2) /* XXX for later cards? */
|
|
|
|
emu_wrptr(sc, 0, SPBYPASS, 0xf00); /* What will happen if
|
|
|
|
* we write 1 here? */
|
|
|
|
|
2007-02-23 19:41:16 +00:00
|
|
|
if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(sc->dev),
|
2007-02-23 13:47:34 +00:00
|
|
|
/* alignment */ 2, /* boundary */ 0,
|
2006-07-15 19:36:28 +00:00
|
|
|
/* lowaddr */ 1 << 31, /* can only access 0-2gb */
|
|
|
|
/* highaddr */ BUS_SPACE_MAXADDR,
|
|
|
|
/* filter */ NULL, /* filterarg */ NULL,
|
2006-08-07 22:44:01 +00:00
|
|
|
/* maxsize */ EMU_MAX_BUFSZ, /* nsegments */ 1, /* maxsegz */ 0x3ffff,
|
2006-07-15 19:36:28 +00:00
|
|
|
/* flags */ 0, /* lockfunc */ busdma_lock_mutex,
|
|
|
|
/* lockarg */ &Giant, &(sc->mem.dmat)) != 0) {
|
|
|
|
device_printf(sc->dev, "unable to create dma tag\n");
|
|
|
|
bus_dma_tag_destroy(sc->mem.dmat);
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
sc->mem.card = sc;
|
2006-07-15 19:36:28 +00:00
|
|
|
SLIST_INIT(&sc->mem.blocks);
|
2006-08-07 22:44:01 +00:00
|
|
|
sc->mem.ptb_pages = emu_malloc(&sc->mem, EMU_MAXPAGES * sizeof(uint32_t), &sc->mem.ptb_pages_addr);
|
2006-07-15 19:36:28 +00:00
|
|
|
if (sc->mem.ptb_pages == NULL)
|
|
|
|
return (ENOMEM);
|
|
|
|
|
|
|
|
sc->mem.silent_page = emu_malloc(&sc->mem, EMUPAGESIZE, &sc->mem.silent_page_addr);
|
|
|
|
if (sc->mem.silent_page == NULL) {
|
|
|
|
emu_free(&sc->mem, sc->mem.ptb_pages);
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
/* Clear page with silence & setup all pointers to this page */
|
|
|
|
bzero(sc->mem.silent_page, EMUPAGESIZE);
|
|
|
|
tmp = (uint32_t) (sc->mem.silent_page_addr) << 1;
|
2006-08-07 22:44:01 +00:00
|
|
|
for (i = 0; i < EMU_MAXPAGES; i++)
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->mem.ptb_pages[i] = tmp | i;
|
|
|
|
|
|
|
|
for (ch = 0; ch < NUM_G; ch++) {
|
|
|
|
emu_wrptr(sc, ch, MAPA, tmp | MAP_PTI_MASK);
|
|
|
|
emu_wrptr(sc, ch, MAPB, tmp | MAP_PTI_MASK);
|
|
|
|
}
|
|
|
|
emu_wrptr(sc, 0, PTB, (sc->mem.ptb_pages_addr));
|
|
|
|
emu_wrptr(sc, 0, TCB, 0); /* taken from original driver */
|
|
|
|
emu_wrptr(sc, 0, TCBS, 0); /* taken from original driver */
|
|
|
|
|
|
|
|
/* init envelope engine */
|
|
|
|
for (ch = 0; ch < NUM_G; ch++) {
|
|
|
|
emu_wrptr(sc, ch, DCYSUSV, 0);
|
|
|
|
emu_wrptr(sc, ch, IP, 0);
|
|
|
|
emu_wrptr(sc, ch, VTFT, 0xffff);
|
|
|
|
emu_wrptr(sc, ch, CVCF, 0xffff);
|
|
|
|
emu_wrptr(sc, ch, PTRX, 0);
|
|
|
|
emu_wrptr(sc, ch, CPF, 0);
|
|
|
|
emu_wrptr(sc, ch, CCR, 0);
|
|
|
|
|
|
|
|
emu_wrptr(sc, ch, PSST, 0);
|
|
|
|
emu_wrptr(sc, ch, DSL, 0x10);
|
|
|
|
emu_wrptr(sc, ch, CCCA, 0);
|
|
|
|
emu_wrptr(sc, ch, Z1, 0);
|
|
|
|
emu_wrptr(sc, ch, Z2, 0);
|
|
|
|
emu_wrptr(sc, ch, FXRT, 0xd01c0000);
|
|
|
|
|
|
|
|
emu_wrptr(sc, ch, ATKHLDM, 0);
|
|
|
|
emu_wrptr(sc, ch, DCYSUSM, 0);
|
|
|
|
emu_wrptr(sc, ch, IFATN, 0xffff);
|
|
|
|
emu_wrptr(sc, ch, PEFE, 0);
|
|
|
|
emu_wrptr(sc, ch, FMMOD, 0);
|
|
|
|
emu_wrptr(sc, ch, TREMFRQ, 24); /* 1 Hz */
|
|
|
|
emu_wrptr(sc, ch, FM2FRQ2, 24); /* 1 Hz */
|
|
|
|
emu_wrptr(sc, ch, TEMPENV, 0);
|
|
|
|
|
|
|
|
/*** these are last so OFF prevents writing ***/
|
|
|
|
emu_wrptr(sc, ch, LFOVAL2, 0);
|
|
|
|
emu_wrptr(sc, ch, LFOVAL1, 0);
|
|
|
|
emu_wrptr(sc, ch, ATKHLDV, 0);
|
|
|
|
emu_wrptr(sc, ch, ENVVOL, 0);
|
|
|
|
emu_wrptr(sc, ch, ENVVAL, 0);
|
|
|
|
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
|
|
|
|
emu_wrptr(sc, ch, 0x4c, 0x0);
|
|
|
|
emu_wrptr(sc, ch, 0x4d, 0x0);
|
|
|
|
emu_wrptr(sc, ch, 0x4e, 0x0);
|
|
|
|
emu_wrptr(sc, ch, 0x4f, 0x0);
|
|
|
|
emu_wrptr(sc, ch, A_FXRT1, 0x3f3f3f3f);
|
|
|
|
emu_wrptr(sc, ch, A_FXRT2, 0x3f3f3f3f);
|
|
|
|
emu_wrptr(sc, ch, A_SENDAMOUNTS, 0x0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
emumix_set_spdif_mode(sc, SPDIF_MODE_PCM);
|
|
|
|
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108))
|
|
|
|
emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, A_SPDIF_48000);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CAxxxx cards needs additional setup:
|
|
|
|
* 1. Set I2S capture sample rate to 96000
|
|
|
|
* 2. Disable P16v / P17v proceesing
|
|
|
|
* 3. Allow EMU10K DSP inputs
|
|
|
|
*/
|
|
|
|
if ((sc->is_ca0102) || (sc->is_ca0108)) {
|
|
|
|
|
|
|
|
spdif_sr = emu_rdptr(sc, 0, A_SPDIF_SAMPLERATE);
|
|
|
|
spdif_sr &= 0xfffff1ff;
|
|
|
|
spdif_sr |= A_I2S_CAPTURE_96000;
|
|
|
|
emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, spdif_sr);
|
|
|
|
|
|
|
|
/* Disable P16v processing */
|
|
|
|
emu_wr_p16vptr(sc, 0, SRCSel, 0x14);
|
|
|
|
|
|
|
|
/* Setup P16v/P17v sound routing */
|
|
|
|
if (sc->is_ca0102)
|
|
|
|
emu_wr_p16vptr(sc, 0, SRCMULTI_ENABLE, 0xFF00FF00);
|
|
|
|
else {
|
|
|
|
emu_wr_p16vptr(sc, 0, P17V_MIXER_I2S_ENABLE, 0xFF000000);
|
|
|
|
emu_wr_p16vptr(sc, 0, P17V_MIXER_SPDIF_ENABLE, 0xFF000000);
|
|
|
|
|
|
|
|
tmp = emu_rd(sc, A_IOCFG, 2);
|
|
|
|
emu_wr(sc, A_IOCFG, tmp & ~0x8, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
emu_initefx(sc);
|
|
|
|
|
|
|
|
def_mode = MODE_ANALOG;
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108))
|
|
|
|
def_mode = MODE_DIGITAL;
|
|
|
|
if (((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) && (sc->broken_digital)) {
|
|
|
|
device_printf(sc->dev, "Audigy card initialized in analog mode.\n");
|
|
|
|
def_mode = MODE_ANALOG;
|
|
|
|
}
|
|
|
|
emumix_set_mode(sc, def_mode);
|
|
|
|
|
|
|
|
if (bootverbose) {
|
|
|
|
tmp = emu_rd(sc, HCFG, 4);
|
|
|
|
device_printf(sc->dev, "Card Configuration ( 0x%08x )\n", tmp);
|
|
|
|
device_printf(sc->dev, "Card Configuration ( & 0xff000000 ) : %s%s%s%s%s%s%s%s\n",
|
|
|
|
(tmp & 0x80000000 ? "[Legacy MPIC] " : ""),
|
|
|
|
(tmp & 0x40000000 ? "[0x40] " : ""),
|
|
|
|
(tmp & 0x20000000 ? "[0x20] " : ""),
|
|
|
|
(tmp & 0x10000000 ? "[0x10] " : ""),
|
|
|
|
(tmp & 0x08000000 ? "[0x08] " : ""),
|
|
|
|
(tmp & 0x04000000 ? "[0x04] " : ""),
|
|
|
|
(tmp & 0x02000000 ? "[0x02] " : ""),
|
|
|
|
(tmp & 0x01000000 ? "[0x01]" : " "));
|
|
|
|
device_printf(sc->dev, "Card Configuration ( & 0x00ff0000 ) : %s%s%s%s%s%s%s%s\n",
|
|
|
|
(tmp & 0x00800000 ? "[0x80] " : ""),
|
|
|
|
(tmp & 0x00400000 ? "[0x40] " : ""),
|
|
|
|
(tmp & 0x00200000 ? "[Legacy INT] " : ""),
|
|
|
|
(tmp & 0x00100000 ? "[0x10] " : ""),
|
|
|
|
(tmp & 0x00080000 ? "[0x08] " : ""),
|
|
|
|
(tmp & 0x00040000 ? "[Codec4] " : ""),
|
|
|
|
(tmp & 0x00020000 ? "[Codec2] " : ""),
|
|
|
|
(tmp & 0x00010000 ? "[I2S Codec]" : " "));
|
|
|
|
device_printf(sc->dev, "Card Configuration ( & 0x0000ff00 ) : %s%s%s%s%s%s%s%s\n",
|
|
|
|
(tmp & 0x00008000 ? "[0x80] " : ""),
|
|
|
|
(tmp & 0x00004000 ? "[GPINPUT0] " : ""),
|
|
|
|
(tmp & 0x00002000 ? "[GPINPUT1] " : ""),
|
|
|
|
(tmp & 0x00001000 ? "[GPOUT0] " : ""),
|
|
|
|
(tmp & 0x00000800 ? "[GPOUT1] " : ""),
|
|
|
|
(tmp & 0x00000400 ? "[GPOUT2] " : ""),
|
|
|
|
(tmp & 0x00000200 ? "[Joystick] " : ""),
|
|
|
|
(tmp & 0x00000100 ? "[0x01]" : " "));
|
|
|
|
device_printf(sc->dev, "Card Configuration ( & 0x000000ff ) : %s%s%s%s%s%s%s%s\n",
|
|
|
|
(tmp & 0x00000080 ? "[0x80] " : ""),
|
|
|
|
(tmp & 0x00000040 ? "[0x40] " : ""),
|
|
|
|
(tmp & 0x00000020 ? "[0x20] " : ""),
|
|
|
|
(tmp & 0x00000010 ? "[AUTOMUTE] " : ""),
|
|
|
|
(tmp & 0x00000008 ? "[LOCKSOUNDCACHE] " : ""),
|
|
|
|
(tmp & 0x00000004 ? "[LOCKTANKCACHE] " : ""),
|
|
|
|
(tmp & 0x00000002 ? "[MUTEBUTTONENABLE] " : ""),
|
|
|
|
(tmp & 0x00000001 ? "[AUDIOENABLE]" : " "));
|
|
|
|
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
|
|
|
|
tmp = emu_rd(sc, A_IOCFG, 2);
|
|
|
|
device_printf(sc->dev, "Audigy Card Configuration ( 0x%04x )\n", tmp);
|
|
|
|
device_printf(sc->dev, "Audigy Card Configuration ( & 0xff00 )");
|
|
|
|
printf(" : %s%s%s%s%s%s%s%s\n",
|
|
|
|
(tmp & 0x8000 ? "[Rear Speakers] " : ""),
|
|
|
|
(tmp & 0x4000 ? "[Front Speakers] " : ""),
|
|
|
|
(tmp & 0x2000 ? "[0x20] " : ""),
|
|
|
|
(tmp & 0x1000 ? "[0x10] " : ""),
|
|
|
|
(tmp & 0x0800 ? "[0x08] " : ""),
|
|
|
|
(tmp & 0x0400 ? "[0x04] " : ""),
|
|
|
|
(tmp & 0x0200 ? "[0x02] " : ""),
|
|
|
|
(tmp & 0x0100 ? "[AudigyDrive Phones]" : " "));
|
|
|
|
device_printf(sc->dev, "Audigy Card Configuration ( & 0x00ff )");
|
|
|
|
printf(" : %s%s%s%s%s%s%s%s\n",
|
|
|
|
(tmp & 0x0080 ? "[0x80] " : ""),
|
|
|
|
(tmp & 0x0040 ? "[Mute AnalogOut] " : ""),
|
|
|
|
(tmp & 0x0020 ? "[0x20] " : ""),
|
|
|
|
(tmp & 0x0010 ? "[0x10] " : ""),
|
|
|
|
(tmp & 0x0008 ? "[0x08] " : ""),
|
|
|
|
(tmp & 0x0004 ? "[GPOUT0] " : ""),
|
|
|
|
(tmp & 0x0002 ? "[GPOUT1] " : ""),
|
|
|
|
(tmp & 0x0001 ? "[GPOUT2]" : " "));
|
|
|
|
} /* is_emu10k2 or ca* */
|
|
|
|
} /* bootverbose */
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_uninit(struct emu_sc_info *sc)
|
|
|
|
{
|
|
|
|
uint32_t ch;
|
|
|
|
struct emu_memblk *blk;
|
|
|
|
|
|
|
|
emu_wr(sc, INTE, 0, 4);
|
|
|
|
for (ch = 0; ch < NUM_G; ch++)
|
|
|
|
emu_wrptr(sc, ch, DCYSUSV, 0);
|
|
|
|
for (ch = 0; ch < NUM_G; ch++) {
|
|
|
|
emu_wrptr(sc, ch, VTFT, 0);
|
|
|
|
emu_wrptr(sc, ch, CVCF, 0);
|
|
|
|
emu_wrptr(sc, ch, PTRX, 0);
|
|
|
|
emu_wrptr(sc, ch, CPF, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* disable audio and lock cache */
|
|
|
|
emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4);
|
|
|
|
|
|
|
|
emu_wrptr(sc, 0, PTB, 0);
|
|
|
|
/* reset recording buffers */
|
|
|
|
emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE);
|
|
|
|
emu_wrptr(sc, 0, MICBA, 0);
|
|
|
|
emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE);
|
|
|
|
emu_wrptr(sc, 0, FXBA, 0);
|
|
|
|
emu_wrptr(sc, 0, FXWC, 0);
|
|
|
|
emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE);
|
|
|
|
emu_wrptr(sc, 0, ADCBA, 0);
|
|
|
|
emu_wrptr(sc, 0, TCB, 0);
|
|
|
|
emu_wrptr(sc, 0, TCBS, 0);
|
|
|
|
|
|
|
|
/* disable channel interrupt */
|
|
|
|
emu_wrptr(sc, 0, CLIEL, 0);
|
|
|
|
emu_wrptr(sc, 0, CLIEH, 0);
|
|
|
|
emu_wrptr(sc, 0, SOLEL, 0);
|
|
|
|
emu_wrptr(sc, 0, SOLEH, 0);
|
|
|
|
|
|
|
|
if (!SLIST_EMPTY(&sc->mem.blocks))
|
|
|
|
device_printf(sc->dev, "warning: memblock list not empty\n");
|
|
|
|
|
|
|
|
SLIST_FOREACH(blk, &sc->mem.blocks, link)
|
|
|
|
if (blk != NULL)
|
|
|
|
device_printf(sc->dev, "lost %d for %s\n", blk->pte_size, blk->owner);
|
|
|
|
|
|
|
|
emu_free(&sc->mem, sc->mem.ptb_pages);
|
|
|
|
emu_free(&sc->mem, sc->mem.silent_page);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_read_ivar(device_t bus, device_t dev, int ivar_index, uintptr_t * result)
|
|
|
|
{
|
|
|
|
struct sndcard_func *func = device_get_ivars(dev);
|
|
|
|
struct emu_sc_info *sc = device_get_softc(bus);
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (func==NULL)
|
|
|
|
return (ENOMEM);
|
|
|
|
if (sc == NULL)
|
|
|
|
return (ENOMEM);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
switch (ivar_index) {
|
|
|
|
case EMU_VAR_FUNC:
|
|
|
|
*result = func->func;
|
|
|
|
break;
|
|
|
|
case EMU_VAR_ROUTE:
|
2007-09-12 07:43:43 +00:00
|
|
|
if (func->varinfo == NULL)
|
|
|
|
return (ENOMEM);
|
2006-07-15 19:36:28 +00:00
|
|
|
*result = ((struct emu_pcminfo *)func->varinfo)->route;
|
|
|
|
break;
|
|
|
|
case EMU_VAR_ISEMU10K1:
|
|
|
|
*result = sc->is_emu10k1;
|
|
|
|
break;
|
2007-09-12 07:43:43 +00:00
|
|
|
case EMU_VAR_MCH_DISABLED:
|
|
|
|
*result = sc->mch_disabled;
|
|
|
|
break;
|
|
|
|
case EMU_VAR_MCH_REC:
|
|
|
|
*result = sc->mch_rec;
|
|
|
|
break;
|
2006-07-15 19:36:28 +00:00
|
|
|
default:
|
|
|
|
return (ENOENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_write_ivar(device_t bus __unused, device_t dev __unused,
|
|
|
|
int ivar_index, uintptr_t value __unused)
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (ivar_index) {
|
|
|
|
case 0:
|
|
|
|
return (EINVAL);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return (ENOENT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_pci_probe(device_t dev)
|
|
|
|
{
|
|
|
|
struct sbuf *s;
|
2007-01-06 18:59:35 +00:00
|
|
|
unsigned int thiscard = 0;
|
2006-07-15 19:36:28 +00:00
|
|
|
uint16_t vendor;
|
|
|
|
|
|
|
|
vendor = pci_read_config(dev, PCIR_DEVVENDOR, /* bytes */ 2);
|
|
|
|
if (vendor != 0x1102)
|
|
|
|
return (ENXIO); /* Not Creative */
|
|
|
|
|
|
|
|
thiscard = emu_getcard(dev);
|
2007-01-06 18:59:35 +00:00
|
|
|
if (thiscard == 0)
|
2006-07-15 19:36:28 +00:00
|
|
|
return (ENXIO);
|
|
|
|
|
|
|
|
s = sbuf_new(NULL, NULL, 4096, 0);
|
|
|
|
if (s == NULL)
|
|
|
|
return (ENOMEM);
|
|
|
|
sbuf_printf(s, "Creative %s [%s]", emu_cards[thiscard].desc, emu_cards[thiscard].SBcode);
|
|
|
|
sbuf_finish(s);
|
|
|
|
|
|
|
|
device_set_desc_copy(dev, sbuf_data(s));
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
sbuf_delete(s);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
return (BUS_PROBE_DEFAULT);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_pci_attach(device_t dev)
|
|
|
|
{
|
|
|
|
struct sndcard_func *func;
|
|
|
|
struct emu_sc_info *sc;
|
|
|
|
struct emu_pcminfo *pcminfo;
|
2007-09-12 07:43:43 +00:00
|
|
|
#if 0
|
|
|
|
struct emu_midiinfo *midiinfo;
|
|
|
|
#endif
|
2006-07-15 19:36:28 +00:00
|
|
|
uint32_t data;
|
|
|
|
int i;
|
|
|
|
int device_flags;
|
|
|
|
char status[255];
|
|
|
|
int error = ENXIO;
|
2007-09-12 07:43:43 +00:00
|
|
|
int unit;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
sc = device_get_softc(dev);
|
2007-09-12 07:43:43 +00:00
|
|
|
unit = device_get_unit(dev);
|
|
|
|
|
|
|
|
if (resource_disabled("emu10kx", unit)) {
|
|
|
|
device_printf(dev, "disabled by kernel hints\n");
|
|
|
|
return (ENXIO); /* XXX to avoid unit reuse */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get configuration */
|
|
|
|
|
|
|
|
sc->ctx = device_get_sysctl_ctx(dev);
|
|
|
|
if (sc->ctx == NULL)
|
|
|
|
goto bad;
|
|
|
|
sc->root = device_get_sysctl_tree(dev);
|
|
|
|
if (sc->root == NULL)
|
|
|
|
goto bad;
|
|
|
|
|
|
|
|
if (resource_int_value("emu10kx", unit, "multichannel_disabled", &(sc->mch_disabled)))
|
|
|
|
RANGE(sc->mch_disabled, 0, 1);
|
|
|
|
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
|
|
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
|
|
|
|
OID_AUTO, "multichannel_disabled", CTLFLAG_RD, &(sc->mch_disabled), 0, "Multichannel playback setting");
|
|
|
|
|
|
|
|
if (resource_int_value("emu10kx", unit, "multichannel_recording", &(sc->mch_rec)))
|
|
|
|
RANGE(sc->mch_rec, 0, 1);
|
|
|
|
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
|
|
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
|
|
|
|
OID_AUTO, "multichannel_recording", CTLFLAG_RD, &(sc->mch_rec), 0, "Multichannel recording setting");
|
|
|
|
|
|
|
|
if (resource_int_value("emu10kx", unit, "debug", &(sc->dbg_level)))
|
|
|
|
RANGE(sc->mch_rec, 0, 2);
|
|
|
|
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
|
|
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
|
|
|
|
OID_AUTO, "debug", CTLFLAG_RW, &(sc->dbg_level), 0, "Debug level");
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
/* Fill in the softc. */
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_init(&sc->lock, device_get_nameunit(dev), "bridge conf", MTX_DEF);
|
|
|
|
mtx_init(&sc->rw, device_get_nameunit(dev), "exclusive io", MTX_DEF);
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->dev = dev;
|
|
|
|
sc->type = pci_get_devid(dev);
|
|
|
|
sc->rev = pci_get_revid(dev);
|
|
|
|
sc->enable_ir = 0;
|
|
|
|
sc->has_ac97 = 0;
|
|
|
|
sc->has_51 = 0;
|
|
|
|
sc->has_71 = 0;
|
|
|
|
sc->broken_digital = 0;
|
|
|
|
sc->is_emu10k1 = 0;
|
|
|
|
sc->is_emu10k2 = 0;
|
|
|
|
sc->is_ca0102 = 0;
|
|
|
|
sc->is_ca0108 = 0;
|
|
|
|
sc->is_cardbus = 0;
|
|
|
|
|
|
|
|
device_flags = emu_cards[emu_getcard(dev)].flags;
|
|
|
|
if (device_flags & HAS_51)
|
|
|
|
sc->has_51 = 1;
|
|
|
|
if (device_flags & HAS_71) {
|
|
|
|
sc->has_51 = 1;
|
|
|
|
sc->has_71 = 1;
|
|
|
|
}
|
|
|
|
if (device_flags & IS_EMU10K1)
|
|
|
|
sc->is_emu10k1 = 1;
|
|
|
|
if (device_flags & IS_EMU10K2)
|
|
|
|
sc->is_emu10k2 = 1;
|
|
|
|
if (device_flags & IS_CA0102)
|
|
|
|
sc->is_ca0102 = 1;
|
|
|
|
if (device_flags & IS_CA0108)
|
|
|
|
sc->is_ca0108 = 1;
|
|
|
|
if ((sc->is_emu10k2) && (sc->rev == 4)) {
|
|
|
|
sc->is_emu10k2 = 0;
|
|
|
|
sc->is_ca0102 = 1; /* for unknown Audigy 2 cards */
|
|
|
|
}
|
|
|
|
if ((sc->is_ca0102 == 1) || (sc->is_ca0108 == 1))
|
|
|
|
if (device_flags & IS_CARDBUS)
|
|
|
|
sc->is_cardbus = 1;
|
|
|
|
|
|
|
|
if ((sc->is_emu10k1 + sc->is_emu10k2 + sc->is_ca0102 + sc->is_ca0108) != 1) {
|
|
|
|
device_printf(sc->dev, "Unable to detect HW chipset\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
if (device_flags & BROKEN_DIGITAL)
|
|
|
|
sc->broken_digital = 1;
|
|
|
|
if (device_flags & HAS_AC97)
|
|
|
|
sc->has_ac97 = 1;
|
|
|
|
|
|
|
|
sc->opcode_shift = 0;
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
|
|
|
|
sc->opcode_shift = 24;
|
|
|
|
sc->high_operand_shift = 12;
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
/* DSP map */
|
2007-01-06 18:59:35 +00:00
|
|
|
/* sc->fx_base = 0x0 */
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->input_base = 0x40;
|
2007-01-06 18:59:35 +00:00
|
|
|
/* sc->p16vinput_base = 0x50; */
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->output_base = 0x60;
|
|
|
|
sc->efxc_base = 0x80;
|
2007-01-06 18:59:35 +00:00
|
|
|
/* sc->output32h_base = 0xa0; */
|
|
|
|
/* sc->output32l_base = 0xb0; */
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->dsp_zero = 0xc0;
|
2007-01-06 18:59:35 +00:00
|
|
|
/* 0xe0...0x100 are unknown */
|
|
|
|
/* sc->tram_base = 0x200 */
|
|
|
|
/* sc->tram_addr_base = 0x300 */
|
|
|
|
sc->gpr_base = A_FXGPREGBASE;
|
|
|
|
sc->num_gprs = 0x200;
|
|
|
|
sc->code_base = A_MICROCODEBASE;
|
|
|
|
sc->code_size = 0x800 / 2; /* 0x600-0xdff, 2048 words,
|
|
|
|
* 1024 instructions */
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->mchannel_fx = 8;
|
|
|
|
sc->num_fxbuses = 16;
|
|
|
|
sc->num_inputs = 8;
|
|
|
|
sc->num_outputs = 16;
|
|
|
|
sc->address_mask = A_PTR_ADDRESS_MASK;
|
|
|
|
}
|
|
|
|
if (sc->is_emu10k1) {
|
2007-09-12 07:43:43 +00:00
|
|
|
sc->has_51 = 0; /* We don't support 5.1 sound on SB Live! 5.1 */
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->opcode_shift = 20;
|
|
|
|
sc->high_operand_shift = 10;
|
|
|
|
sc->code_base = MICROCODEBASE;
|
|
|
|
sc->code_size = 0x400 / 2; /* 0x400-0x7ff, 1024 words,
|
|
|
|
* 512 instructions */
|
|
|
|
sc->gpr_base = FXGPREGBASE;
|
|
|
|
sc->num_gprs = 0x100;
|
|
|
|
sc->input_base = 0x10;
|
|
|
|
sc->output_base = 0x20;
|
2007-09-12 07:43:43 +00:00
|
|
|
/*
|
|
|
|
* XXX 5.1 Analog outputs are inside efxc address space!
|
2007-01-06 18:59:35 +00:00
|
|
|
* They use ouput+0x11/+0x12 (=efxc+1/+2).
|
|
|
|
* Don't use this efx registers for recording on SB Live! 5.1!
|
|
|
|
*/
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->efxc_base = 0x30;
|
|
|
|
sc->dsp_zero = 0x40;
|
|
|
|
sc->mchannel_fx = 0;
|
|
|
|
sc->num_fxbuses = 8;
|
|
|
|
sc->num_inputs = 8;
|
|
|
|
sc->num_outputs = 16;
|
|
|
|
sc->address_mask = PTR_ADDRESS_MASK;
|
|
|
|
}
|
|
|
|
if (sc->opcode_shift == 0)
|
|
|
|
goto bad;
|
|
|
|
|
|
|
|
data = pci_read_config(dev, PCIR_COMMAND, 2);
|
|
|
|
data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN);
|
|
|
|
pci_write_config(dev, PCIR_COMMAND, data, 2);
|
|
|
|
data = pci_read_config(dev, PCIR_COMMAND, 2);
|
|
|
|
|
|
|
|
pci_enable_busmaster(dev);
|
|
|
|
|
|
|
|
i = PCIR_BAR(0);
|
|
|
|
sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE);
|
|
|
|
if (sc->reg == NULL) {
|
|
|
|
device_printf(dev, "unable to map register space\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
sc->st = rman_get_bustag(sc->reg);
|
|
|
|
sc->sh = rman_get_bushandle(sc->reg);
|
|
|
|
|
|
|
|
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
|
|
|
|
sc->timer[i] = 0; /* disable it */
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE);
|
Sound Mega-commit. Expect further cleanup until code freeze.
For a slightly thorough explaination, please refer to
[1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html .
Summary of changes includes:
1 Volume Per-Channel (vpc). Provides private / standalone volume control
unique per-stream pcm channel without touching master volume / pcm.
Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for
backwards compatibility, SOUND_MIXER_PCM through the opened dsp device
instead of /dev/mixer. Special "bypass" mode is enabled through
/dev/mixer which will automatically detect if the adjustment is made
through /dev/mixer and forward its request to this private volume
controller. Changes to this volume object will not interfere with
other channels.
Requirements:
- SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which
require specific application modifications (preferred).
- No modifications required for using bypass mode, so applications
like mplayer or xmms should work out of the box.
Kernel hints:
- hint.pcm.%d.vpc (0 = disable vpc).
Kernel sysctls:
- hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer
bypass mode.
- hw.snd.vpc_autoreset (default: 1). By default, closing/opening
/dev/dsp will reset the volume back to 0 db gain/attenuation.
Setting this to 0 will preserve its settings across device
closing/opening.
- hw.snd.vpc_reset (default: 0). Panic/reset button to reset all
volume settings back to 0 db.
- hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value.
2 High quality fixed-point Bandlimited SINC sampling rate converter,
based on Julius O'Smith's Digital Audio Resampling -
http://ccrma.stanford.edu/~jos/resample/. It includes a filter design
script written in awk (the clumsiest joke I've ever written)
- 100% 32bit fixed-point, 64bit accumulator.
- Possibly among the fastest (if not fastest) of its kind.
- Resampling quality is tunable, either runtime or during kernel
compilation (FEEDER_RATE_PRESETS).
- Quality can be further customized during kernel compilation by
defining FEEDER_RATE_PRESETS in /etc/make.conf.
Kernel sysctls:
- hw.snd.feeder_rate_quality.
0 - Zero-order Hold (ZOH). Fastest, bad quality.
1 - Linear Interpolation (LINEAR). Slightly slower than ZOH,
better quality but still does not eliminate aliasing.
2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC
quality always start from 2 and above.
Rough quality comparisons:
- http://people.freebsd.org/~ariff/z_comparison/
3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be
directly fed into the hardware.
4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can
be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf.
5 Transparent/Adaptive Virtual Channel. Now you don't have to disable
vchans in order to make digital format pass through. It also makes
vchans more dynamic by choosing a better format/rate among all the
concurrent streams, which means that dev.pcm.X.play.vchanformat/rate
becomes sort of optional.
6 Exclusive Stream, with special open() mode O_EXCL. This will "mute"
other concurrent vchan streams and only allow a single channel with
O_EXCL set to keep producing sound.
Other Changes:
* most feeder_* stuffs are compilable in userland. Let's not
speculate whether we should go all out for it (save that for
FreeBSD 16.0-RELEASE).
* kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org>
* pull out channel mixing logic out of vchan.c and create its own
feeder_mixer for world justice.
* various refactoring here and there, for good or bad.
* activation of few more OSSv4 ioctls() (see [1] above).
* opt_snd.h for possible compile time configuration:
(mostly for debugging purposes, don't try these at home)
SND_DEBUG
SND_DIAGNOSTIC
SND_FEEDER_MULTIFORMAT
SND_FEEDER_FULL_MULTIFORMAT
SND_FEEDER_RATE_HP
SND_PCM_64
SND_OLDSTEREO
Manual page updates are on the way.
Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many
unsung / unnamed heroes.
2009-06-07 19:12:08 +00:00
|
|
|
if ((sc->irq == NULL) || bus_setup_intr(dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV,
|
|
|
|
#if __FreeBSD_version >= 700031
|
|
|
|
NULL,
|
|
|
|
#endif
|
|
|
|
emu_intr, sc, &sc->ih)) {
|
2006-07-15 19:36:28 +00:00
|
|
|
device_printf(dev, "unable to map interrupt\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
if (emu_rm_init(sc) != 0) {
|
|
|
|
device_printf(dev, "unable to create resource manager\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
if (sc->is_cardbus)
|
|
|
|
if (emu_cardbus_init(sc) != 0) {
|
|
|
|
device_printf(dev, "unable to initialize CardBus interface\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
if (emu_init(sc) != 0) {
|
2006-07-15 19:36:28 +00:00
|
|
|
device_printf(dev, "unable to initialize the card\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
if (emu10kx_dev_init(sc) != 0) {
|
2006-07-15 19:36:28 +00:00
|
|
|
device_printf(dev, "unable to create control device\n");
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
snprintf(status, 255, "rev %d at io 0x%lx irq %ld", sc->rev, rman_get_start(sc->reg), rman_get_start(sc->irq));
|
|
|
|
|
|
|
|
/* Voices */
|
|
|
|
for (i = 0; i < NUM_G; i++) {
|
|
|
|
sc->voice[i].vnum = i;
|
|
|
|
sc->voice[i].slave = NULL;
|
|
|
|
sc->voice[i].busy = 0;
|
|
|
|
sc->voice[i].ismaster = 0;
|
|
|
|
sc->voice[i].running = 0;
|
|
|
|
sc->voice[i].b16 = 0;
|
|
|
|
sc->voice[i].stereo = 0;
|
|
|
|
sc->voice[i].speed = 0;
|
|
|
|
sc->voice[i].start = 0;
|
|
|
|
sc->voice[i].end = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* PCM Audio */
|
2007-09-12 07:43:43 +00:00
|
|
|
for (i = 0; i < RT_COUNT; i++)
|
|
|
|
sc->pcm[i] = NULL;
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
/* FRONT */
|
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (pcminfo == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo->card = sc;
|
|
|
|
pcminfo->route = RT_FRONT;
|
|
|
|
|
|
|
|
func->func = SCF_PCM;
|
|
|
|
func->varinfo = pcminfo;
|
|
|
|
sc->pcm[RT_FRONT] = device_add_child(dev, "pcm", -1);
|
|
|
|
device_set_ivars(sc->pcm[RT_FRONT], func);
|
2007-01-06 18:59:35 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (!(sc->mch_disabled)) {
|
|
|
|
/* REAR */
|
2006-07-15 19:36:28 +00:00
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (pcminfo == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo->card = sc;
|
2007-09-12 07:43:43 +00:00
|
|
|
pcminfo->route = RT_REAR;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
func->func = SCF_PCM;
|
|
|
|
func->varinfo = pcminfo;
|
2007-09-12 07:43:43 +00:00
|
|
|
sc->pcm[RT_REAR] = device_add_child(dev, "pcm", -1);
|
|
|
|
device_set_ivars(sc->pcm[RT_REAR], func);
|
|
|
|
if (sc->has_51) {
|
|
|
|
/* CENTER */
|
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (pcminfo == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo->card = sc;
|
|
|
|
pcminfo->route = RT_CENTER;
|
|
|
|
|
|
|
|
func->func = SCF_PCM;
|
|
|
|
func->varinfo = pcminfo;
|
|
|
|
sc->pcm[RT_CENTER] = device_add_child(dev, "pcm", -1);
|
|
|
|
device_set_ivars(sc->pcm[RT_CENTER], func);
|
|
|
|
/* SUB */
|
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (pcminfo == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo->card = sc;
|
|
|
|
pcminfo->route = RT_SUB;
|
|
|
|
|
|
|
|
func->func = SCF_PCM;
|
|
|
|
func->varinfo = pcminfo;
|
|
|
|
sc->pcm[RT_SUB] = device_add_child(dev, "pcm", -1);
|
|
|
|
device_set_ivars(sc->pcm[RT_SUB], func);
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->has_71) {
|
|
|
|
/* SIDE */
|
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (pcminfo == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo->card = sc;
|
|
|
|
pcminfo->route = RT_SIDE;
|
|
|
|
|
|
|
|
func->func = SCF_PCM;
|
|
|
|
func->varinfo = pcminfo;
|
|
|
|
sc->pcm[RT_SIDE] = device_add_child(dev, "pcm", -1);
|
|
|
|
device_set_ivars(sc->pcm[RT_SIDE], func);
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
} /* mch_disabled */
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->mch_rec) {
|
2006-07-15 19:36:28 +00:00
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (pcminfo == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
pcminfo->card = sc;
|
2007-09-12 07:43:43 +00:00
|
|
|
pcminfo->route = RT_MCHRECORD;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
func->func = SCF_PCM;
|
|
|
|
func->varinfo = pcminfo;
|
2007-09-12 07:43:43 +00:00
|
|
|
sc->pcm[RT_MCHRECORD] = device_add_child(dev, "pcm", -1);
|
|
|
|
device_set_ivars(sc->pcm[RT_MCHRECORD], func);
|
|
|
|
} /*mch_rec */
|
2007-01-06 18:59:35 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
for (i = 0; i < 2; i++)
|
|
|
|
sc->midi[i] = NULL;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
/* MIDI has some memory mangament and (possible) locking problems */
|
|
|
|
#if 0
|
2006-07-15 19:36:28 +00:00
|
|
|
/* Midi Interface 1: Live!, Audigy, Audigy 2 */
|
|
|
|
if ((sc->is_emu10k1) || (sc->is_emu10k2) || (sc->is_ca0102)) {
|
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (midiinfo == NULL) {
|
2006-07-15 19:36:28 +00:00
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo->card = sc;
|
2006-07-15 19:36:28 +00:00
|
|
|
if (sc->is_emu10k2 || (sc->is_ca0102)) {
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo->port = A_MUDATA1;
|
|
|
|
midiinfo->portnr = 1;
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
if (sc->is_emu10k1) {
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo->port = MUDATA;
|
|
|
|
midiinfo->portnr = 1;
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
func->func = SCF_MIDI;
|
2007-09-12 07:43:43 +00:00
|
|
|
func->varinfo = midiinfo;
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->midi[0] = device_add_child(dev, "midi", -1);
|
|
|
|
device_set_ivars(sc->midi[0], func);
|
|
|
|
}
|
|
|
|
/* Midi Interface 2: Audigy, Audigy 2 (on AudigyDrive) */
|
|
|
|
if (sc->is_emu10k2 || (sc->is_ca0102)) {
|
|
|
|
func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (func == NULL) {
|
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
if (midiinfo == NULL) {
|
2006-07-15 19:36:28 +00:00
|
|
|
error = ENOMEM;
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo->card = sc;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
midiinfo->port = A_MUDATA2;
|
|
|
|
midiinfo->portnr = 2;
|
2006-07-15 19:36:28 +00:00
|
|
|
|
|
|
|
func->func = SCF_MIDI;
|
2007-09-12 07:43:43 +00:00
|
|
|
func->varinfo = midiinfo;
|
2006-07-15 19:36:28 +00:00
|
|
|
sc->midi[1] = device_add_child(dev, "midi", -1);
|
|
|
|
device_set_ivars(sc->midi[1], func);
|
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
#endif
|
2006-07-15 19:36:28 +00:00
|
|
|
return (bus_generic_attach(dev));
|
|
|
|
|
|
|
|
bad:
|
|
|
|
/* XXX can we just call emu_pci_detach here? */
|
|
|
|
if (sc->cdev)
|
|
|
|
emu10kx_dev_uninit(sc);
|
|
|
|
if (sc->rm != NULL)
|
|
|
|
emu_rm_uninit(sc);
|
|
|
|
if (sc->reg)
|
|
|
|
bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg);
|
|
|
|
if (sc->ih)
|
|
|
|
bus_teardown_intr(dev, sc->irq, sc->ih);
|
|
|
|
if (sc->irq)
|
|
|
|
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
|
|
|
|
mtx_destroy(&sc->rw);
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_destroy(&sc->lock);
|
2006-07-15 19:36:28 +00:00
|
|
|
return (error);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_pci_detach(device_t dev)
|
|
|
|
{
|
|
|
|
struct emu_sc_info *sc;
|
2007-09-12 07:43:43 +00:00
|
|
|
struct sndcard_func *func;
|
2006-07-15 19:36:28 +00:00
|
|
|
int devcount, i;
|
|
|
|
device_t *childlist;
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
sc = device_get_softc(dev);
|
2007-09-12 07:43:43 +00:00
|
|
|
|
2007-01-06 18:59:35 +00:00
|
|
|
for (i = 0; i < RT_COUNT; i++) {
|
2007-09-12 07:43:43 +00:00
|
|
|
if (sc->pcm[i] != NULL) {
|
|
|
|
func = device_get_ivars(sc->pcm[i]);
|
|
|
|
if (func != NULL && func->func == SCF_PCM) {
|
|
|
|
device_set_ivars(sc->pcm[i], NULL);
|
|
|
|
free(func->varinfo, M_DEVBUF);
|
|
|
|
free(func, M_DEVBUF);
|
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
r = device_delete_child(dev, sc->pcm[i]);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (r) return (r);
|
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
if (sc->midi[0] != NULL) {
|
|
|
|
func = device_get_ivars(sc->midi[0]);
|
|
|
|
if (func != NULL && func->func == SCF_MIDI) {
|
|
|
|
device_set_ivars(sc->midi[0], NULL);
|
|
|
|
free(func->varinfo, M_DEVBUF);
|
|
|
|
free(func, M_DEVBUF);
|
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
r = device_delete_child(dev, sc->midi[0]);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (r) return (r);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sc->midi[1] != NULL) {
|
|
|
|
func = device_get_ivars(sc->midi[1]);
|
|
|
|
if (func != NULL && func->func == SCF_MIDI) {
|
|
|
|
device_set_ivars(sc->midi[1], NULL);
|
|
|
|
free(func->varinfo, M_DEVBUF);
|
|
|
|
free(func, M_DEVBUF);
|
|
|
|
}
|
2006-07-15 19:36:28 +00:00
|
|
|
r = device_delete_child(dev, sc->midi[1]);
|
2007-09-12 07:43:43 +00:00
|
|
|
if (r) return (r);
|
2006-07-15 19:36:28 +00:00
|
|
|
}
|
|
|
|
|
2007-09-12 07:43:43 +00:00
|
|
|
if (device_get_children(dev, &childlist, &devcount) == 0)
|
|
|
|
for (i = 0; i < devcount - 1; i++) {
|
|
|
|
device_printf(dev, "removing stale child %d (unit %d)\n", i, device_get_unit(childlist[i]));
|
|
|
|
func = device_get_ivars(childlist[i]);
|
|
|
|
if (func != NULL && (func->func == SCF_MIDI || func->func == SCF_PCM)) {
|
|
|
|
device_set_ivars(childlist[i], NULL);
|
|
|
|
free(func->varinfo, M_DEVBUF);
|
|
|
|
free(func, M_DEVBUF);
|
|
|
|
}
|
|
|
|
device_delete_child(dev, childlist[i]);
|
|
|
|
}
|
|
|
|
if (childlist != NULL)
|
|
|
|
free(childlist, M_TEMP);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
r = emu10kx_dev_uninit(sc);
|
|
|
|
if (r)
|
|
|
|
return (r);
|
2007-09-12 07:43:43 +00:00
|
|
|
|
|
|
|
/* shutdown chip */
|
|
|
|
emu_uninit(sc);
|
2006-07-15 19:36:28 +00:00
|
|
|
emu_rm_uninit(sc);
|
2007-05-04 12:38:46 +00:00
|
|
|
|
|
|
|
if (sc->mem.dmat)
|
|
|
|
bus_dma_tag_destroy(sc->mem.dmat);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
if (sc->reg)
|
|
|
|
bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg);
|
|
|
|
bus_teardown_intr(dev, sc->irq, sc->ih);
|
|
|
|
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
|
|
|
|
mtx_destroy(&sc->rw);
|
2007-09-12 07:43:43 +00:00
|
|
|
mtx_destroy(&sc->lock);
|
|
|
|
|
2006-07-15 19:36:28 +00:00
|
|
|
return (bus_generic_detach(dev));
|
|
|
|
}
|
|
|
|
/* add suspend, resume */
|
|
|
|
static device_method_t emu_methods[] = {
|
|
|
|
/* Device interface */
|
|
|
|
DEVMETHOD(device_probe, emu_pci_probe),
|
|
|
|
DEVMETHOD(device_attach, emu_pci_attach),
|
|
|
|
DEVMETHOD(device_detach, emu_pci_detach),
|
|
|
|
/* Bus methods */
|
|
|
|
DEVMETHOD(bus_read_ivar, emu_read_ivar),
|
|
|
|
DEVMETHOD(bus_write_ivar, emu_write_ivar),
|
|
|
|
|
|
|
|
{0, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static driver_t emu_driver = {
|
|
|
|
"emu10kx",
|
|
|
|
emu_methods,
|
|
|
|
sizeof(struct emu_sc_info),
|
|
|
|
NULL,
|
|
|
|
0,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
emu_modevent(module_t mod __unused, int cmd, void *data __unused)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case MOD_LOAD:
|
|
|
|
break; /* Success */
|
|
|
|
|
|
|
|
case MOD_UNLOAD:
|
|
|
|
case MOD_SHUTDOWN:
|
|
|
|
|
|
|
|
/* XXX Should we check state of pcm & midi subdevices here? */
|
|
|
|
|
|
|
|
break; /* Success */
|
|
|
|
|
|
|
|
default:
|
|
|
|
err = EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (err);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static devclass_t emu_devclass;
|
|
|
|
|
|
|
|
DRIVER_MODULE(snd_emu10kx, pci, emu_driver, emu_devclass, emu_modevent, NULL);
|
|
|
|
MODULE_VERSION(snd_emu10kx, SND_EMU10KX_PREFVER);
|