freebsd-nq/sys/dev/sound/pci/emu10kx.c
Ariff Abdullah 90da2b2859 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

3568 lines
97 KiB
C

/*-
* Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
* Copyright (c) 2003-2007 Yuriy Tsibizov <yuriy.tsibizov@gfk.ru>
* 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>
#include <sys/kdb.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <machine/clock.h> /* for DELAY */
#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#endif
#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)
#define DSP_ACCUM DSP_CONST(0x16)
#define DSP_CCR DSP_CONST(0x17)
/* 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
/* Live! 5.1 Digital, non-standart 5.1 (center & sub) outputs */
#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
#define CDSPDIFMUTE 0
#define ANALOGMUTE 1
#define NUM_MUTE 2
#define EMU_MAX_GPR 512
#define EMU_MAX_IRQ_CONSUMERS 32
struct emu_voice {
int vnum;
unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1;
int speed;
int start;
int end;
int vol;
uint32_t buf;
void *vbuf;
struct emu_voice *slave;
uint32_t sa;
uint32_t ea;
uint32_t routing[8];
uint32_t amounts[8];
};
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 {
uint8_t bmap[EMU_MAXPAGES / 8];
uint32_t *ptb_pages;
void *silent_page;
bus_addr_t silent_page_addr;
bus_addr_t ptb_pages_addr;
bus_dma_tag_t dmat;
struct emu_sc_info *card;
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;
device_t pcm[RT_COUNT];
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 */
unsigned int mode; /* analog / digital */
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,
enable_ir:1,
broken_digital:1, is_cardbus:1;
signed int mch_disabled, mch_rec, dbg_level;
signed int num_inputs;
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];
int dummy_gpr;
int mute_gpr[NUM_MUTE];
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);
static unsigned int emu_getcard(device_t dev);
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);
static int emu_init(struct emu_sc_info *sc);
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);
#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)
/* 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[] = {
{0xffff, 0xffff, 0xffff, 0xffff, "BADCRD", "Not a compatible card", 0},
/* 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},
{0x1102, 0x0002, 0x1102, 0x8062, "CT4830", "SBLive! 1024", HAS_AC97 | HAS_51 | IS_EMU10K1},
{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},
/* 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},
/* (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.
*/
static unsigned int
emu_getcard(device_t dev)
{
uint16_t device;
uint16_t subdevice;
int n_cards;
unsigned int thiscard;
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);
thiscard = 0;
for (i = 1; i < n_cards; i++) {
if (device == emu_cards[i].device) {
if (subdevice == emu_cards[i].subdevice) {
thiscard = i;
break;
}
if (0x0000 == emu_cards[i].subdevice) {
thiscard = i;
/*
* don't break, we can get more specific card
* later in the list.
*/
}
}
}
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) {
thiscard = 0;
break;
}
if (0x0000 == emu_bad_cards[i].subdevice) {
thiscard = 0;
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);
EMU_RWLOCK();
emu_wr_nolock(sc, PTR, ptr, 4);
val = emu_rd_nolock(sc, DATA, 4);
EMU_RWUNLOCK();
/*
* 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;
ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK);
EMU_RWLOCK();
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);
EMU_RWUNLOCK();
}
/*
* 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;
/* XXX separate lock? */
EMU_RWLOCK();
emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4);
val = emu_rd_nolock(sc, DATA2, 4);
EMU_RWUNLOCK();
return (val);
}
void
emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data)
{
EMU_RWLOCK();
emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4);
emu_wr_nolock(sc, DATA2, data, 4);
EMU_RWUNLOCK();
}
/*
* 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;
/*
* 0x38 is IPE3 (CD S/PDIF interrupt pending register) on CA0102. Seems
* to be some reg/value accessible kind of config register on CardBus
* CA0108, with value(?) in top 16 bit, address(?) in low 16
*/
val = emu_rd_nolock(sc, 0x38, 4);
emu_wr_nolock(sc, 0x38, data, 4);
val = emu_rd_nolock(sc, 0x38, 4);
}
/*
* Direct hardware register access
* Assume that it is never used to access PTR-based registers and can run unlocked.
*/
void
emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size)
{
KASSERT(regno != PTR, ("emu_wr: attempt to write to PTR"));
KASSERT(regno != PTR2, ("emu_wr: attempt to write to PTR2"));
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;
KASSERT(regno != DATA, ("emu_rd: attempt to read DATA"));
KASSERT(regno != DATA2, ("emu_rd: attempt to read DATA2"));
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;
mtx_lock(&sc->lock);
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
if (sc->timer[i] == 0) {
sc->timer[i] = -1; /* disable it */
timer = i;
mtx_unlock(&sc->lock);
return (timer);
}
mtx_unlock(&sc->lock);
return (-1);
}
int
emu_timer_set(struct emu_sc_info *sc, int timer, int delay)
{
int i;
if (timer < 0)
return (-1);
RANGE(delay, 16, 1024);
RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1);
mtx_lock(&sc->lock);
sc->timer[timer] = delay;
for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++)
if (sc->timerinterval > sc->timer[i])
sc->timerinterval = sc->timer[i];
/* XXX */
emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2);
mtx_unlock(&sc->lock);
return (timer);
}
int
emu_timer_enable(struct emu_sc_info *sc, int timer, int go)
{
uint32_t x;
int ena_int;
int i;
if (timer < 0)
return (-1);
RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1);
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)
{
if (timer < 0)
return (-1);
RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1);
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);
if (sc->dbg_level > 1)
device_printf(sc->dev, "ihandle %d registered\n", i);
return (i);
}
mtx_unlock(&sc->lock);
if (sc->dbg_level > 1)
device_printf(sc->dev, "ihandle not registered\n");
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;
/* other interrupt handlers may use this INTE value */
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);
}
}
if (sc->dbg_level > 1)
if (stat & (~ack))
device_printf(sc->dev, "Unhandled interrupt: %08x\n", stat & (~ack));
}
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);
if (sc->dbg_level > 1)
device_printf(sc->dev, "IPR2: %08x\n", stat);
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);
if (sc->dbg_level > 1)
device_printf(sc->dev, "IPR3: %08x\n", stat);
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;
int error;
*addr = 0;
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);
return (NULL);
}
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);
return (NULL);
}
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++;
if (blksz > EMU_MAX_BUFSZ / EMUPAGESIZE) {
if (mem->card->dbg_level > 2)
device_printf(mem->card->dev, "emu_memalloc: memory request tool large\n");
return (NULL);
}
/* find a free block in the bitmap */
found = 0;
start = 1;
while (!found && start + blksz < EMU_MAXPAGES) {
found = 1;
for (idx = start; idx < start + blksz; idx++)
if (mem->bmap[idx >> 3] & (1 << (idx & 7)))
found = 0;
if (!found)
start++;
}
if (!found) {
if (mem->card->dbg_level > 2)
device_printf(mem->card->dev, "emu_memalloc: no free space in bitmap\n");
return (NULL);
}
blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT);
if (blk == NULL) {
if (mem->card->dbg_level > 2)
device_printf(mem->card->dev, "emu_memalloc: buffer allocation failed\n");
return (NULL);
}
bzero(blk, sizeof(*blk));
membuf = emu_malloc(mem, sz, &blk->buf_addr);
*addr = blk->buf_addr;
if (membuf == NULL) {
if (mem->card->dbg_level > 2)
device_printf(mem->card->dev, "emu_memalloc: can't setup HW memory\n");
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;
/*
* XXX What we should do with mono channels?
* See -pcm.c emupchan_init for other side of
* this problem
*/
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");
if (vbuf == NULL) {
if(sc->dbg_level > 2)
device_printf(sc->dev, "emu_memalloc returns NULL in enu_vinit\n");
return (ENOMEM);
}
if (b != NULL)
sndbuf_setup(b, vbuf, sz);
m->start = emu_memstart(&sc->mem, vbuf) * EMUPAGESIZE;
if (m->start < 0) {
if(sc->dbg_level > 2)
device_printf(sc->dev, "emu_memstart returns (-1) in enu_vinit\n");
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;
v->stereo = (AFMT_CHANNEL(fmt) > 1) ? 1 : 0;
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++) {
v->routing[i] = rt->routing_left[i];
v->amounts[i] = rt->amounts_left[i];
}
if ((v->stereo) && (v->ismaster == 0))
for (i = 0; i < 8; i++) {
v->routing[i] = rt->routing_right[i];
v->amounts[i] = rt->amounts_right[i];
}
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;
uint32_t start, val, silent_page;
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;
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));
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);
err = sysctl_handle_int(oidp, &new_vol, 0, req);
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;
char sysctl_name[32];
volgpr = emu_rm_gpr_alloc(sc->rm, 1);
emumix_set_fxvol(sc, volgpr, defvolume);
/*
* Mixer controls with NULL mix_name are handled
* by AC97 emulation code or PCM mixer.
*/
if (mix_name != NULL) {
/*
* Temporary sysctls should start with underscore,
* see freebsd-current mailing list, emu10kx driver
* discussion around 2006-05-24.
*/
snprintf(sysctl_name, 32, "_%s", mix_name);
SYSCTL_ADD_PROC(sc->ctx,
SYSCTL_CHILDREN(sc->root),
OID_AUTO, sysctl_name,
CTLTYPE_INT | CTLFLAG_RW, sc, mix_id,
sysctl_emu_mixer_control, "I", "");
}
return (volgpr);
}
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
* 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 */
#define EFX_OUTPUT(TITLE, OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR, DEF) do { \
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 */
#define EFX_OUTPUTD(OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR) do { \
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)
/* 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)
#define EFX_COPY(TO, FROM) do { \
emu_addefxop(sc, ACC3, \
TO, \
DSP_CONST(0), \
DSP_CONST(0), \
FROM, \
&pc); \
} while (0)
static void
emu_initefx(struct emu_sc_info *sc)
{
unsigned int i;
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;
for (i = 0; i < sc->code_size; i++) {
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);
}
}
/* 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);
pc = 0;
/*
* 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 */
for (i = 0; i < 16 ; i++) {
emu_addefxop(sc, ACC3, OUTP(i), DSP_CONST(0), DSP_CONST(0), DSP_CONST(0), &pc);
}
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);
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);
/* 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 */
/* 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);
/* in4, LineIn */
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);
/* in5, on-card S/PDIF */
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);
/* in6, Line2 on Live!Drive */
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 */
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);
/* 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 */
EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_HEADPHONE_L);
EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_HEADPHONE_R);
/* 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);
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);
}
} 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% */
#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);
}
#endif
} /* !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 */
} 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);
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);
/* in0, from AC97 codec output */
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);
/* in1, from CD S/PDIF */
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);
/* in2, optical & coax S/PDIF on AudigyDrive*/
/* XXX Should be muted when GPRSCS valid stream == 0 */
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);
}
/* in4, LineIn 2 on AudigyDrive */
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);
/* in5, on-card S/PDIF */
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);
/* in6, AUX2 on AudigyDrive */
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);
}
/* 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);
EFX_OUTPUT(NULL, C_REC_R, M_MASTER_REC_R, A_OUT_ADC_REC_R, 100);
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);
#if 0
/*
* 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);
#endif
/* 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);
#if 0
/*
* 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);
#endif
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));
#endif
} /* mch_rec */
}
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");
for (i = 0; i < RT_COUNT; i++)
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");
}
sbuf_printf(s, "Card is in %s mode\n", (sc->mode == MODE_ANALOG) ? "analog" : "digital");
sbuf_finish(s);
return (sbuf_len(s));
}
/* INIT & UNINIT */
static int
emu10kx_dev_init(struct emu_sc_info *sc)
{
int unit;
mtx_init(&sc->emu10kx_lock, device_get_nameunit(sc->dev), "kxdevlock", 0);
unit = device_get_unit(sc->dev);
sc->cdev = make_dev(&emu10kx_cdevsw, PCMMINOR(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit);
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;
mtx_init(&(rm->gpr_lock), device_get_nameunit(sc->dev), "gpr alloc", MTX_DEF);
rm->num_gprs = (maxcount < EMU_MAX_GPR ? maxcount : EMU_MAX_GPR);
for (i = 0; i < rm->num_gprs; i++)
rm->allocmap[i] = 0;
/* pre-allocate gpr[0] */
rm->allocmap[0] = 1;
rm->last_free_gpr = 1;
return (0);
}
int
emu_rm_uninit(struct emu_sc_info *sc)
{
int i;
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));
}
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) {
device_printf(sc->dev, "Digital mode is reported as broken on this card.\n");
}
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))
/*
* 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;
if (sc->is_ca0108)
a_iocfg |= 0x20; /* XXX */
/* Mute analog center & subwoofer before mode change */
if (mode == MODE_DIGITAL)
emumix_set_gpr(sc, sc->mute_gpr[ANALOGMUTE], 1);
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);
}
/* Unmute if we have changed mode to analog. */
if (mode == MODE_ANALOG)
emumix_set_gpr(sc, sc->mute_gpr[ANALOGMUTE], 0);
sc->mode = mode;
}
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)
{
if (sc->dbg_level > 1)
if (gpr == 0) {
device_printf(sc->dev, "Zero gpr write access\n");
#ifdef KDB
kdb_backtrace();
#endif
return;
}
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
emu_init(struct emu_sc_info *sc)
{
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? */
if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(sc->dev),
/* alignment */ 2, /* boundary */ 0,
/* lowaddr */ 1 << 31, /* can only access 0-2gb */
/* highaddr */ BUS_SPACE_MAXADDR,
/* filter */ NULL, /* filterarg */ NULL,
/* maxsize */ EMU_MAX_BUFSZ, /* nsegments */ 1, /* maxsegz */ 0x3ffff,
/* 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);
}
sc->mem.card = sc;
SLIST_INIT(&sc->mem.blocks);
sc->mem.ptb_pages = emu_malloc(&sc->mem, EMU_MAXPAGES * sizeof(uint32_t), &sc->mem.ptb_pages_addr);
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;
for (i = 0; i < EMU_MAXPAGES; i++)
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);
if (func==NULL)
return (ENOMEM);
if (sc == NULL)
return (ENOMEM);
switch (ivar_index) {
case EMU_VAR_FUNC:
*result = func->func;
break;
case EMU_VAR_ROUTE:
if (func->varinfo == NULL)
return (ENOMEM);
*result = ((struct emu_pcminfo *)func->varinfo)->route;
break;
case EMU_VAR_ISEMU10K1:
*result = sc->is_emu10k1;
break;
case EMU_VAR_MCH_DISABLED:
*result = sc->mch_disabled;
break;
case EMU_VAR_MCH_REC:
*result = sc->mch_rec;
break;
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;
unsigned int thiscard = 0;
uint16_t vendor;
vendor = pci_read_config(dev, PCIR_DEVVENDOR, /* bytes */ 2);
if (vendor != 0x1102)
return (ENXIO); /* Not Creative */
thiscard = emu_getcard(dev);
if (thiscard == 0)
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));
sbuf_delete(s);
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;
#if 0
struct emu_midiinfo *midiinfo;
#endif
uint32_t data;
int i;
int device_flags;
char status[255];
int error = ENXIO;
int unit;
sc = device_get_softc(dev);
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");
/* Fill in the softc. */
mtx_init(&sc->lock, device_get_nameunit(dev), "bridge conf", MTX_DEF);
mtx_init(&sc->rw, device_get_nameunit(dev), "exclusive io", MTX_DEF);
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;
/* DSP map */
/* sc->fx_base = 0x0 */
sc->input_base = 0x40;
/* sc->p16vinput_base = 0x50; */
sc->output_base = 0x60;
sc->efxc_base = 0x80;
/* sc->output32h_base = 0xa0; */
/* sc->output32l_base = 0xb0; */
sc->dsp_zero = 0xc0;
/* 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 */
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) {
sc->has_51 = 0; /* We don't support 5.1 sound on SB Live! 5.1 */
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;
/*
* XXX 5.1 Analog outputs are inside efxc address space!
* They use ouput+0x11/+0x12 (=efxc+1/+2).
* Don't use this efx registers for recording on SB Live! 5.1!
*/
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);
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)) {
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;
}
if (emu_init(sc) != 0) {
device_printf(dev, "unable to initialize the card\n");
goto bad;
}
if (emu10kx_dev_init(sc) != 0) {
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 */
for (i = 0; i < RT_COUNT; i++)
sc->pcm[i] = NULL;
/* 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);
if (!(sc->mch_disabled)) {
/* REAR */
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_REAR;
func->func = SCF_PCM;
func->varinfo = pcminfo;
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);
}
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);
}
} /* mch_disabled */
if (sc->mch_rec) {
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_MCHRECORD;
func->func = SCF_PCM;
func->varinfo = pcminfo;
sc->pcm[RT_MCHRECORD] = device_add_child(dev, "pcm", -1);
device_set_ivars(sc->pcm[RT_MCHRECORD], func);
} /*mch_rec */
for (i = 0; i < 2; i++)
sc->midi[i] = NULL;
/* MIDI has some memory mangament and (possible) locking problems */
#if 0
/* 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;
}
midiinfo = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
if (midiinfo == NULL) {
error = ENOMEM;
goto bad;
}
midiinfo->card = sc;
if (sc->is_emu10k2 || (sc->is_ca0102)) {
midiinfo->port = A_MUDATA1;
midiinfo->portnr = 1;
}
if (sc->is_emu10k1) {
midiinfo->port = MUDATA;
midiinfo->portnr = 1;
}
func->func = SCF_MIDI;
func->varinfo = midiinfo;
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;
}
midiinfo = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
if (midiinfo == NULL) {
error = ENOMEM;
goto bad;
}
midiinfo->card = sc;
midiinfo->port = A_MUDATA2;
midiinfo->portnr = 2;
func->func = SCF_MIDI;
func->varinfo = midiinfo;
sc->midi[1] = device_add_child(dev, "midi", -1);
device_set_ivars(sc->midi[1], func);
}
#endif
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);
mtx_destroy(&sc->lock);
return (error);
}
static int
emu_pci_detach(device_t dev)
{
struct emu_sc_info *sc;
struct sndcard_func *func;
int devcount, i;
device_t *childlist;
int r = 0;
sc = device_get_softc(dev);
for (i = 0; i < RT_COUNT; i++) {
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);
}
r = device_delete_child(dev, sc->pcm[i]);
if (r) return (r);
}
}
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);
}
r = device_delete_child(dev, sc->midi[0]);
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);
}
r = device_delete_child(dev, sc->midi[1]);
if (r) return (r);
}
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);
r = emu10kx_dev_uninit(sc);
if (r)
return (r);
/* shutdown chip */
emu_uninit(sc);
emu_rm_uninit(sc);
if (sc->mem.dmat)
bus_dma_tag_destroy(sc->mem.dmat);
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);
mtx_destroy(&sc->lock);
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);