70e0bbedef
This uses the emuxkireg.h already used in the emu10k1 snd driver. Special thanks go to Alexander Motin as he was able to find some errors and reverse engineer some wrong values in the emuxkireg header. The emu10kx driver is now free from the GPL. PR: 153901 Tested by: mav, joel Approved by: jhb (mentor) MFC after: 2 weeks
3579 lines
99 KiB
C
3579 lines
99 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/emuxkireg.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
|
|
|
|
/* Audigy 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
|
|
|
|
#define EMU_DATA2 0x24
|
|
#define EMU_IPR2 0x28
|
|
#define EMU_INTE2 0x2c
|
|
#define EMU_IPR3 0x38
|
|
#define EMU_INTE3 0x3c
|
|
|
|
#define EMU_A2_SRCSel 0x60
|
|
#define EMU_A2_SRCMULTI_ENABLE 0x6e
|
|
|
|
#define EMU_A_I2S_CAPTURE_96000 0x00000400
|
|
|
|
#define EMU_A2_MIXER_I2S_ENABLE 0x7B
|
|
#define EMU_A2_MIXER_SPDIF_ENABLE 0x7A
|
|
|
|
#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;
|
|
}
|
|
}
|
|
/*
|
|
* EMU_PTR / EMU_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 & EMU_PTR_CHNO_MASK);
|
|
|
|
EMU_RWLOCK();
|
|
emu_wr_nolock(sc, EMU_PTR, ptr, 4);
|
|
val = emu_rd_nolock(sc, EMU_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 & EMU_PTR_CHNO_MASK);
|
|
|
|
EMU_RWLOCK();
|
|
emu_wr_nolock(sc, EMU_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, EMU_DATA, 4) & ~mask;
|
|
}
|
|
emu_wr_nolock(sc, EMU_DATA, data, 4);
|
|
EMU_RWUNLOCK();
|
|
}
|
|
/*
|
|
* EMU_A2_PTR / EMU_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, EMU_A2_PTR, (reg << 16) | chn, 4);
|
|
val = emu_rd_nolock(sc, EMU_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, EMU_A2_PTR, (reg << 16) | chn, 4);
|
|
emu_wr_nolock(sc, EMU_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 EMU_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 != EMU_PTR, ("emu_wr: attempt to write to EMU_PTR"));
|
|
KASSERT(regno != EMU_A2_PTR, ("emu_wr: attempt to write to EMU_A2_PTR"));
|
|
|
|
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 != EMU_DATA, ("emu_rd: attempt to read DATA"));
|
|
KASSERT(regno != EMU_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, EMU_A_IOCFG, 2);
|
|
emu_wr_nolock(sc, EMU_A_IOCFG, iocfg | EMU_A_IOCFG_GPOUT2, 2);
|
|
DELAY(500);
|
|
emu_wr_nolock(sc, EMU_A_IOCFG, iocfg | EMU_A_IOCFG_GPOUT1 | EMU_A_IOCFG_GPOUT2, 2);
|
|
DELAY(500);
|
|
emu_wr_nolock(sc, EMU_A_IOCFG, iocfg | EMU_A_IOCFG_GPOUT1, 2);
|
|
DELAY(100);
|
|
emu_wr_nolock(sc, EMU_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, EMU_HCFG, 4);
|
|
emu_wr_nolock(sc, EMU_HCFG, iocfg | EMU_HCFG_GPOUT2, 4);
|
|
DELAY(500);
|
|
emu_wr_nolock(sc, EMU_HCFG, iocfg | EMU_HCFG_GPOUT1 | EMU_HCFG_GPOUT2, 4);
|
|
DELAY(100);
|
|
emu_wr_nolock(sc, EMU_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, EMU_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, EMU_TIMER, sc->timerinterval & 0x03ff, 2);
|
|
|
|
if (ena_int == 1) {
|
|
x = emu_rd(sc, EMU_INTE, 4);
|
|
x |= EMU_INTE_INTERTIMERENB;
|
|
emu_wr(sc, EMU_INTE, x, 4);
|
|
} else {
|
|
x = emu_rd(sc, EMU_INTE, 4);
|
|
x &= ~EMU_INTE_INTERTIMERENB;
|
|
emu_wr(sc, EMU_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, EMU_INTE, 4);
|
|
x |= inte_mask;
|
|
emu_wr(sc, EMU_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, EMU_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 EMU_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, EMU_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, EMU_IPR, 4);
|
|
ack = 0;
|
|
if (stat == 0)
|
|
break;
|
|
emu_wr(sc, EMU_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, EMU_IPR2, 4);
|
|
ack = 0;
|
|
if (stat == 0)
|
|
break;
|
|
emu_wr(sc, EMU_IPR2, stat, 4);
|
|
if (sc->dbg_level > 1)
|
|
device_printf(sc->dev, "EMU_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, EMU_IPR3, 4);
|
|
ack = 0;
|
|
if (stat == 0)
|
|
break;
|
|
emu_wr(sc, EMU_IPR3, stat, 4);
|
|
if (sc->dbg_level > 1)
|
|
device_printf(sc->dev, "EMU_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, EMU_CHAN_CPF, EMU_CHAN_CPF_STEREO_MASK);
|
|
} else {
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_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, EMU_CHAN_FXRT, ((v->routing[3] << 12) |
|
|
(v->routing[2] << 8) |
|
|
(v->routing[1] << 4) |
|
|
(v->routing[0] << 0)) << 16);
|
|
} else {
|
|
emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT1, (v->routing[3] << 24) |
|
|
(v->routing[2] << 16) |
|
|
(v->routing[1] << 8) |
|
|
(v->routing[0] << 0));
|
|
emu_wrptr(sc, v->vnum, EMU_A_CHAN_FXRT2, (v->routing[7] << 24) |
|
|
(v->routing[6] << 16) |
|
|
(v->routing[5] << 8) |
|
|
(v->routing[4] << 0));
|
|
emu_wrptr(sc, v->vnum, EMU_A_CHAN_SENDAMOUNTS, (v->amounts[7] << 24) |
|
|
(v->amounts[6] << 26) |
|
|
(v->amounts[5] << 8) |
|
|
(v->amounts[4] << 0));
|
|
}
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX, (v->amounts[0] << 8) | (v->amounts[1] << 0));
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_DSL, v->ea | (v->amounts[3] << 24));
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_PSST, v->sa | (v->amounts[2] << 24));
|
|
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CCCA, start | (v->b16 ? 0 : EMU_CHAN_CCCA_8BITSELECT));
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_Z1, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_Z2, 0);
|
|
|
|
silent_page = ((uint32_t) (sc->mem.silent_page_addr) << 1) | EMU_CHAN_MAP_PTI_MASK;
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_MAPA, silent_page);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_MAPB, silent_page);
|
|
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, EMU_CHAN_CVCF_CURRFILTER_MASK);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, EMU_CHAN_VTFT_FILTERTARGET_MASK);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDM, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSM, EMU_CHAN_DCYSUSM_DECAYTIME_MASK);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL1, 0x8000);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_LFOVAL2, 0x8000);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_FMMOD, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_TREMFRQ, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_FM2FRQ2, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVAL, 0x8000);
|
|
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_ATKHLDV, EMU_CHAN_ATKHLDV_HOLDTIME_MASK | EMU_CHAN_ATKHLDV_ATTACKTIME_MASK);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_ENVVOL, 0x8000);
|
|
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_FILTERAMOUNT, 0x7f);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_PEFE_PITCHAMOUNT, 0);
|
|
if ((v->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) ? EMU_SOLEH : EMU_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, EMU_CHAN_CD0 + i, sample);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_READADDRESS, cra);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CCR_CACHEINVALIDSIZE, ccis);
|
|
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xff00);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0xffffffff);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0xffffffff);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_DCYSUSV, 0x00007f7f);
|
|
emu_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, EMU_CHAN_PTRX_PITCHTARGET, pitch_target);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, pitch_target);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_IP, initial_pitch);
|
|
} else {
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_PTRX_PITCHTARGET, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CPF_PITCH, 0);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_IFATN, 0xffff);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_VTFT, 0x0000ffff);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_CVCF, 0x0000ffff);
|
|
emu_wrptr(sc, v->vnum, EMU_CHAN_IP, 0);
|
|
emu_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, EMU_CHAN_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, EMU_DBG, EMU_DBG_SINGLE_STEP);
|
|
} else {
|
|
emu_wrptr(sc, 0, EMU_A_DBG, EMU_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, EMU_DBG, 0);
|
|
} else {
|
|
emu_wrptr(sc, 0, EMU_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 = EMU_HCFG_AUDIOENABLE | EMU_HCFG_AUTOMUTE;
|
|
a_iocfg = 0;
|
|
|
|
if (sc->rev >= 6)
|
|
hcfg |= EMU_HCFG_JOYENABLE;
|
|
|
|
if (sc->is_emu10k1)
|
|
hcfg |= EMU_HCFG_LOCKTANKCACHE_MASK;
|
|
else
|
|
hcfg |= EMU_HCFG_CODECFMT_I2S | EMU_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 |= EMU_A_IOCFG_GPOUT1;
|
|
hcfg |= EMU_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 EMU_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 |= EMU_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, EMU_HCFG, hcfg, 4);
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) {
|
|
tmp = emu_rd(sc, EMU_A_IOCFG, 2);
|
|
tmp = a_iocfg;
|
|
emu_wr(sc, EMU_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 = EMU_SPCS_CLKACCY_1000PPM | EMU_SPCS_SAMPLERATE_48 |
|
|
EMU_SPCS_CHANNELNUM_LEFT | EMU_SPCS_SOURCENUM_UNSPEC |
|
|
EMU_SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 |
|
|
EMU_SPCS_EMPHASIS_NONE | EMU_SPCS_COPYRIGHT;
|
|
|
|
mode = SPDIF_MODE_PCM;
|
|
|
|
emu_wrptr(sc, 0, EMU_SPCS0, spcs);
|
|
emu_wrptr(sc, 0, EMU_SPCS1, spcs);
|
|
emu_wrptr(sc, 0, EMU_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 EMU_IPR3 handler.
|
|
* Is it a real init calls, or EMU_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, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4);
|
|
|
|
/* reset recording buffers */
|
|
emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE);
|
|
emu_wrptr(sc, 0, EMU_MICBA, 0);
|
|
emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE);
|
|
emu_wrptr(sc, 0, EMU_FXBA, 0);
|
|
emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE);
|
|
emu_wrptr(sc, 0, EMU_ADCBA, 0);
|
|
|
|
/* disable channel interrupt */
|
|
emu_wr(sc, EMU_INTE, EMU_INTE_INTERTIMERENB | EMU_INTE_SAMPLERATER | EMU_INTE_PCIERRENABLE, 4);
|
|
emu_wrptr(sc, 0, EMU_CLIEL, 0);
|
|
emu_wrptr(sc, 0, EMU_CLIEH, 0);
|
|
emu_wrptr(sc, 0, EMU_SOLEL, 0);
|
|
emu_wrptr(sc, 0, EMU_SOLEH, 0);
|
|
|
|
/* disable P16V and S/PDIF interrupts */
|
|
if ((sc->is_ca0102) || (sc->is_ca0108))
|
|
emu_wr(sc, EMU_INTE2, 0, 4);
|
|
|
|
if (sc->is_ca0102)
|
|
emu_wr(sc, EMU_INTE3, 0, 4);
|
|
|
|
/* init phys inputs and outputs */
|
|
ac97slot = 0;
|
|
if (sc->has_51)
|
|
ac97slot = EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE;
|
|
if (sc->has_71)
|
|
ac97slot = EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE | EMU_AC97SLOT_REAR_LEFT | EMU_AC97SLOT_REAR_RIGHT;
|
|
if (sc->is_emu10k2)
|
|
ac97slot |= 0x40;
|
|
emu_wrptr(sc, 0, EMU_AC97SLOT, ac97slot);
|
|
|
|
if (sc->is_emu10k2) /* XXX for later cards? */
|
|
emu_wrptr(sc, 0, EMU_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 */ (1U << 31) - 1, /* 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, EMU_CHAN_MAPA, tmp | EMU_CHAN_MAP_PTI_MASK);
|
|
emu_wrptr(sc, ch, EMU_CHAN_MAPB, tmp | EMU_CHAN_MAP_PTI_MASK);
|
|
}
|
|
emu_wrptr(sc, 0, EMU_PTB, (sc->mem.ptb_pages_addr));
|
|
emu_wrptr(sc, 0, EMU_TCB, 0); /* taken from original driver */
|
|
emu_wrptr(sc, 0, EMU_TCBS, 0); /* taken from original driver */
|
|
|
|
/* init envelope engine */
|
|
for (ch = 0; ch < NUM_G; ch++) {
|
|
emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_IP, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0xffff);
|
|
emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0xffff);
|
|
emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_CPF, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_CCR, 0);
|
|
|
|
emu_wrptr(sc, ch, EMU_CHAN_PSST, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_DSL, 0x10);
|
|
emu_wrptr(sc, ch, EMU_CHAN_CCCA, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_Z1, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_Z2, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_FXRT, 0xd01c0000);
|
|
|
|
emu_wrptr(sc, ch, EMU_CHAN_ATKHLDM, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_DCYSUSM, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_IFATN, 0xffff);
|
|
emu_wrptr(sc, ch, EMU_CHAN_PEFE, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_FMMOD, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_TREMFRQ, 24); /* 1 Hz */
|
|
emu_wrptr(sc, ch, EMU_CHAN_FM2FRQ2, 24); /* 1 Hz */
|
|
emu_wrptr(sc, ch, EMU_CHAN_TEMPENV, 0);
|
|
|
|
/*** these are last so OFF prevents writing ***/
|
|
emu_wrptr(sc, ch, EMU_CHAN_LFOVAL2, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_LFOVAL1, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_ATKHLDV, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_ENVVOL, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_ENVVAL, 0);
|
|
|
|
if ((sc->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, EMU_A_CHAN_FXRT1, 0x3f3f3f3f);
|
|
emu_wrptr(sc, ch, EMU_A_CHAN_FXRT2, 0x3f3f3f3f);
|
|
emu_wrptr(sc, ch, EMU_A_CHAN_SENDAMOUNTS, 0x0);
|
|
}
|
|
}
|
|
|
|
emumix_set_spdif_mode(sc, SPDIF_MODE_PCM);
|
|
|
|
if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108))
|
|
emu_wrptr(sc, 0, EMU_A_SPDIF_SAMPLERATE, EMU_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, EMU_A_SPDIF_SAMPLERATE);
|
|
spdif_sr &= 0xfffff1ff;
|
|
spdif_sr |= EMU_A_I2S_CAPTURE_96000;
|
|
emu_wrptr(sc, 0, EMU_A_SPDIF_SAMPLERATE, spdif_sr);
|
|
|
|
/* Disable P16v processing */
|
|
emu_wr_p16vptr(sc, 0, EMU_A2_SRCSel, 0x14);
|
|
|
|
/* Setup P16v/P17v sound routing */
|
|
if (sc->is_ca0102)
|
|
emu_wr_p16vptr(sc, 0, EMU_A2_SRCMULTI_ENABLE, 0xFF00FF00);
|
|
else {
|
|
emu_wr_p16vptr(sc, 0, EMU_A2_MIXER_I2S_ENABLE, 0xFF000000);
|
|
emu_wr_p16vptr(sc, 0, EMU_A2_MIXER_SPDIF_ENABLE, 0xFF000000);
|
|
|
|
tmp = emu_rd(sc, EMU_A_IOCFG, 2);
|
|
emu_wr(sc, EMU_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, EMU_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, EMU_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, EMU_INTE, 0, 4);
|
|
for (ch = 0; ch < NUM_G; ch++)
|
|
emu_wrptr(sc, ch, EMU_CHAN_DCYSUSV, 0);
|
|
for (ch = 0; ch < NUM_G; ch++) {
|
|
emu_wrptr(sc, ch, EMU_CHAN_VTFT, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_CVCF, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_PTRX, 0);
|
|
emu_wrptr(sc, ch, EMU_CHAN_CPF, 0);
|
|
}
|
|
|
|
/* disable audio and lock cache */
|
|
emu_wr(sc, EMU_HCFG, EMU_HCFG_LOCKSOUNDCACHE | EMU_HCFG_LOCKTANKCACHE_MASK | EMU_HCFG_MUTEBUTTONENABLE, 4);
|
|
|
|
emu_wrptr(sc, 0, EMU_PTB, 0);
|
|
/* reset recording buffers */
|
|
emu_wrptr(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE);
|
|
emu_wrptr(sc, 0, EMU_MICBA, 0);
|
|
emu_wrptr(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE);
|
|
emu_wrptr(sc, 0, EMU_FXBA, 0);
|
|
emu_wrptr(sc, 0, EMU_FXWC, 0);
|
|
emu_wrptr(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE);
|
|
emu_wrptr(sc, 0, EMU_ADCBA, 0);
|
|
emu_wrptr(sc, 0, EMU_TCB, 0);
|
|
emu_wrptr(sc, 0, EMU_TCBS, 0);
|
|
|
|
/* disable channel interrupt */
|
|
emu_wrptr(sc, 0, EMU_CLIEL, 0);
|
|
emu_wrptr(sc, 0, EMU_CLIEH, 0);
|
|
emu_wrptr(sc, 0, EMU_SOLEL, 0);
|
|
emu_wrptr(sc, 0, EMU_SOLEH, 0);
|
|
|
|
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 = EMU_A_FXGPREGBASE;
|
|
sc->num_gprs = 0x200;
|
|
sc->code_base = EMU_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 = EMU_A_PTR_ADDR_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 = EMU_MICROCODEBASE;
|
|
sc->code_size = 0x400 / 2; /* 0x400-0x7ff, 1024 words,
|
|
* 512 instructions */
|
|
sc->gpr_base = EMU_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 = EMU_PTR_ADDR_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 = EMU_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 = EMU_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);
|