diff options
Diffstat (limited to 'kernel/drv/oss_sblive/oss_sblive.c')
-rw-r--r-- | kernel/drv/oss_sblive/oss_sblive.c | 3985 |
1 files changed, 3985 insertions, 0 deletions
diff --git a/kernel/drv/oss_sblive/oss_sblive.c b/kernel/drv/oss_sblive/oss_sblive.c new file mode 100644 index 0000000..b516746 --- /dev/null +++ b/kernel/drv/oss_sblive/oss_sblive.c @@ -0,0 +1,3985 @@ +/* + * Purpose: Driver for Creative SB Live/Audigy/2/4. Audio, MIDI and mixer services. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "oss_sblive_cfg.h" +#include "midi_core.h" +#include "oss_pci.h" +#include "remux.h" + +/* + * Include the DSP files for emu10k1 (Live!) and emu10k2 (Audigy) + * These headers have been generated using the emu10k tools (by 4Front). + * The _be.h files have been generated in a big endian system and the others + * in a litle endian (intel) system. + */ +#ifdef OSS_BIG_ENDIAN +# include "emu10k1_dsp_be.h" +# include "emu10k2_dsp_be.h" +#else +# include "emu10k1_dsp.h" +# include "emu10k2_dsp.h" +#endif + +#define NO_EMU10K1_SYNTH +#undef TEST_3D + +#define BOGUS_MIXER_CONTROLS ( \ + SOUND_MASK_SPEAKER | \ + SOUND_MASK_ALTPCM | \ + SOUND_MASK_VIDEO | \ + SOUND_MASK_DEPTH | \ + SOUND_MASK_MONO \ + ) + +#ifndef linux +#define NO_EMU10K1_SYNTH +#endif + +#include "ac97.h" +#include "sblive.h" +#include "eq1.h" + +#define MAX_SENDS 4 + +#define SEND_L 0 +#define SEND_R 1 +#define SEND_SL 2 +#define SEND_SR 3 +#define SEND_C 4 +#define SEND_W 5 +#define SEND_RL 6 +#define SEND_RR 7 + +#define SPDIF_L 20 +#define SPDIF_R 21 + +static unsigned char default_routing[MAX_SENDS] = + { SEND_L, SEND_R, SEND_SL, SEND_SR }; +static unsigned char front_routing[MAX_SENDS] = + { SEND_L, SEND_R, 0x3f, 0x3f }; +static unsigned char surr_routing[MAX_SENDS] = + { SEND_SL, SEND_SR, 0x3f, 0x3f }; +static unsigned char center_lfe_routing[MAX_SENDS] = + { SEND_C, SEND_W, 0x3f, 0x3f }; +static unsigned char rear_routing[MAX_SENDS] = + { SEND_RL, SEND_RR, 0x3f, 0x3f }; +static unsigned char spdif_routing[MAX_SENDS] = + { SPDIF_L, SPDIF_R, 0x3f, 0x3f }; + +typedef struct +{ + int speed; + int pitch; + int recbits; + int audigy_recbits; + int rom; +} +speed_ent; + +/* Note! with audigy speedsel=7 means 12 kHz */ + +static speed_ent speed_tab[] = { + {8000, 0xb6a41b, 7, 8, ROM7}, + {11025, 0xbe0b64, 6, 6, ROM6}, + {16000, 0xc6a41b, 5, 5, ROM5}, + {22050, 0xce0b64, 4, 3, ROM4}, + {24000, 0xd00000, 3, 3, ROM3}, + {32000, 0xd6a41b, 2, 2, ROM2}, + {44100, 0xde0b64, 1, 1, ROM1}, + {48000, 0xe00000, 0, 0, ROM0}, + {0} +}; + +#define PCI_VENDOR_ID_CREATIVE 0x1102 +#define PCI_DEVICE_ID_SBLIVE 0x0002 +#define PCI_DEVICE_ID_AUDIGY 0x0004 +#define PCI_DEVICE_ID_AUDIGYVALUE 0x0008 +#define PCI_DEVICE_ID_AUDIGY_CARDBUS 0x2001 + +#define LEFT_CH 0 +#define RIGHT_CH 1 + +void sblive_init_voice (sblive_devc * devc, int chn); +static void audigyuartintr (sblive_devc * devc); + +static int +ac97_read (void *devc_, int wAddr) +{ + sblive_devc *devc = devc_; + int dtemp = 0, i; + + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, wAddr, devc->base + 0x1e); + for (i = 0; i < 10000; i++) + if (INB (devc->osdev, devc->base + 0x1e) & 0x80) + break; + if (i == 1000) + { + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return OSS_EIO; /* Timeout */ + } + dtemp = INW (devc->osdev, devc->base + 0x1c); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return dtemp & 0xffff; +} + +static int +ac97_write (void *devc_, int wAddr, int wData) +{ + sblive_devc *devc = devc_; + oss_native_word flags; + int i; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, wAddr, devc->base + 0x1e); + for (i = 0; i < 10000; i++) + if (INB (devc->osdev, devc->base + 0x1e) & 0x80) + break; + OUTW (devc->osdev, wData, devc->base + 0x1c); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return 0; +} + +unsigned int +sblive_read_reg (sblive_devc * devc, int reg, int chn) +{ + oss_native_word flags; + unsigned int ptr, ptr_addr_mask, val, mask, size, offset; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + ptr_addr_mask = (devc->feature_mask & SB_AUDIGY) ? 0x0fff0000 : 0x07ff0000; + ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f); + OUTL (devc->osdev, ptr, devc->base + 0x00); /* Pointer */ + val = INL (devc->osdev, devc->base + 0x04); /* Data */ + if (reg & 0xff000000) + { + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + val &= mask; + val >>= offset; + } + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return val; +} + +void +sblive_write_reg (sblive_devc * devc, int reg, int chn, unsigned int value) +{ + oss_native_word flags; + unsigned int ptr, ptr_addr_mask, mask, size, offset; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + ptr_addr_mask = (devc->feature_mask & SB_AUDIGY) ? 0x0fff0000 : 0x07ff0000; + ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f); + OUTL (devc->osdev, ptr, devc->base + 0x00); /* Pointer */ + if (reg & 0xff000000) + { + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + value <<= offset; + value &= mask; + value |= INL (devc->osdev, devc->base + 0x04) & ~mask; /* data */ + } + OUTL (devc->osdev, value, devc->base + 0x04); /* Data */ + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); +} + +static void +write_efx (sblive_devc * devc, int reg, unsigned int value) +{ + sblive_write_reg (devc, reg, 0, value); +} + +static void +update_vu (sblive_devc * devc, sblive_portc * portc, dmap_p dmap, int frag) +{ + int left = 0, right = 0; + int i, l; + + frag %= dmap->nfrags; + l = dmap->fragment_size / 2; + + if (portc->format == AFMT_AC3) /* Raw S/PDIF mode */ + { + if (devc->vu_tmp2 == 0) + devc->vu_tmp2 = 2; + + portc->vu_left = devc->vu_tmp; + portc->vu_right = 144 - devc->vu_tmp; + devc->vu_tmp = devc->vu_tmp + devc->vu_tmp2; + + if (devc->vu_tmp >= 144) + { + devc->vu_tmp2 = -2; + devc->vu_tmp = 144; + } + + if (devc->vu_tmp <= 0) + { + devc->vu_tmp2 = 2; + devc->vu_tmp = 0; + } + + return; + } + + if (portc->format == AFMT_U8) + { + unsigned char *p; + int v; + + p = dmap->dmabuf + (frag * dmap->fragment_size); + + if (dmap->dmabuf != NULL) + for (i = 0; i < l; i++) + { + v = *p; + p++; + v -= 128; + if (v < 0) + v = -v; + if (v > left) + left = v; + + v = *p; + p++; + v -= 128; + if (v < 0) + v = -v; + if (v > right) + right = v; + } + } + else + { + short *p; + int v; + + l /= 2; + p = (short *) (dmap->dmabuf + (frag * dmap->fragment_size)); + + if (dmap->dmabuf != NULL) + for (i = 0; i < l; i++) + { + v = SSWAP (*p++) >> 8; + if (v < 0) + v = -v; + if (v > left) + left = v; + + v = *p++ >> 8; + if (v < 0) + v = -v; + if (v > right) + right = v; + } + } + + if (portc->channels == 1) /* Mono */ + { + if (right > left) + left = right; + right = left; + } + + if (left > portc->vu_left) + portc->vu_left = left; + if (right > portc->vu_right) + portc->vu_right = right; +} + +static int +sbliveintr (oss_device_t * osdev) +{ + int p; + unsigned int status; +#ifndef NO_EMU10K1_SYNTH + extern int sblive_synth_enable; +#endif + + sblive_devc *devc = osdev->devc; + + /* + * TODO: Fix mutexes and move the inputintr/outputintr calls outside the + * mutex block. + */ + /* oss_native_word flags; */ + /* MUTEX_ENTER (devc->mutex, flags); */ + + status = INL (devc->osdev, devc->base + 0x08); + +#if !defined(sparc) + if (status == 0) + { + /* MUTEX_EXIT (devc->mutex, flags); */ + return 0; + } +#endif + + if (status & 0x00000080) /* MIDI RX interrupt */ + { + if (devc->feature_mask & SB_AUDIGY) + audigyuartintr (devc); + else + uart401_irq (&devc->uart401devc); + } + + if (status & 0x00008000) /* ADC buffer full intr */ + { + /* Force the starting position to match this moment */ + unsigned int pos; + pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff; + devc->portc[0].rec_starttime = pos; + } + + if (status & 0x00000240) /* Interval timer or channel loop interrupt */ + { + for (p = 0; p < devc->n_audiodevs; p++) + { + sblive_portc *portc = &devc->portc[p]; + + if (portc->audiodev >= 0 && portc->audiodev < num_audio_engines + && portc->trigger_bits & PCM_ENABLE_OUTPUT) + { + int pos, n; + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out; + + pos = sblive_read_reg (devc, QKBCA, portc->voice_chn) & 0x00ffffff; /* Curr pos */ + pos <<= portc->out_sz; + pos -= dmap->driver_use_value; + + if (dmap->fragment_size == 0) + { + cmn_err (CE_WARN, "dmap->fragment_size == 0\n"); + continue; + } + + pos /= dmap->fragment_size; + if (pos < 0 || pos >= dmap->nfrags) + pos = 0; + + /* + * If this was a full/half loop interrupt then use forced pointer + */ + if (sblive_get_voice_loopintr (devc, portc->voice_chn)) + pos = 0; /* Full loop boundary */ + else if (sblive_get_voice_halfloopintr (devc, portc->voice_chn)) + pos = dmap->nfrags / 2; /* Half loop boundary */ + + n = 0; + while (dmap_get_qhead (dmap) != pos && n++ < dmap->nfrags) + { + update_vu (devc, portc, dmap, dmap_get_qhead (dmap)); + oss_audio_outputintr (portc->audiodev, 0); + } + } + + if (num_audio_engines > 0 && portc->audiodev < num_audio_engines + && portc->trigger_bits & PCM_ENABLE_INPUT) + { + int n; + unsigned int pos; + + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in; + + /* Compute current pos based on the wall clock register */ + pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff; + if (pos > portc->rec_starttime) + pos = pos - portc->rec_starttime; + else + pos = 0xfffff - (portc->rec_starttime - pos); + pos = (pos * (portc->speed / 25)) / (48000 / 25); + pos *= 2; /* 16 bit->bytes */ + pos *= portc->channels; + pos = pos % dmap->bytes_in_use; + if (dmap->fragment_size == 0) + cmn_err (CE_WARN, "dmap->fragment_size==0\n"); + else + { + pos /= dmap->fragment_size; + if (pos >= dmap->nfrags) + pos = 0; + n = 0; + while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags) + { + oss_audio_inputintr (devc->recording_dev, 0); + } + } + } + } + +#ifndef NO_EMU10K1_SYNTH + if (sblive_synth_enable) + sblive_synth_interrupt (devc); +#endif + } + + OUTL (devc->osdev, status, devc->base + 0x08); /* Acknowledge them */ + + /* MUTEX_EXIT (devc->mutex, flags); */ + + return 1; +} + +static int +setup_passthrough (sblive_devc * devc, sblive_portc * portc, int pass) +{ + int ctrl = devc->passthrough_gpr; + + if (ctrl < 0) + return 0; + + if (pass == portc->uses_spdif) + return 1; + + if (pass && devc->spdif_busy) + return 0; + + portc->uses_spdif = pass; + + sblive_write_reg (devc, ctrl + GPR0, 0, pass); + sblive_write_reg (devc, ctrl + GPR0 + 1, 0, !pass); + if (pass) + { + devc->spdif_busy = 1; + } + else + { + devc->spdif_busy = 0; + } + + return 1; +} + +static int +sblive_audio_set_rate (int dev, int arg) +{ + sblive_portc *portc = audio_engines[dev]->portc; + int i, n = -1, dif, best = -1, bestdif = 0x7fffffff; + + if (arg == 0) + return portc->speed; + + for (i = 0; speed_tab[i].speed != 0 && n == -1; i++) + { + if (speed_tab[i].speed == arg) /* Exact match */ + { + n = i; + break; + } + + dif = arg - speed_tab[i].speed; + if (dif < 0) + dif = -dif; + + if (dif < bestdif) + { + best = i; + bestdif = dif; + } + } + + if (n == -1) + n = best; + + if (n == -1) + n = 0; + + portc->speed = speed_tab[n].speed; + portc->speedsel = n; + + return portc->speed; +} + +static short +sblive_audio_set_channels (int dev, short arg) +{ + sblive_portc *portc = audio_engines[dev]->portc; + + if ((arg != 1) && (arg != 2)) + return portc->channels; + portc->channels = arg; + + return portc->channels; +} + +static unsigned int +sblive_audio_set_format (int dev, unsigned int arg) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->format; + + if (portc->mode & OPEN_READ) + arg = AFMT_S16_LE; + + if (arg != AFMT_U8 && arg != AFMT_S16_LE && arg != AFMT_AC3) + return portc->format; + + /* Enforce stereo mode with AC3 */ + if (arg == AFMT_AC3) + { + if (!setup_passthrough (devc, portc, 1)) + return portc->format; + portc->channels = 2; + portc->speed = 48000; + } + else + if (portc->input_type != ITYPE_SPDIF && portc->uses_spdif + && arg != AFMT_AC3) + { + setup_passthrough (devc, portc, 0); + } + + portc->format = arg; + + return portc->format; +} + +static int mixer_ext_init (int dev); +static int create_efx_mixer (int dev); +static int sblive_set_gpr (int dev, int ctrl, unsigned int cmd, int value); + +static int +is_special_gpr (int gpr) +{ + if (gpr >= NEXT_FREE_GPR) + return 0; + + if (SPECIAL_GPRS & (1 << gpr)) + { + return 1; + } + + if (gpr > 0) + if (SPECIAL_GPRS & (1 << (gpr - 1))) + { + return 1; + } + return 0; +} + +static void update_output_device (sblive_devc * devc, sblive_portc * portc); + +static int +sblive_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + sblive_reg *reg = (sblive_reg *) arg; + gpr_info *gpr = (gpr_info *) arg; + const_info *consts = (const_info *) arg; + unsigned int *code = (unsigned int *) arg; + int i; + + switch (cmd) + { + case SNDCTL_DSP_GETCHANNELMASK: + return *arg = + DSP_BIND_FRONT | DSP_BIND_REAR | DSP_BIND_SURR | DSP_BIND_CENTER_LFE; + break; + + case SNDCTL_DSP_BIND_CHANNEL: + { + int val; + + val = *arg; + portc->speaker_mode = SMODE_BIND; + portc->binding = val; + return *arg = val; + } + break; + + case SNDCTL_DSP_SETPLAYVOL: + { + int left, right, val; + + val = *arg; + + left = val & 0xff; + right = (val >> 8) & 0xff; + if (left < 0) + left = 0; + if (right < 0) + right = 0; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + +#if 0 + if (right > left) + left = right; +#endif + portc->playvol = left; + update_output_device (devc, portc); + + return *arg = left | (left << 8); + } + break; + + case SNDCTL_DSP_GETPLAYVOL: + { + int vol; + vol = (portc->playvol << 8) | portc->playvol; + return *arg = vol; + } + break; + + case SBLIVE_READREG: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + reg->value = sblive_read_reg (devc, reg->reg, reg->chn); + return 0; + break; + + case SBLIVE_WRITEREG: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + sblive_write_reg (devc, reg->reg, reg->chn, reg->value); + return 0; + break; + + case SBLIVE_READGPIO: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + return *arg = INW (devc->osdev, devc->base + 0x18); + break; + + case SBLIVE_WRITEGPIO: + { + int val; + val = *arg; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + OUTW (devc->osdev, val, devc->base + 0x18); + + } + return 0; + + case SBLIVE_WRITEPARMS: + { + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + if (gpr->ngpr >= MAX_GPR_PARMS) + return OSS_EIO; + + for (i = 0; i < gpr->ngpr; i++) + { + gpr->gpr[i].name[GPR_NAME_SIZE - 1] = 0; /* Overflow protection */ + if (strlen (gpr->gpr[i].name) >= 32) /* Name may be bad */ + { + return OSS_EIO; + } + + if (gpr->gpr[i].num >= MAX_GPR) + { + return OSS_EIO; + } + +/* cmn_err(CE_CONT, "Gpr %d = %s (vol %x) type=%x\n", gpr->gpr[i].num, gpr->gpr[i].name, gpr->gpr[i].def, gpr->gpr[i].type); */ + if (gpr->gpr[i].type != MIXT_GROUP) + { + if (is_special_gpr (gpr->gpr[i].num)) + sblive_set_gpr (devc->mixer_dev, gpr->gpr[i].num, + SNDCTL_MIX_WRITE, + devc->gpr_values[gpr->gpr[i].num]); + else + sblive_set_gpr (devc->mixer_dev, gpr->gpr[i].num, + SNDCTL_MIX_WRITE, gpr->gpr[i].def); + } + } + + + if (devc->gpr == NULL) + { + devc->gpr = PMALLOC (devc->osdev, sizeof (gpr_info)); + if (devc->gpr == NULL) + { + cmn_err (CE_WARN, "Out of memory (gpr)\n"); + return OSS_ENOSPC; + } + memset (devc->gpr, 0, sizeof (gpr_info)); + } + memcpy (devc->gpr, gpr, sizeof (gpr_info)); + create_efx_mixer (devc->mixer_dev); + } + return 0; + break; + + case SBLIVE_WRITECONST: + { + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + if (consts->nconst >= MAX_CONST_PARMS) + return OSS_EIO; + + for (i = 0; i < consts->nconst; i++) + { + if (consts->consts[i].gpr >= MAX_GPR) + { + return OSS_EIO; + } + + sblive_write_reg (devc, consts->consts[i].gpr + GPR0, 0, + consts->consts[i].value); + } + + } + return 0; + break; + + case SBLIVE_WRITECODE1: + { + int pc; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + for (pc = 0; pc < 512; pc++) + { + write_efx (devc, UC0 + pc, code[pc]); + } + + } + sblive_write_reg (devc, DBG, 0, 0); + return 0; + break; + + case SBLIVE_WRITECODE2: + { + int pc; + +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + for (pc = 0; pc < 512; pc++) + { + write_efx (devc, UC0 + 512 + pc, code[pc]); + } + } + sblive_write_reg (devc, DBG, 0, 0); + return 0; + break; + + case SBLIVE_GETCHIPTYPE: +#ifdef GET_PROCESS_UID + if (GET_PROCESS_UID () != 0) /* Not root */ + return OSS_EINVAL; +#endif + return *arg = devc->feature_mask & ~SB_AUDIGY2; + break; + + } + return OSS_EINVAL; +} + +static void sblive_audio_trigger (int dev, int state); + +static void +sblive_audio_reset (int dev) +{ + sblive_audio_trigger (dev, 0); +} + +static void +sblive_audio_reset_input (int dev) +{ + sblive_portc *portc = audio_engines[dev]->portc; + sblive_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +sblive_audio_reset_output (int dev) +{ + sblive_portc *portc = audio_engines[dev]->portc; + sblive_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +static int sblive_set_vol (int dev, int ctrl, unsigned int cmd, int value); + +static void +reset_portc_volume (sblive_devc * devc, sblive_portc * portc) +{ + int v; +#ifdef TEST_3D + v = 100 | (50 << 8) | (0 << 16); /* vol=100, dist=50, angle=0 */ +#else + v = 100 | (100 < 8); +#endif + sblive_set_vol (devc->mixer_dev, portc->port_number, SNDCTL_MIX_WRITE, v); +} + +#if MAX_ADEV == 2 +static const unsigned int binding_map[MAX_ADEV] = + { DSP_BIND_FRONT, DSP_BIND_SURR }; +#else +static const unsigned int binding_map[MAX_ADEV] = { + DSP_BIND_FRONT, + DSP_BIND_FRONT, + DSP_BIND_SURR, + DSP_BIND_CENTER_LFE, + DSP_BIND_REAR +}; +#endif + +/*ARGSUSED*/ +static int +sblive_audio_open (int dev, int mode, int open_flags) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + portc->mode = mode; + portc->audio_active &= ~mode; + + portc->resetvol = 0; + + portc->speaker_mode = devc->speaker_mode; + portc->binding = binding_map[portc->port_number]; + if (portc->binding == 0) + portc->binding = DSP_BIND_FRONT; + mixer_devs[devc->mixer_dev]->modify_counter++; /* Force update of mixer */ + + audio_engines[dev]->flags &= ~ADEV_FIXEDRATE; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + if (devc->autoreset || portc->resetvol) + reset_portc_volume (devc, portc); + + if (portc->input_type == ITYPE_SPDIF) + { + if (!setup_passthrough (devc, portc, 1)) + { + portc->mode = 0; + return OSS_EBUSY; + } + } + else + { + /* Enable AC3 format if possible */ + if (!devc->spdif_busy) + audio_engines[dev]->oformat_mask |= AFMT_AC3; + else + audio_engines[dev]->oformat_mask &= ~AFMT_AC3; + } + return 0; +} + +static void +sblive_audio_close (int dev, int mode) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + + sblive_audio_reset (dev); + portc->mode = 0; + portc->audio_active &= ~mode; + mixer_devs[devc->mixer_dev]->modify_counter++; + setup_passthrough (devc, portc, 0); + audio_engines[dev]->oformat_mask |= AFMT_AC3; +} + +#ifdef TEST_3D +/* Sin table for Q1 (*10000) */ +static const int sincos[] = { + 0, 174, 348, 523, 697, 871, 1045, 1218, 1391, 1564, + 1736, 1908, 2079, 2249, 2419, 2588, 2756, 2923, 3090, 3255, + 3420, 3583, 3746, 3907, 4067, 4226, 4383, 4539, 4694, 4848, + 5000, 5150, 5299, 5446, 5591, 5735, 5877, 6018, 6156, 6293, + 6427, 6560, 6691, 6819, 6946, 7071, 7193, 7313, 7431, 7547, + 7660, 7771, 7880, 7986, 8090, 8191, 8290, 8386, 8480, 8571, + 8660, 8746, 8829, 8910, 8987, 9063, 9135, 9205, 9271, 9335, + 9396, 9455, 9510, 9563, 9612, 9659, 9702, 9743, 9781, 9816, + 9848, 9876, 9902, 9925, 9945, 9961, 9975, 9986, 9993, 9998, + 10000, +}; + +static __inline__ int +oss_sin (int angle) +{ + int a; + int f; + + a = angle % 90; + + if ((angle / 90) & 1) + a = 90 - a; + + f = sincos[a]; + if (angle >= 180) + f = -f; + return f; +} + +static __inline__ int +oss_cos (int angle) +{ + int a, q; + int f; + + a = angle % 90; + q = angle / 90; + + if (!(q & 1)) + a = 90 - a; + + f = sincos[a]; + if (angle >= 90 && angle < 270) + f = -f; + return f; +} + +static void +compute_3d (sblive_devc * devc, sblive_portc * portc, int voice, int chn, + int *send) +{ + int angle = portc->playangle; + int dist, opening = 45; + int i; + + /* left, right, rear_right, rear_left */ + static int spk_angles[4] = { 315, 45, 135, 225 }; + int gain = 50, leak = 0; + int v[4]; + + dist = portc->playdist; + + if (dist < 0) + dist = 0; + if (dist > 100) + dist = 100; + portc->playdist = dist; + + dist = 100 - dist; /* Invert distance */ + opening = (90 * dist) / 100; + + if (dist < 50) + { /* Attenuate distant sounds */ + gain = dist; + } + else + { + /* "Expand" close sounds by leaking signal to silent channels */ + leak = dist - 50; + } + + if (portc->channels == 2) + { + if (chn == LEFT_CH) + angle -= opening; + else + angle += opening; + } + + if (angle < 0) + angle += 360; + + angle %= 360; + + for (i = 0; i < 4; i++) + v[i] = (gain * portc->playvol * 255 + 25) / 50; + + for (i = 0; i < 4; i++) + { + int a = spk_angles[i] - angle; + + if (a < 0) + a = -a; /* ABS */ + if (a > 180) + a = 360 - a; + + if (a >= 90) /* Too far */ + { + v[i] = 0; /* Muted speaker */ + continue; + } + else + v[i] = ((v[i] * oss_cos (a) + 5000) / 10000) / 100; + } + + if (leak > 0) + { + leak = (255 * portc->playvol * leak + 2500) / 5000; + + for (i = 0; i < 4; i++) + if (v[i] < leak) + v[i] = leak; + } + + send[0] = v[0]; /* Left */ + send[1] = v[1]; /* Right */ + send[2] = v[3]; /* Rear left */ + send[3] = v[2]; /* Rear right */ +} +#endif + +static void +write_routing (sblive_devc * devc, int voice, unsigned char *routing) +{ + int i; + + if (routing == NULL) + routing = default_routing; + + if (devc->feature_mask & SB_AUDIGY) + { + unsigned int srda = 0; + + for (i = 0; i < 4; i++) + srda |= routing[i] << (i * 8); + + sblive_write_reg (devc, SRDA, voice, srda); + } + else + { + int fxrt = 0; + + for (i = 0; i < 4; i++) + fxrt |= routing[i] << ((i * 4) + 16); + sblive_write_reg (devc, FXRT, voice, fxrt); + } +} + +/*ARGSUSED*/ +static void +compute_bind (sblive_devc * devc, sblive_portc * portc, unsigned char *send, + int chn) +{ + memset (send, 0, MAX_SENDS); + + if (chn == LEFT_CH) + send[0] = (0xff * portc->playvol + 50) / 100; + else + send[1] = (0xff * portc->playvol + 50) / 100; + + switch (portc->binding) + { + case DSP_BIND_FRONT: + portc->routing = front_routing; + break; + case DSP_BIND_SURR: + portc->routing = surr_routing; + break; + case DSP_BIND_CENTER_LFE: + portc->routing = center_lfe_routing; + break; + case DSP_BIND_REAR: + portc->routing = rear_routing; + break; + default: + portc->routing = default_routing; + } +} + +static void +update_output_volume (int dev, int voice, int chn) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + int left, right; + unsigned int loop_start, loop_end, tmp; + unsigned char send[MAX_SENDS]; + + send[0] = 0xff; /* Max */ + send[1] = 0xff; /* Max */ + send[2] = 0xff; /* Max */ + send[3] = 0xff; /* Max */ + + if (portc->input_type == ITYPE_SPDIF || portc->format == AFMT_AC3) + { + /* Digital voice */ + send[2] = 0; /* Muted */ + send[3] = 0; /* Muted */ + + /* sends are revered between Audigy2 and Audigy */ + left = (devc->feature_mask & SB_AUDIGY2) ? 1 : 0; + right = !left; + + if (portc->channels > 1) + { + if (chn == LEFT_CH) + { + send[left] = 0; + } + else + { + send[right] = 0; + } + } + } + else + { + /* Analog voice */ + + if (portc->channels > 1) + { + if (chn == LEFT_CH) + { + send[1] = 0; + } + else + { + send[0] = 0; + } + } + send[2] = send[0]; + send[3] = send[1]; + +#ifdef TEST_3D + if (portc->speaker_mode == SMODE_3D) + compute_3d (devc, portc, voice, chn, send); + else +#endif + { + send[0] = (send[0] * portc->playvol + 50) / 100; + send[1] = (send[1] * portc->playvol + 50) / 100; + send[2] = (send[2] * portc->playvol + 50) / 100; + send[3] = (send[3] * portc->playvol + 50) / 100; + + switch (portc->speaker_mode) + { + case SMODE_FRONT: + send[2] = send[3] = 0; + break; + + case SMODE_SURR: + send[0] = send[1] = 0; + break; + + case SMODE_FRONTREAR: + break; + + case SMODE_BIND: + compute_bind (devc, portc, send, chn); + break; + } + } + + /* Analog voice */ + } + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + loop_end = sblive_read_reg (devc, SDL, voice) & 0xffffff; + sblive_write_reg (devc, SDL, voice, loop_end | (send[3] << 24)); + loop_start = sblive_read_reg (devc, SCSA, voice) & 0xffffff; + sblive_write_reg (devc, SCSA, voice, loop_start | (send[2] << 24)); + tmp = sblive_read_reg (devc, PTAB, voice) & 0xffff0000; + sblive_write_reg (devc, PTAB, voice, tmp | (send[0] << 8) | send[1]); + write_routing (devc, voice, portc->routing); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static void +setup_audio_voice (int dev, int voice, int chn) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_out; + unsigned int nCRA = 0; + + unsigned int loop_start, loop_end; + + int sz = 1; + int start_pos; + + sblive_write_reg (devc, VEDS, voice, 0x0); /* OFF */ + sblive_write_reg (devc, VTFT, voice, 0xffff); + sblive_write_reg (devc, CVCF, voice, 0xffff); + + if (devc->feature_mask & SB_AUDIGY) + sblive_write_reg (devc, SRDA, voice, 0x03020100); + else + sblive_write_reg (devc, FXRT, voice, 0x32100000); + + + sz = + (((portc->format == AFMT_S16_LE + || portc->format == AFMT_AC3)) ? 1 : 0) + ((portc->channels == + 2) ? 1 : 0); + + loop_start = dmap->driver_use_value >> sz; + loop_end = (dmap->driver_use_value + dmap->bytes_in_use) >> sz; + + /* set mono/stereo */ + sblive_write_reg (devc, CPF, voice, (portc->channels > 1) ? 0x8000 : 0); + + nCRA = (portc->channels > 1) ? 28 : 30; + nCRA *= (portc->format == AFMT_S16_LE || portc->format == AFMT_AC3) ? 1 : 2; + start_pos = loop_start + nCRA; + + /* SDL, ST, CA */ + portc->out_sz = sz; + + sblive_write_reg (devc, SDL, voice, loop_end); + sblive_write_reg (devc, SCSA, voice, loop_start); + sblive_write_reg (devc, PTAB, voice, 0); + + update_output_volume (dev, voice, chn); /* Set volume */ + + if (portc->format == AFMT_S16_LE || portc->format == AFMT_AC3) + sblive_write_reg (devc, QKBCA, voice, start_pos); + else + sblive_write_reg (devc, QKBCA, voice, start_pos | BYTESIZE); + + sblive_write_reg (devc, Z1, voice, 0); + sblive_write_reg (devc, Z2, voice, 0); + + sblive_write_reg (devc, MAPA, voice, 0x1fff | (devc->silent_page_phys << 1)); /* This is really a physical address */ + sblive_write_reg (devc, MAPB, voice, 0x1fff | (devc->silent_page_phys << 1)); /* This is really a physical address */ + + sblive_write_reg (devc, VTFT, voice, 0x0000ffff); + sblive_write_reg (devc, CVCF, voice, 0x0000ffff); + sblive_write_reg (devc, MEHA, voice, 0); + sblive_write_reg (devc, MEDS, voice, 0x7f); + sblive_write_reg (devc, MLV, voice, 0x8000); + sblive_write_reg (devc, VLV, voice, 0x8000); + sblive_write_reg (devc, VFM, voice, 0); + sblive_write_reg (devc, TMFQ, voice, 0); + sblive_write_reg (devc, VVFQ, voice, 0); + sblive_write_reg (devc, MEV, voice, 0x8000); + sblive_write_reg (devc, VEHA, voice, 0x7f7f); /* OK */ + sblive_write_reg (devc, VEV, voice, 0x8000); /* No volume envelope delay (OK) */ + sblive_write_reg (devc, PEFE_FILTERAMOUNT, voice, 0x7f); + sblive_write_reg (devc, PEFE_PITCHAMOUNT, voice, 0x00); +} + +/*ARGSUSED*/ +static void +update_output_device (sblive_devc * devc, sblive_portc * portc) +{ + int voiceL = portc->voice_chn, voiceR = portc->voice_chn + 1; + + if (!(portc->audio_active & PCM_ENABLE_OUTPUT)) + return; + + update_output_volume (portc->audiodev, voiceL, LEFT_CH); + update_output_volume (portc->audiodev, voiceR, RIGHT_CH); +} + + +/*ARGSUSED*/ +static void +sblive_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + sblive_portc *portc = audio_engines[dev]->portc; + + portc->audio_active |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; +} + +/*ARGSUSED*/ +static void +sblive_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + sblive_portc *portc = audio_engines[dev]->portc; + + portc->audio_active |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + +void +sblive_set_loop_stop (sblive_devc * devc, int voice, int s) +{ + unsigned int tmp; + int offs, bit; + + offs = voice / 32; + bit = voice % 32; + s = !!s; + + tmp = sblive_read_reg (devc, SOLL + offs, 0); + tmp &= ~(1 << bit); + + if (s) + tmp |= (1 << bit); + sblive_write_reg (devc, SOLL + offs, 0, tmp); +} + +int +sblive_get_voice_loopintr (sblive_devc * devc, int voice) +{ + unsigned int tmp; + int offs, bit; + + offs = voice / 32; + bit = voice % 32; + + tmp = sblive_read_reg (devc, CLIPL + offs, 0); + tmp &= 1 << bit; + sblive_write_reg (devc, CLIPL + offs, 0, tmp); /* Ack the interrupt */ + + return !!tmp; +} + +int +sblive_get_voice_halfloopintr (sblive_devc * devc, int voice) +{ + unsigned int tmp; + int offs, bit; + + offs = voice / 32; + bit = voice % 32; + + tmp = sblive_read_reg (devc, HLIPL + offs, 0); + tmp &= 1 << bit; + sblive_write_reg (devc, HLIPL + offs, 0, tmp); /* Ack the interrupt */ + + return !!tmp; +} + +void +sblive_set_voice_intr (sblive_devc * devc, int voice, int s) +{ + unsigned int tmp; + int offs, bit; + + offs = voice / 32; + bit = voice % 32; + s = !!s; + + tmp = sblive_read_reg (devc, CLIEL + offs, 0); + tmp &= ~(1 << bit); + if (s) + tmp |= (1 << bit); + sblive_write_reg (devc, CLIEL + offs, 0, tmp); + sblive_write_reg (devc, HLIEL + offs, 0, tmp); +} + +static unsigned int +emu_rate_to_pitch (unsigned int rate) +{ + static unsigned int 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; /* Bail out if no leading "1" */ + rate *= 11185; /* Scale 48000 to 0x20002380 */ + for (i = 31; i > 0; i--) + { + if (rate & 0x80000000) + { /* Detect leading "1" */ + return (((unsigned int) (i - 15) << 20) + + logMagTable[0x7f & (rate >> 24)] + + (0x7f & (rate >> 17)) * logSlopeTable[0x7f & (rate >> 24)]); + } + rate <<= 1; + } + + return 0; /* Should never reach this point */ +} + +static unsigned int +emu_rate_to_linearpitch (unsigned int rate) +{ + rate = (rate << 8) / 375; + return (rate >> 1) + (rate & 1); +} + +static void +start_audio_voice (int dev, int voice, int chn) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + unsigned int sample, initial_pitch, pitch_target; + unsigned int cra, cs, ccis, i; + + /* setup CCR regs */ + cra = 64; + cs = (portc->channels > 1) ? 4 : 2; + ccis = (portc->channels > 1) ? 28 : 30; + ccis *= (portc->format == AFMT_S16_LE || portc->format == AFMT_AC3) ? 1 : 2; + sample = (portc->format == AFMT_S16_LE + || portc->format == AFMT_AC3) ? 0x00000000 : 0x80808080; + + for (i = 0; i < cs; i++) + sblive_write_reg (devc, CD0 + i, voice, sample); + + sblive_write_reg (devc, CCR_CACHEINVALIDSIZE, voice, 0); + sblive_write_reg (devc, CCR_READADDRESS, voice, cra); + sblive_write_reg (devc, CCR_CACHEINVALIDSIZE, voice, ccis); + + /* Set current pitch */ + sblive_write_reg (devc, IFA, voice, 0xff00); + sblive_write_reg (devc, VTFT, voice, 0xffffffff); + sblive_write_reg (devc, CVCF, voice, 0xffffffff); + sblive_set_loop_stop (devc, voice, 0); + + pitch_target = emu_rate_to_linearpitch (portc->speed); + initial_pitch = emu_rate_to_pitch (portc->speed) >> 8; + sblive_write_reg (devc, PTRX_PITCHTARGET, voice, pitch_target); + sblive_write_reg (devc, CPF_CURRENTPITCH, voice, pitch_target); + sblive_write_reg (devc, IP, voice, initial_pitch); + + if (chn == LEFT_CH) + { + sblive_get_voice_loopintr (devc, voice); + sblive_get_voice_halfloopintr (devc, voice); + sblive_set_voice_intr (devc, voice, 1); + } + sblive_write_reg (devc, VEDS, voice, /*0x80 | */ 0x7f7f); /* Trigger (OK) */ +} + +/*ARGSUSED*/ +static void +stop_audio_voice (int dev, int voice, int chn) +{ + sblive_devc *devc = audio_engines[dev]->devc; + + sblive_write_reg (devc, IFA, voice, 0xffff); + sblive_write_reg (devc, VTFT, voice, 0xffff); + sblive_write_reg (devc, PTRX_PITCHTARGET, voice, 0); + sblive_write_reg (devc, CPF_CURRENTPITCH, voice, 0); + sblive_write_reg (devc, IP, voice, 0); + sblive_set_loop_stop (devc, voice, 1); + sblive_set_voice_intr (devc, voice, 0); +} + +static void +sblive_audio_trigger (int dev, int state) +{ + sblive_portc *portc = audio_engines[dev]->portc; + sblive_devc *devc = audio_engines[dev]->devc; + int voiceL = portc->voice_chn, voiceR = portc->voice_chn + 1; + + if (portc->mode & OPEN_WRITE) + { + if (state & PCM_ENABLE_OUTPUT) + { + if ((portc->audio_active & PCM_ENABLE_OUTPUT) && + !(portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + start_audio_voice (dev, voiceL, LEFT_CH); + /* sblive_dump_regs(devc, voiceL); */ + if (portc->channels > 1) + start_audio_voice (dev, voiceR, RIGHT_CH); + portc->trigger_bits |= PCM_ENABLE_OUTPUT; + } + } + else + { + if ((portc->audio_active & PCM_ENABLE_OUTPUT) && + (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + portc->audio_active &= ~PCM_ENABLE_OUTPUT; + stop_audio_voice (dev, voiceL, LEFT_CH); + stop_audio_voice (dev, voiceR, RIGHT_CH); + } + } + } + + if (portc->mode & OPEN_READ && !(audio_engines[dev]->flags & ADEV_NOINPUT)) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_active & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + int tmp = sblive_read_reg (devc, ADCSR, 0); + unsigned int pos; + + if (portc->input_type == ITYPE_SPDIF) + { + /* Start recording from S/PDIF input A */ + sblive_write_reg (devc, SPRC, 0, portc->in_szbits | 0x00); + } + else + { + if (devc->feature_mask & SB_AUDIGY) + { + tmp |= 0x10; /* Left channel enable */ + if (portc->channels > 1) + tmp |= 0x20; /* Right channel enable */ + } + else + { + tmp |= 0x08; /* Left channel enable */ + if (portc->channels > 1) + tmp |= 0x10; /* Right channel enable */ + } + sblive_write_reg (devc, ADCBS, 0, portc->in_szbits); + sblive_write_reg (devc, ADCSR, 0, tmp); /* GO */ + } + + pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff; + portc->rec_starttime = pos; + portc->trigger_bits |= PCM_ENABLE_INPUT; + } + } + else + { + if ((portc->audio_active & PCM_ENABLE_INPUT) && + (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + if (portc->input_type == ITYPE_SPDIF) + sblive_write_reg (devc, SPRC, 0, 0); + else + sblive_write_reg (devc, ADCSR, 0, 0); + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + portc->audio_active &= ~PCM_ENABLE_INPUT; + } + } + } + +} + +/*ARGSUSED*/ +static int +sblive_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_in; + int sz = -1; + + if (audio_engines[dev]->flags & ADEV_NOINPUT) + { + cmn_err (CE_WARN, "Audio device %d is output only\n", dev); + return OSS_EIO; + } + + if (dmap->buffsize > 65536) + { + cmn_err (CE_WARN, "Recording buffer bigger than 64k\n"); + dmap->buffsize = 65536; + } + +#ifdef sun1 + if (dmap->buffsize == 36864) + { + dmap->buffsize = 32768; + } +#endif + + switch (dmap->buffsize) + { + case 4096: + sz = 15; + break; + case 8192: + sz = 19; + break; + case 16384: + sz = 23; + break; + case 32768: + sz = 27; + break; + case 65536: + sz = 31; + break; + + default: + cmn_err (CE_WARN, "Unsupported input buffer size %d\n", dmap->buffsize); + return OSS_ENOSPC; + } + + if (portc->input_type == ITYPE_SPDIF) + { + sblive_write_reg (devc, SPRA, 0, dmap->dmabuf_phys); + sblive_write_reg (devc, SPRC, 0, 0); + } + else + { + sblive_write_reg (devc, ADCBA, 0, dmap->dmabuf_phys); + sblive_write_reg (devc, ADCBS, 0, 0); + } + portc->in_szbits = sz; + + sblive_write_reg (devc, ADCSR, 0, 0x0); + + if (portc->input_type == ITYPE_ANALOG) + { + if (devc->feature_mask & SB_AUDIGY) + sblive_write_reg (devc, ADCSR, 0, + speed_tab[portc->speedsel].audigy_recbits); + else + sblive_write_reg (devc, ADCSR, 0, speed_tab[portc->speedsel].recbits); + } + + devc->recording_dev = dev; + + portc->audio_active |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + + return 0; +} + +/*ARGSUSED*/ +static int +sblive_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + + int voiceL = portc->voice_chn, voiceR = portc->voice_chn + 1; + + if (audio_engines[dev]->flags & ADEV_NOOUTPUT) + return OSS_EIO; + + /* AC3 needs stereo too */ + if (portc->format == AFMT_AC3 || portc->input_type == ITYPE_SPDIF) + { + portc->channels = 2; + portc->speed = 48000; + portc->routing = spdif_routing; + } + else + portc->routing = default_routing; + + /* Left channel */ + sblive_write_reg (devc, IFA, voiceL, 0xffff); /* Intial filter cutoff and attenuation */ + sblive_write_reg (devc, VEDS, voiceL, 0x0); /* Volume envelope decay and sustain */ + sblive_write_reg (devc, VTFT, voiceL, 0xffff); /* Volume target and Filter cutoff target */ + sblive_write_reg (devc, PTAB, voiceL, 0x0); /* Pitch target and sends A and B */ + /* The same for right channel */ + sblive_write_reg (devc, IFA, voiceR, 0xffff); + sblive_write_reg (devc, VEDS, voiceR, 0x0); + sblive_write_reg (devc, VTFT, voiceR, 0xffff); + sblive_write_reg (devc, PTAB, voiceR, 0x0); + + /* now setup the voices and go! */ + setup_audio_voice (dev, voiceL, LEFT_CH); + if (portc->channels == 2) + setup_audio_voice (dev, voiceR, RIGHT_CH); + + portc->audio_active |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + + if (portc->uses_spdif) + { + if (portc->format == AFMT_AC3) + { + sblive_write_reg (devc, SCS0, 0, 0x2109206); + } + else + { + sblive_write_reg (devc, SCS0, 0, 0x2108504); + } + } + return 0; +} + +static int +sblive_alloc_buffer (int dev, dmap_t * dmap, int direction) +{ + int err, i, n; + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + + if (dmap->dmabuf != NULL) + return 0; + + if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0) + { + cmn_err (CE_WARN, "Cannot allocate DMA buffer\n"); + return err; + } + + if (dmap->buffsize > DMABUF_SIZE) + { + cmn_err (CE_NOTE, "DMA buffer was too large - truncated\n"); + dmap->buffsize = DMABUF_SIZE; + } + + if (devc->feature_mask & SB_LIVE) + if (dmap->dmabuf_phys & 0x80000000) + { + cmn_err (CE_CONT, "Got DMA buffer address beyond 2G limit.\n"); + oss_free_dmabuf (dev, dmap); + dmap->dmabuf = NULL; + + return OSS_ENOSPC; + } + + if (direction == PCM_ENABLE_OUTPUT) + { + dmap->driver_use_value = portc->memptr; + n = portc->memptr / 4096; + +/* + * Fill the page table + */ + for (i = 0; i < dmap->buffsize / 4096; i++) + { + FILL_PAGE_MAP_ENTRY (n + i, dmap->dmabuf_phys + i * 4096); + } + } + + return 0; +} + +/*ARGSUSED*/ +static int +sblive_free_buffer (int dev, dmap_t * dmap, int direction) +{ + if (dmap->dmabuf == NULL) + return 0; + oss_free_dmabuf (dev, dmap); + + dmap->dmabuf = NULL; + return 0; +} + +/*ARGSUSED*/ +static int +sblive_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + sblive_devc *devc = audio_engines[dev]->devc; + sblive_portc *portc = audio_engines[dev]->portc; + int pos; + + if (!(portc->trigger_bits & direction)) + return 0; + + if (direction == OPEN_WRITE) + { + pos = sblive_read_reg (devc, QKBCA, portc->voice_chn) & 0x00ffffff; /* Curr pos */ + pos <<= portc->out_sz; + pos -= dmap->driver_use_value; + } + else + { + /* Compute current pos based on the wall clock register */ + pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff; + if (pos > portc->rec_starttime) + pos = pos - portc->rec_starttime; + else + pos = 0xfffff - (portc->rec_starttime - pos); + pos = (pos * (portc->speed / 25)) / (48000 / 25); + pos *= 2; /* 16 bit->bytes */ + pos *= portc->channels; + pos = pos % dmap->bytes_in_use; + } + + if (pos < 0) + pos = 0; + + return pos; +} + +static audiodrv_t sblive_audio_driver = { + sblive_audio_open, + sblive_audio_close, + sblive_audio_output_block, + sblive_audio_start_input, + sblive_audio_ioctl, + sblive_audio_prepare_for_input, + sblive_audio_prepare_for_output, + sblive_audio_reset, + NULL, + NULL, + sblive_audio_reset_input, + sblive_audio_reset_output, + sblive_audio_trigger, + sblive_audio_set_rate, + sblive_audio_set_format, + sblive_audio_set_channels, + NULL, + NULL, + NULL, /* sblive_check_input, */ + NULL, /* sblive_check_output, */ + sblive_alloc_buffer, + sblive_free_buffer, + NULL, + NULL, + sblive_get_buffer_pointer, + NULL, + NULL, + NULL, + NULL, + NULL, + sblive_audio_ioctl /* bind */ +}; + +#define DATAPORT (devc->base) +#define COMDPORT (devc->base+1) +#define STATPORT (devc->base+1) + +static __inline__ int +audigyuart_status (sblive_devc * devc) +{ + return sblive_read_reg (devc, MUASTAT, 0); +} + +#define input_avail(devc) (!(audigyuart_status(devc)&INPUT_AVAIL)) +#define output_ready(devc) (!(audigyuart_status(devc)&OUTPUT_READY)) +static void +audigyuart_cmd (sblive_devc * devc, unsigned char cmd) +{ + sblive_write_reg (devc, MUACMD, 0, cmd); +} + +static __inline__ int +audigyuart_read (sblive_devc * devc) +{ + return sblive_read_reg (devc, MUADAT, 0); +} + +static __inline__ void +audigyuart_write (sblive_devc * devc, unsigned char byte) +{ + sblive_write_reg (devc, MUADAT, 0, byte); +} + +#define OUTPUT_READY 0x40 +#define INPUT_AVAIL 0x80 +#define MPU_ACK 0xFE +#define MPU_RESET 0xFF +#define UART_MODE_ON 0x3F + +static int reset_audigyuart (sblive_devc * devc); +static void enter_uart_mode (sblive_devc * devc); + +typedef struct +{ + int keycode; + int action; + int local_action; +} ir_code_t; + +static void +sblive_key_action (sblive_devc * devc, ir_code_t * code) +{ + int value, left, right, dev; + + dev = devc->mixer_dev; + + switch (code->local_action) + { + case 1: /* Volume- */ + value = sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_READ, 0); + left = value & 0xff; + right = (value >> 8) & 0xff; + left -= 5; + if (left < 0) + left = 0; + right -= 5; + if (right < 0) + right = 0; + value = left | (right << 8); + sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_WRITE, value); + mixer_devs[dev]->modify_counter++; + return; + break; + + case 2: /* Volume+ */ + value = sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_READ, 0); + left = value & 0xff; + right = (value >> 8) & 0xff; + left += 5; + if (left > 100) + left = 100; + right += 5; + if (right > 100) + right = 100; + value = left | (right << 8); + sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_WRITE, value); + mixer_devs[dev]->modify_counter++; + return; + break; + } +} + +static void +sblive_handle_ir (sblive_devc * devc, unsigned char c) +{ +/* + * Receive a MIDI SysEx message and check if it's an IR remote command + */ +#if 1 + +/* + * Sysex code sent by the Live!DRIVE IR unit + */ + static unsigned char remote_id[] = + { 0xf0, 0x00, 0x20, 0x21, 0x60, 0x00, 0x02, 0x00, 0xf7 }; + + static ir_code_t codes[] = { + /* Creative RM-900B remote control unit */ + {0x09017e}, /* 0 */ + {0x0a512e}, /* 1 */ + {0x0a710e}, /* 2 */ + {0x090976}, /* 3 */ + {0x09512e}, /* 4 */ + {0x09215e}, /* 5 */ + {0x091e61}, /* 6 */ + {0x0a116e}, /* 7 */ + {0x0a413e}, /* 8 */ + {0x0a6e11}, /* 9 */ + {0x0a1e61}, /* Play/Pause */ + {0x0a215e}, /* Stop/Eject */ + {0x0a3e41}, /* Slow */ + {0x0a7e01}, /* Prev */ + {0x095e21}, /* Next */ + {0x097e01}, /* Step */ + {0x097609}, /* Mute */ + {0x0a4639, 0, 1}, /* Vol- */ + {0x094639, 0, 2}, /* Vol+ */ + /* Speaker ??? */ + {0x09314e}, /* EAX */ + {0x09413e}, /* Options */ + {0x096e11}, /* Display */ + {0x09710e}, /* Return */ + {0x09116e}, /* Start */ + {0x093e41}, /* Cancel */ + {0x0a5e21}, /* Up */ + {0x0a611e}, /* << */ + {0x0a017e}, /* Select/OK */ + {0x0a2e51}, /* >> */ + {0x0a314e}, /* Down */ + +/* Creative RM-1000 remote control unit */ + {0x0a0679}, /* Power */ + {0x0a0e71}, /* CMSS */ + {0x0a4e31}, /* Rec */ + +/* Creative Inspire 5.1 Digital 5700 remote */ + {0x0a0778}, /* Power */ + {0x097708}, /* Mute */ + {0x0a7708}, /* Test */ + {0x0a4738}, /* Vol- */ + {0x094738}, /* Vol+ */ + {0x0a0f70}, /* Effect */ + {0x0a5728}, /* Analog */ + {0x0a2758}, /* Pro logic */ + {0x094f30}, /* Dynamic mode */ + {0x093748}, /* Digital/PCM audio */ + {0} + }; +#endif + if (c == 0xf0) /* Sysex start */ + { + devc->sysex_buf[0] = c; + devc->sysex_p = 1; + return; + } + + if (devc->sysex_p <= 0) + return; + + if (devc->sysex_p >= 20) /* Too long */ + { + devc->sysex_p = 0; + return; + } + + if (c == 0xf7) /* Sysex end */ + { + int i, l, v; + unsigned char *buf; + + devc->sysex_buf[devc->sysex_p] = c; + devc->sysex_p++; + l = devc->sysex_p; + + devc->sysex_p = 0; + buf = devc->sysex_buf; + + if (l == 9) + { + int ok = 1; + + for (i = 0; i < sizeof (remote_id); i++) + if (buf[i] != remote_id[i]) + ok = 0; + + if (ok) + { + /* cmn_err (CE_CONT, "Live!DRIVE IR detected\n"); */ + return; + } + + return; + } + + if (l != 13) /* Wrong length */ + return; + + if (buf[0] != 0xf0 || buf[12] != 0xf7) /* Not sysex */ + return; + + /* Verify that this is an IR receiver sysex */ + if (buf[1] != 0x00 || buf[2] != 0x20 || buf[3] != 0x21) + return; + if (buf[4] != 0x60 || buf[5] != 0x00 || buf[6] != 0x01) + return; +#if 0 + if (buf[7] != 0x09 && buf[7] != 0x0a) /* Remote ID */ + return; +#endif + if (buf[8] != 0x41 || buf[9] != 0x44) + return; + + v = (buf[7] << 16) | (buf[10] << 8) | buf[11]; + + for (i = 0; codes[i].keycode != 0; i++) + if (codes[i].keycode == v) + { + sblive_key_action (devc, &codes[i]); + return; + } + + return; + } + + /* Ordinary byte */ + devc->sysex_buf[devc->sysex_p] = c; + devc->sysex_p++; +} + +static void +sblive_ir_callback (int dev, unsigned char c) +{ + sblive_devc *devc; + oss_device_t *osdev = midi_devs[dev]->osdev; + devc = osdev->devc; + + if (devc->midi_dev != dev) + return; + + sblive_handle_ir (devc, c); +} + +static void +audigyuart_input_loop (sblive_devc * devc) +{ + int t = 0; + + while (input_avail (devc) && t++ < 1000) + { + unsigned char c = audigyuart_read (devc); + + sblive_handle_ir (devc, c); + + if (c == MPU_ACK) + devc->input_byte = c; + else if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) + devc->midi_input_intr (devc->midi_dev, c); + } +} + +static void +audigyuartintr (sblive_devc * devc) +{ + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + audigyuart_input_loop (devc); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static int +audigyuart_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, + oss_midi_inputbuf_t inputbuf, + oss_midi_outputintr_t outputintr) +{ + sblive_devc *devc = (sblive_devc *) midi_devs[dev]->devc; + + if (devc->midi_opened) + { + return OSS_EBUSY; + } + + while (input_avail (devc)) + audigyuart_read (devc); + + devc->midi_input_intr = inputbyte; + devc->midi_opened = mode; + enter_uart_mode (devc); + devc->midi_disabled = 0; + + return 0; +} + +/*ARGSUSED*/ +static void +audigyuart_close (int dev, int mode) +{ + sblive_devc *devc = (sblive_devc *) midi_devs[dev]->devc; + + reset_audigyuart (devc); + oss_udelay (10); + enter_uart_mode (devc); + reset_audigyuart (devc); + devc->midi_opened = 0; +} + +static int +audigyuart_out (int dev, unsigned char midi_byte) +{ + sblive_devc *devc = (sblive_devc *) midi_devs[dev]->devc; + + if (devc->midi_disabled) + return 1; + + if (!output_ready (devc)) + { + return 0; + } + + audigyuart_write (devc, midi_byte); + return 1; +} + +/*ARGSUSED*/ +static int +audigyuart_ioctl (int dev, unsigned cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static midi_driver_t audigy_midi_driver = { + audigyuart_open, + audigyuart_close, + audigyuart_ioctl, + audigyuart_out +}; + +static void +enter_uart_mode (sblive_devc * devc) +{ + int ok, timeout; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--); + + devc->input_byte = 0; + audigyuart_cmd (devc, UART_MODE_ON); + + ok = 0; + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (devc->input_byte == MPU_ACK) + ok = 1; + else if (input_avail (devc)) + if (audigyuart_read (devc) == MPU_ACK) + ok = 1; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +void +attach_audigyuart (sblive_devc * devc) +{ + enter_uart_mode (devc); + + devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIGY", "Audigy UART", &audigy_midi_driver, sizeof (midi_driver_t), + 0, devc, devc->osdev); + devc->midi_opened = 0; +} + +static int +reset_audigyuart (sblive_devc * devc) +{ + int ok, timeout, n; + oss_native_word flags; + + /* + * Send the RESET command. Try again if no success at the first time. + */ + + ok = 0; + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + for (n = 0; n < 2 && !ok; n++) + { + for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--); + + devc->input_byte = 0; + audigyuart_cmd (devc, MPU_RESET); + + /* + * Wait at least 25 msec. This method is not accurate so let's make the + * loop bit longer. Cannot sleep since this is called during boot. + */ + + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (devc->input_byte == MPU_ACK) /* Interrupt */ + ok = 1; + else if (input_avail (devc)) + if (audigyuart_read (devc) == MPU_ACK) + ok = 1; + + } + + + + if (ok) + audigyuart_input_loop (devc); /* + * Flush input before enabling interrupts + */ + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return ok; +} + +int +probe_audigyuart (sblive_devc * devc) +{ + int ok = 0; + + DDB (cmn_err (CE_CONT, "Entered probe_audigyuart\n")); + + devc->midi_input_intr = NULL; + devc->midi_opened = 0; + devc->input_byte = 0; + + ok = reset_audigyuart (devc); + + if (ok) + { + DDB (cmn_err (CE_CONT, "Reset UART401 OK\n")); + } + else + { + DDB (cmn_err + (CE_CONT, "Reset UART401 failed (no hardware present?).\n")); + DDB (cmn_err + (CE_CONT, "mpu401 status %02x\n", audigyuart_status (devc))); + } + + DDB (cmn_err (CE_CONT, "audigyuart detected OK\n")); + return ok; +} + +void +unload_audigyuart (sblive_devc * devc) +{ + reset_audigyuart (devc); +} + +static void +attach_mpu (sblive_devc * devc) +{ + char tmp[128]; + int ndevs = num_mididevs; + oss_native_word flags; + + sprintf (tmp, "%s external MIDI", devc->card_name); + + if (devc->feature_mask & SB_AUDIGY) + { + if (!probe_audigyuart (devc)) + { + cmn_err (CE_NOTE, "MIDI UART was not detected\n"); + return; + } + DDB (cmn_err (CE_CONT, "SB Audigy: MIDI UART detected - Good\n")); + devc->mpu_attached = 1; + attach_audigyuart (devc); + } + else + { + MUTEX_ENTER (devc->mutex, flags); + if (uart401_init (&devc->uart401devc, devc->osdev, devc->base + 0x18, + tmp) >= 0) + devc->mpu_attached = 1; + MUTEX_EXIT (devc->mutex, flags); + + if (ndevs != num_mididevs) + { + devc->midi_dev = ndevs; + midi_devs[ndevs]->input_callback = sblive_ir_callback; + } + } +} + +static void +load_dsp (sblive_devc * devc, unsigned char *buf, int len) +{ + emu10k1_file *code; + int pc, i; + + if (len != sizeof (*code)) + { + cmn_err (CE_NOTE, "DSP file size mismatch\n"); + return; + } + + code = (emu10k1_file *) buf; + + for (pc = 0; pc < 1024; pc++) + { + write_efx (devc, UC0 + pc, code->code[pc]); + } + + if (code->parms.ngpr < MAX_GPR_PARMS) + for (i = 0; i < code->parms.ngpr; i++) + { + code->parms.gpr[i].name[GPR_NAME_SIZE - 1] = 0; /* Overflow protection */ + if (strlen (code->parms.gpr[i].name) >= 32) /* Name may be bad */ + { + return; + } + +/* cmn_err(CE_CONT, "Gpr %d = %s (vol %x) type=%x\n", gpr->gpr[i].num, gpr->gpr[i].name, gpr->gpr[i].def, gpr->gpr[i].type); */ + if (code->parms.gpr[i].num < MAX_GPR) + if (code->parms.gpr[i].type != MIXT_GROUP) + { + if (is_special_gpr (code->parms.gpr[i].num)) + sblive_set_gpr (devc->mixer_dev, code->parms.gpr[i].num, + SNDCTL_MIX_WRITE, + devc->gpr_values[code->parms.gpr[i].num]); + else + sblive_set_gpr (devc->mixer_dev, code->parms.gpr[i].num, + SNDCTL_MIX_WRITE, code->parms.gpr[i].def); + } + } + + + if (devc->gpr == NULL) + { + devc->gpr = PMALLOC (devc->osdev, sizeof (gpr_info)); + if (devc->gpr == NULL) + { + cmn_err (CE_WARN, "Out of memory (gpr)\n"); + return; + } + memset (devc->gpr, 0, sizeof (gpr_info)); + } + memcpy (devc->gpr, &code->parms, sizeof (gpr_info)); + create_efx_mixer (devc->mixer_dev); + + if (code->consts.nconst >= MAX_CONST_PARMS) + return; + + for (i = 0; i < code->consts.nconst; i++) + { + if (code->consts.consts[i].gpr >= MAX_GPR) + { + return; + } + + sblive_write_reg (devc, code->consts.consts[i].gpr + GPR0, 0, + code->consts.consts[i].value); + } +} + +#define LIVE_NOP() \ + write_efx(devc, UC0+(pc*2), 0x10040); \ + write_efx(devc, UC0+(pc*2+1), 0x610040);pc++ +#define LIVE_ACC3(r, a, x, y) /* z=w+x+y */ \ + write_efx(devc, UC0+(pc*2), (x << 10) | y); \ + write_efx(devc, UC0+(pc*2+1), (6 << 20) | (r << 10) | a);pc++ + +#define AUDIGY_ACC3(r, a, x, y) /* z=w+x+y */ \ + write_efx(devc, UC0+(pc*2), (x << 12) | y); \ + write_efx(devc, UC0+(pc*2+1), (6 << 24) | (r << 12) | a);pc++ +#define AUDIGY_NOP() AUDIGY_ACC3(0xc0, 0xc0, 0xc0, 0xc0) + +static int +init_effects (sblive_devc * devc) +{ + int i; + unsigned short pc; + + if (devc->feature_mask & SB_AUDIGY) + { + pc = 0; + for (i = 0; i < 512; i++) + { + AUDIGY_NOP (); + } + + for (i = 0; i < 256; i++) + write_efx (devc, GPR0 + i, 0); + sblive_write_reg (devc, AUDIGY_DBG, 0, 0); + load_dsp (devc, emu10k2_dsp, sizeof (emu10k2_dsp)); + } + else + { + pc = 0; + for (i = 0; i < 512; i++) + { + LIVE_NOP (); + } + + for (i = 0; i < 256; i++) + write_efx (devc, GPR0 + i, 0); + sblive_write_reg (devc, DBG, 0, 0); + load_dsp (devc, emu10k1_dsp, sizeof (emu10k1_dsp)); + } + + return 1; +} + +static void +init_emu10k1 (sblive_devc * devc) +{ + unsigned int tmp, i; + extern int sblive_memlimit; +#ifndef NO_EMU10K1_SYNTH + extern int sblive_synth_enable; +#endif + int xmem_mode = 0; + unsigned int reg, val; + extern int sblive_digital_din; + extern int audigy_digital_din; + oss_native_word phaddr; + unsigned int memlimit = MEMLIMIT_31BITS; + + OUTL (devc->osdev, 0x00000000, devc->base + 0x0c); /* Intr disable */ + OUTL (devc->osdev, + HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, + devc->base + 0x14); + + sblive_write_reg (devc, MBS, 0, 0x0); + sblive_write_reg (devc, MBA, 0, 0x0); + sblive_write_reg (devc, FXBS, 0, 0x0); + sblive_write_reg (devc, FXBA, 0, 0x0); + sblive_write_reg (devc, ADCBS, 0, 0x0); + sblive_write_reg (devc, ADCBA, 0, 0x0); + + sblive_write_reg (devc, CLIEL, 0, 0x0); + sblive_write_reg (devc, CLIEH, 0, 0x0); + sblive_write_reg (devc, SOLL, 0, 0xffffffff); + sblive_write_reg (devc, SOLH, 0, 0xffffffff); + + + if (devc->feature_mask & SB_AUDIGY) + { + memlimit=MEMLIMIT_32BITS; + sblive_write_reg (devc, 0x5e, 0, 0xf00); /* ?? */ + sblive_write_reg (devc, 0x5f, 0, 0x3); /* ?? */ + } + +#ifndef NO_EMU10K1_SYNTH + if (!sblive_synth_enable) + sblive_memlimit = 0; +#endif + + if (sblive_memlimit < 4096) /* Given in megabytes */ + sblive_memlimit *= (1024 * 1024); + + devc->max_mem = sblive_memlimit; + if (devc->max_mem < 1024 * 1024) + devc->max_mem = 1024 * 1024; + + devc->max_mem += AUDIO_MEMSIZE; + + /* SB Live/Audigy supports at most 32M of memory) */ + if (devc->max_mem > 32 * 1024 * 1024) + devc->max_mem = 32 * 1024 * 1024; + + devc->max_pages = devc->max_mem / 4096; + if (devc->max_pages < 1024) + devc->max_pages = 1024; + devc->page_map = + (int *) CONTIG_MALLOC (devc->osdev, devc->max_pages * 4, memlimit, + &phaddr, devc->page_map_dma_handle); + devc->vpage_map = + KERNEL_MALLOC (devc->max_pages * sizeof (unsigned char *)); + if (devc->page_map == NULL || devc->vpage_map == NULL) + { + cmn_err (CE_WARN, "Can't allocate the PTBA table\n"); + return; + } + memset (devc->vpage_map, 0, devc->max_pages * 4); + + tmp = phaddr; + if (devc->feature_mask & SB_LIVE) + { + if (tmp & 0x80000000) + { + cmn_err (CE_CONT, + "SB Live Error: Page table is beyond the 2G limit\n"); + } + } + else + { + if (tmp & 0x80000000) + { + DDB (cmn_err (CE_CONT, "Audigy: Using 4G PCI addressing mode\n")); + xmem_mode = 1; + devc->emu_page_shift = 0; + if (devc->max_mem > 16 * 1024 * 1034) + { + devc->max_mem = 16 * 1024 * 1024; + + DDB (cmn_err + (CE_NOTE, + "Max memory dropped to 16M due to need for extended PCI address mode.\n")); + } + } + } + + devc->synth_memlimit = devc->max_mem - AUDIO_MEMSIZE; + devc->synth_membase = SYNTH_MEMBASE; + devc->synth_memtop = devc->synth_membase; + devc->synth_memptr = devc->synth_membase; + + devc->silent_page = + (int *) CONTIG_MALLOC (devc->osdev, 4096, memlimit, &phaddr, devc->silent_page_dma_handle); + if (devc->silent_page == NULL) + { + cmn_err (CE_WARN, "Can't allocate a silent page\n"); + return; + } + + devc->silent_page_phys = phaddr; + if (devc->feature_mask & SB_LIVE) + if (devc->silent_page_phys & 0x80000000) + { + cmn_err (CE_CONT, + "SB Live warning: Silent page is beyond the 2G limit\n"); + } + + devc->audio_memptr = 4096; /* Skip the silence page */ + memset (devc->silent_page, 0, 4096); + + for (i = 0; i < devc->max_pages; i++) + { + FILL_PAGE_MAP_ENTRY (i, devc->silent_page_phys); + devc->vpage_map[i] = NULL; + } + + for (i = 0; i < 64; i++) + sblive_init_voice (devc, i); + + if (devc->feature_mask & SB_AUDIGY) + { + sblive_write_reg (devc, SCS0, 0, 0x2108504); + sblive_write_reg (devc, SCS1, 0, 0x2108504); + sblive_write_reg (devc, SCS2, 0, 0x2108504); + } + else + { + sblive_write_reg (devc, SCS0, 0, 0x2109204); + sblive_write_reg (devc, SCS1, 0, 0x2109204); + sblive_write_reg (devc, SCS2, 0, 0x2109204); + } + + sblive_write_reg (devc, PTBA, 0, tmp); + tmp = sblive_read_reg (devc, PTBA, 0); + + sblive_write_reg (devc, TCBA, 0, 0x0); + sblive_write_reg (devc, TCBS, 0, 0x4); + + OUTL (devc->osdev, IE_RXA | IE_AB | IE_IT, devc->base + IE); /* Intr enable */ + +/* + * SB Live 5.1 support. Turn on S/PDIF output + */ + if (devc->subvendor == 0x80611102) /* Live 5.1 */ + { + tmp = INL (devc->osdev, devc->base + 0x14); + tmp |= 0x00001000; /* Turn GPO0 pin on to enable S/PDIF outputs */ + OUTL (devc->osdev, tmp, devc->base + 0x14); + } + + if (devc->subvendor == 0x80661102) + { + sblive_write_reg (devc, AC97SLOT, 0, + AC97SLOT_CENTER | AC97SLOT_LFE | AC97SLOT_REAR_LEFT | + AC97SLOT_REAR_RIGHT); + } + + if (devc->feature_mask & SB_AUDIGY2) + { + /* Enable analog outputs on Audigy2 */ + int tmp; + + /* Setup SRCMulti_I2S SamplingRate */ + tmp = sblive_read_reg (devc, EHC, 0); + tmp &= 0xfffff1ff; + tmp |= (0x2 << 9); + sblive_write_reg (devc, EHC, 0, tmp); + /* sblive_write_reg (devc, SOC, 0, 0x00000000); */ + + /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */ + OUTL (devc->osdev, 0x600000, devc->base + 0x20); + OUTL (devc->osdev, 0x14, devc->base + 0x24); + + /* Setup SRCMulti Input Audio Enable */ + /* Setup SRCMulti Input Audio Enable */ + if (devc->feature_mask & SB_AUDIGY2VAL) + OUTL (devc->osdev, 0x7B0000, devc->base + 0x20); + else + OUTL (devc->osdev, 0x6E0000, devc->base + 0x20); + + OUTL (devc->osdev, 0xFF00FF00, devc->base + 0x24); + + /* Setup I2S ASRC Enable (HC register) */ + tmp = INL (devc->osdev, devc->base + 0x14); + tmp |= 0x00000070; + OUTL (devc->osdev, tmp, devc->base + 0x14); + + /* + * Unmute Analog now. Set GPO6 to 1 for Apollo. + * This has to be done after init ALice3 I2SOut beyond 48KHz. + * So, sequence is important + */ + tmp = INL (devc->osdev, devc->base + 0x18); + tmp |= 0x0040; + if (devc->feature_mask & SB_AUDIGY2VAL) + tmp |= 0x0060; + + OUTL (devc->osdev, tmp, devc->base + 0x18); + } + + sblive_write_reg (devc, SOLL, 0, 0xffffffff); + sblive_write_reg (devc, SOLH, 0, 0xffffffff); + + if (devc->feature_mask & SB_AUDIGY) + { + unsigned int mode = 0; + + if (devc->feature_mask & SB_AUDIGY2) + mode |= HCFG_AC3ENABLE_GPSPDIF | HCFG_AC3ENABLE_CDSPDIF; + if (xmem_mode) + { + OUTL (devc->osdev, + HCFG_AUDIOENABLE | HCFG_AUTOMUTE | HCFG_JOYENABLE | + A_HCFG_VMUTE | A_HCFG_AUTOMUTE | A_HCFG_XM | mode, + devc->base + 0x14); + } + else + OUTL (devc->osdev, + HCFG_AUDIOENABLE | HCFG_AUTOMUTE | HCFG_JOYENABLE | A_HCFG_VMUTE + | A_HCFG_AUTOMUTE | mode, devc->base + 0x14); + + OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x18) | 0x0004, devc->base + 0x18); /* GPIO (S/PDIF enable) */ + + + /* enable IR port */ + tmp = INL (devc->osdev, devc->base + 0x18); + OUTL (devc->osdev, tmp | A_IOCFG_GPOUT2, devc->base + 0x18); + oss_udelay (500); + OUTL (devc->osdev, tmp | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, + devc->base + 0x18); + oss_udelay (100); + OUTL (devc->osdev, tmp, devc->base + 0x18); + } + else + OUTL (devc->osdev, + HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE | + HCFG_JOYENABLE, devc->base + 0x14); + + + /* enable IR port */ + tmp = INL (devc->osdev, devc->base + 0x14); + OUTL (devc->osdev, tmp | HCFG_GPOUT2, devc->base + 0x14); + oss_udelay (500); + OUTL (devc->osdev, tmp | HCFG_GPOUT1 | HCFG_GPOUT2, devc->base + 0x14); + oss_udelay (100); + OUTL (devc->osdev, tmp, devc->base + 0x14); + + /* Switch the shared SPDIF/OUT3 to DIGITAL or ANALOG mode */ + /* depending on whether the port is SPDIF or analog */ + + if ((devc->feature_mask == SB_AUDIGY) || + ((devc->feature_mask & SB_AUDIGY2) && (audigy_digital_din == 0))) + { + reg = INL (devc->osdev, devc->base + 0x18) & ~A_IOCFG_GPOUT0; + val = (audigy_digital_din) ? 0x4 : 0; + reg |= val; + OUTL (devc->osdev, reg, devc->base + 0x18); + } + if (devc->feature_mask & SB_LIVE) /* SBLIVE */ + { + reg = INL (devc->osdev, devc->base + 0x14) & ~HCFG_GPOUT0; + val = (sblive_digital_din) ? HCFG_GPOUT0 : 0; + reg |= val; + OUTL (devc->osdev, reg, devc->base + 0x14); + } + +} + +void +sblive_init_voice (sblive_devc * devc, int voice) +{ + sblive_set_loop_stop (devc, voice, 1); + + sblive_write_reg (devc, VEDS, voice, 0x0); + sblive_write_reg (devc, IP, voice, 0x0); + sblive_write_reg (devc, VTFT, voice, 0xffff); + sblive_write_reg (devc, CVCF, voice, 0xffff); + sblive_write_reg (devc, PTAB, voice, 0x0); + sblive_write_reg (devc, CPF, voice, 0x0); + sblive_write_reg (devc, CCR, voice, 0x0); + sblive_write_reg (devc, SCSA, voice, 0x0); + sblive_write_reg (devc, SDL, voice, 0x10); + sblive_write_reg (devc, QKBCA, voice, 0x0); + sblive_write_reg (devc, Z1, voice, 0x0); + sblive_write_reg (devc, Z2, voice, 0x0); + + if (devc->feature_mask & SB_AUDIGY) + sblive_write_reg (devc, SRDA, voice, 0x03020100); + sblive_write_reg (devc, FXRT, voice, 0x32100000); + + sblive_write_reg (devc, MEHA, voice, 0x0); + sblive_write_reg (devc, MEDS, voice, 0x0); + sblive_write_reg (devc, IFA, voice, 0xffff); + sblive_write_reg (devc, PEFE, voice, 0x0); + sblive_write_reg (devc, VFM, voice, 0x0); + sblive_write_reg (devc, TMFQ, voice, 24); + sblive_write_reg (devc, VVFQ, voice, 24); + sblive_write_reg (devc, TMPE, voice, 0x0); + sblive_write_reg (devc, VLV, voice, 0x0); + sblive_write_reg (devc, MLV, voice, 0x0); + sblive_write_reg (devc, VEHA, voice, 0x0); + sblive_write_reg (devc, VEV, voice, 0x0); + sblive_write_reg (devc, MEV, voice, 0x0); + + if (devc->feature_mask & SB_AUDIGY) + { + sblive_write_reg (devc, CSBA, voice, 0x0); + sblive_write_reg (devc, CSDC, voice, 0x0); + sblive_write_reg (devc, CSFE, voice, 0x0); + sblive_write_reg (devc, CSHG, voice, 0x0); + sblive_write_reg (devc, SRHE, voice, 0x3f3f3f3f); + } +} + +#ifndef NO_EMU10K1_SYNTH +extern void sblive_install_synth (sblive_devc * devc); +extern void sblive_remove_synth (sblive_devc * devc); +#endif + +static const unsigned char peak_cnv[256] = { + 0, 18, 29, 36, 42, 47, 51, 54, 57, 60, 62, 65, 67, 69, 71, 72, + 74, 75, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 89, 90, + 91, 92, 93, 93, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101, + 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, + 108, + 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114, + 114, + 115, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119, + 119, + 119, 119, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, + 123, + 123, 123, 124, 124, 124, 124, 125, 125, 125, 125, 125, 126, 126, 126, 126, + 126, + 127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, + 130, + 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132, + 132, + 132, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 135, + 135, + 135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137, + 137, + 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, + 139, + 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, + 141, + 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143, + 143, + 143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144, + 144, +}; + +static void +set_equalizer (sblive_devc * devc, int ctrl, int band, int value) +{ + const unsigned int *row; + int i; + + switch (band) + { + case 0: + row = (unsigned int *) &eq_band1_data[value][0]; + break; + case 1: + row = (unsigned int *) &eq_band2_data[value][0]; + break; + case 2: + row = (unsigned int *) &eq_band3_data[value][0]; + break; + case 3: + row = (unsigned int *) &eq_band4_data[value][0]; + break; + + default: + cmn_err (CE_CONT, "%s: bad equalizer band %d\n", devc->card_name, band); + return; + } + + for (i = 0; i < 5; i++) + { + sblive_write_reg (devc, ctrl + GPR0 + i, 0, row[i]); + } +} + +static const int db2lin_101[101] = { 0x00000000, + 0x0024B53A, 0x002750CA, 0x002A1BC6, 0x002D198D, 0x00304DBA, 0x0033BC2A, + 0x00376901, 0x003B58AF, 0x003F8FF1, 0x004413DF, 0x0048E9EA, 0x004E17E9, + 0x0053A419, 0x0059952C, 0x005FF24E, 0x0066C32A, 0x006E0FFB, 0x0075E18D, + 0x007E414F, 0x0087395B, 0x0090D482, 0x009B1E5B, 0x00A6234F, 0x00B1F0A7, + 0x00BE94A1, 0x00CC1E7C, 0x00DA9E8D, 0x00EA2650, 0x00FAC881, 0x010C9931, + 0x011FADDC, 0x01341D87, 0x014A00D8, 0x01617235, 0x017A8DE6, 0x01957233, + 0x01B23F8D, 0x01D118B1, 0x01F222D4, 0x021585D1, 0x023B6C57, 0x0264041D, + 0x028F7E19, 0x02BE0EBD, 0x02EFEE33, 0x032558A2, 0x035E8E7A, 0x039BD4BC, + 0x03DD7551, 0x0423BF61, 0x046F07B5, 0x04BFA91B, 0x051604D5, 0x0572830D, + 0x05D59354, 0x063FAD27, 0x06B15080, 0x072B0673, 0x07AD61CD, 0x0838FFCA, + 0x08CE88D3, 0x096EB147, 0x0A1A3A53, 0x0AD1F2E0, 0x0B96B889, 0x0C6978A5, + 0x0D4B316A, 0x0E3CF31B, 0x0F3FE155, 0x10553469, 0x117E3AD9, 0x12BC5AEA, + 0x14111457, 0x157E0219, 0x1704DC5E, 0x18A77A97, 0x1A67D5B6, 0x1C480A87, + 0x1E4A5C45, 0x2071374D, 0x22BF3412, 0x25371A37, 0x27DBE3EF, 0x2AB0C18F, + 0x2DB91D6F, 0x30F89FFD, 0x34733433, 0x382D0C46, 0x3C2AA6BD, 0x4070D3D9, + 0x4504BB66, 0x49EBE2F1, 0x4F2C346F, 0x54CC0565, 0x5AD21E86, 0x6145C3E7, + 0x682EBDBD, 0x6F9561C4, 0x77829D4D, + 0x7fffffff +}; + +static __inline__ int +convert_fixpoint (int val) +{ + if (val < 0) + val = 0; + if (val > 100) + val = 100; + return db2lin_101[val]; +} + +static int +sblive_set_gpr (int dev, int ctrl, unsigned int cmd, int value) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + int typ, i; + + if (devc == NULL) + return 0; + + if (devc->gpr == NULL) + { + int left, right; + + if (ctrl >= NEXT_FREE_GPR) + return 0; + + if (cmd != SNDCTL_MIX_WRITE) + return 0; + + left = value & 0xff; + right = (value >> 8) & 0xff; + + if (left < 0) + left = 0; + if (left > 100) + left = 100; + if (right < 0) + right = 0; + if (right > 100) + right = 100; + value = left | (right << 8); + devc->gpr_values[ctrl] = value; + + left = convert_fixpoint (left); + sblive_write_reg (devc, ctrl + GPR0, 0, left); + right = convert_fixpoint (right); + sblive_write_reg (devc, ctrl + GPR0 + 1, 0, right); + return value; + } + + if (ctrl < 0 || ctrl >= MAX_GPR) + return OSS_EIO; + + typ = MIXT_SLIDER; + for (i = 0; i < devc->gpr->ngpr; i++) + if (devc->gpr->gpr[i].num == ctrl && devc->gpr->gpr[i].type != MIXT_GROUP) + typ = devc->gpr->gpr[i].type; + + if (typ == MIXT_GROUP) + { + return OSS_EIO; + } + + if (cmd == SNDCTL_MIX_READ) + { + if (typ == MIXT_STEREOPEAK || typ == MIXT_STEREOVU) + { + int v, l, r; + + /* Get the sample values and scale them to 0-144 dB range */ + v = sblive_read_reg (devc, ctrl + GPR0, 0); + l = v >> 23; + + v = sblive_read_reg (devc, ctrl + GPR0 + 1, 0); + r = v >> 23; + + if (l < 0) + l = -l; + if (r < 0) + r = -r; + l = peak_cnv[l]; + r = peak_cnv[r]; + + /* Reset values back to 0 */ + sblive_write_reg (devc, ctrl + GPR0, 0, 0); + sblive_write_reg (devc, ctrl + GPR0 + 1, 0, 0); + + return l | (r << 8); + } + return devc->gpr_values[ctrl]; + } + + if (cmd == SNDCTL_MIX_WRITE) + { + switch (typ) + { + case MIXT_STEREOSLIDER: + { + int left, right; + + left = value & 0xff; + right = (value >> 8) & 0xff; + + if (left < 0) + left = 0; + if (left > 100) + left = 100; + if (right < 0) + right = 0; + if (right > 100) + right = 100; + value = left | (right << 8); + devc->gpr_values[ctrl] = value; + + left = convert_fixpoint (left); + sblive_write_reg (devc, ctrl + GPR0, 0, left); + right = convert_fixpoint (right); + sblive_write_reg (devc, ctrl + GPR0 + 1, 0, right); + } + break; + + case MIXT_ONOFF: + { + value = !!value; + devc->gpr_values[ctrl] = value; + + sblive_write_reg (devc, ctrl + GPR0, 0, value); + sblive_write_reg (devc, ctrl + GPR0 + 1, 0, !value); + } + break; + + case EMU_MIXT_EQ1: + case EMU_MIXT_EQ2: + case EMU_MIXT_EQ3: + case EMU_MIXT_EQ4: + { + int band; + + band = typ & 3; + value = value & 0xff; + set_equalizer (devc, ctrl, band, value); + devc->gpr_values[ctrl] = value; + } + break; + + default: + { + int tmp; + + value = value & 0xff; + if (value > 100) + value = 100; + + devc->gpr_values[ctrl] = value; + + tmp = convert_fixpoint (value); + sblive_write_reg (devc, ctrl + GPR0, 0, tmp); + } + } + + return value; + } + + return OSS_EINVAL; +} + +static int +sblive_set_vol (int dev, int ctrl, unsigned int cmd, int value) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + sblive_portc *portc; + + if (ctrl < 0 || ctrl >= devc->n_audiodevs) + return OSS_EINVAL; + + portc = &devc->portc[ctrl]; + + if (portc->input_type == ITYPE_SPDIF) + { + mixer_devs[dev]->modify_counter++; + return 100 | (100 << 8); + } + + if (cmd == SNDCTL_MIX_READ) + { +#ifdef TEST_3D + return (devc->portc[ctrl].playvol & 0x00ff) | + ((devc->portc[ctrl].playangle & 0xffff) << 16) | + ((devc->portc[ctrl].playdist & 0xff) << 8); +#else + return devc->portc[ctrl].playvol & 0xff; +#endif + } + + if (cmd == SNDCTL_MIX_WRITE) + { +#ifdef TEST_3D + int angle, dist; + angle = (value >> 16) & 0xffff; /* Rotation angle */ + dist = (value >> 8) & 0xff; /* Distance */ + value &= 0x00ff; /* Volume */ + + if (value < 0) + value = 0; + if (value > 100) + value = 100; + + switch (portc->speaker_mode) + { + case SMODE_FRONT: + angle = 0; + dist = 50; + break; + + case SMODE_SURR: + angle = 180; + dist = 50; + break; + + case SMODE_FRONTREAR: + angle = 0; + dist = 50; + break; + + case SMODE_3D: + break; + } + devc->portc[ctrl].playvol = value; + devc->portc[ctrl].playdist = dist; + devc->portc[ctrl].playangle = angle; + + update_output_device (devc, &devc->portc[ctrl]); + return (value & 0x00ff) | (angle << 16) | ((dist & 0xff) << 8); +#else + value &= 0xff; /* Only left channel */ + + if (value < 0) + value = 0; + if (value > 100) + value = 100; + devc->portc[ctrl].playvol = value; + + update_output_device (devc, &devc->portc[ctrl]); + return value; + +#endif + } + + return OSS_EINVAL; +} + +/*ARGSUSED*/ +static int +sblive_get_peak (int dev, int ctrl, unsigned int cmd, int value) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + + if (ctrl < 0 || ctrl >= devc->n_audiodevs) + return OSS_EINVAL; + + if (cmd == SNDCTL_MIX_READ) + { + int l, r, vol; + + l = devc->portc[ctrl].vu_left & 0xff; + r = devc->portc[ctrl].vu_right & 0xff; +#if 1 + vol = devc->portc[ctrl].playvol; + /* if (vol<1) vol=5; */ + l = (l * vol + 50) / 100; + r = (r * vol + 50) / 100; +#endif + devc->portc[ctrl].vu_left = 0; + devc->portc[ctrl].vu_right = 0; + + return peak_cnv[l] | (peak_cnv[r] << 8); + } + + return OSS_EINVAL; +} + +static int +sblive_set_parm (int dev, int ctrl, unsigned int cmd, int value) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + + if (cmd == SNDCTL_MIX_READ) + { + switch (ctrl) + { + case 1: + return devc->autoreset; + case 2: + return devc->speaker_mode; + } + } + + if (cmd == SNDCTL_MIX_WRITE) + { + switch (ctrl) + { + case 1: + return devc->autoreset = !!(value); + case 2: + if (devc->speaker_mode != value) + { + int i; + for (i = 0; i < devc->n_audiodevs; i++) + devc->portc[i].resetvol = 1; + } + return devc->speaker_mode = value; + break; + + } + } + + return OSS_EINVAL; +} + +static int +create_soft_mixer (int dev) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + int group = 0, err, i, n; + char tmp[100]; + + if ((err = mixer_ext_create_control (dev, 0, + 1, + sblive_set_parm, + MIXT_ONOFF, + "SBLIVE_AUTORESET", + 2, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + +#ifdef TEST_3D + n = 5; +#else + n = 4; +#endif + if ((err = mixer_ext_create_control (dev, 0, + 2, + sblive_set_parm, + MIXT_ENUM, + "SBLIVE_SPKMODE", + n, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + for (i = 0; i < devc->n_audiodevs; i++) + { + if (devc->n_audiodevs > devc->min_audiodevs) + { + /* + * Use the traditional dspN naming system for sliders. + */ + if ((i % 8) == 0) + if ((group = mixer_ext_create_group (dev, 0, "/dev")) < 0) + return group; + + sprintf (tmp, "@pcm%d", devc->portc[i].audiodev); + } + else + { + /* + * Use front/rear/etc naming style + */ + if ((i % 8) == 0) + if ((group = mixer_ext_create_group (dev, 0, "pcm")) < 0) + return group; + + switch (i) + { + case 0: + strcpy (tmp, "main"); + break; /* Duplex device */ + case 1: + strcpy (tmp, "front"); + break; + case 2: + strcpy (tmp, "side"); + break; + case 3: + strcpy (tmp, "C/L"); + break; + case 4: + strcpy (tmp, "rear"); + break; + } + } + + + if ((err = mixer_ext_create_control (dev, group, i, sblive_set_vol, +#ifdef TEST_3D + MIXT_3D, +#else + MIXT_SLIDER, +#endif + tmp, + 100, + MIXF_PCMVOL | MIXF_READABLE | MIXF_WRITEABLE)) < + 0) + return err; + if ((err = mixer_ext_create_control (dev, group, + i, + sblive_get_peak, + MIXT_STEREOPEAK, + "-", 144, MIXF_READABLE)) < 0) + return err; + } + + return 0; +} + +static int +create_efx_mixer (int dev) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + int group = 0, err = 0, i, mode; + int group_created = 0; + int typ, maxval; + + if (!devc->extinfo_loaded) + { + return 0; + } + + if (devc->gpr == NULL) + { + return 0; + } + + if (devc->mixer_group >= 0) + mixer_ext_truncate (dev, devc->mixer_group); + devc->mixer_group = -1; + + if (devc->gpr->ngpr >= MAX_GPR_PARMS) + return OSS_EINVAL; + + for (i = 0; i < devc->gpr->ngpr; i++) + { + if (devc->gpr->gpr[i].num >= MAX_GPR) + continue; + + typ = devc->gpr->gpr[i].type; + + if (typ == MIXT_GROUP) + { + if ((group = + mixer_ext_create_group (dev, 0, devc->gpr->gpr[i].name)) < 0) + return group; + + if (!group_created) + devc->mixer_group = group; + group_created = 1; + continue; + } + +#if 0 + if (!group_created) + { + cmn_err (CE_WARN, "Mixer initialization sequence error\n"); + return OSS_EINVAL; + } +#endif + mode = MIXF_READABLE; + maxval = 144; + + switch (typ) + { + case EMU_MIXT_EQ1: + case EMU_MIXT_EQ2: + case EMU_MIXT_EQ3: + case EMU_MIXT_EQ4: + { + mode |= MIXF_WRITEABLE; + maxval = 255; + typ = MIXT_SLIDER; + } + break; + + case MIXT_STEREOSLIDER: + case MIXT_SLIDER: + case MIXT_MONOSLIDER: + { + mode |= MIXF_WRITEABLE; + maxval = 100; + } + break; + + case MIXT_STEREOVU: + typ = MIXT_STEREOPEAK; + break; + + case MIXT_ONOFF: + { + mode |= MIXF_WRITEABLE; + maxval = 1; + } + break; + } + + if (devc->gpr->gpr[i].name[0] == '_') + { + /* Hidden control */ + if (strcmp (devc->gpr->gpr[i].name, "_PASSTHROUGH") == 0) + { + int ctrl = devc->gpr->gpr[i].num; + devc->passthrough_gpr = ctrl; + + sblive_write_reg (devc, ctrl + GPR0, 0, 1); + sblive_write_reg (devc, ctrl + GPR0 + 1, 0, 0); + } + } + else + { + /* Visible control */ + if ((err = mixer_ext_create_control (dev, group, + devc->gpr->gpr[i].num, + sblive_set_gpr, typ, + devc->gpr->gpr[i].name, + maxval, mode)) < 0) + return err; + } + + if (!group_created) + devc->mixer_group = err; + group_created = 1; + + if (is_special_gpr (devc->gpr->gpr[i].num)) + { + sblive_set_gpr (dev, devc->gpr->gpr[i].num, SNDCTL_MIX_WRITE, + devc->gpr_values[devc->gpr->gpr[i].num]); + } + else + { + sblive_set_gpr (dev, devc->gpr->gpr[i].num, SNDCTL_MIX_WRITE, + devc->gpr->gpr[i].def); + } + } + return 0; +} + +static int +mixer_ext_init (int dev) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + + devc->extinfo_loaded = 1; + create_soft_mixer (dev); + create_efx_mixer (dev); + return 0; +} + +static int +mixer_override (int dev, int audiodev, unsigned int cmd, int val) +{ + sblive_devc *devc = mixer_devs[dev]->hw_devc; + switch (cmd) + { + case SOUND_MIXER_READ_VOLUME: + return sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_READ, 0); + break; + + case SOUND_MIXER_WRITE_VOLUME: + return sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_WRITE, val); + break; + + case SOUND_MIXER_READ_PCM: + if (audiodev >= 0 && audiodev < num_audio_engines) + { + sblive_portc *portc = NULL; + int i; + + for (i = 0; i < devc->n_audiodevs && portc == NULL; i++) + if (devc->portc[i].audiodev == audiodev) + portc = &devc->portc[i]; + + if (portc == NULL) + return OSS_EIO; + + return portc->playvol | (portc->playvol << 8); + } + return sblive_set_gpr (dev, GPR_PCM, SNDCTL_MIX_READ, 0); + break; + + case SOUND_MIXER_WRITE_PCM: + if (audiodev >= 0 && audiodev < num_audio_engines) + { + sblive_portc *portc = NULL; + int i, left, right; + + for (i = 0; i < devc->n_audiodevs && portc == NULL; i++) + if (devc->portc[i].audiodev == audiodev) + portc = &devc->portc[i]; + + if (portc == NULL) + return OSS_EIO; + + left = val & 0xff; + right = (val >> 8) & 0xff; + + if (left < 0) + left = 0; + if (right < 0) + right = 0; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + + if (right > left) + left = right; + portc->playvol = left; + mixer_devs[devc->mixer_dev]->modify_counter++; /* Force update of mixer */ + update_output_device (devc, portc); + + return portc->playvol | (portc->playvol << 8); + } + return sblive_set_gpr (dev, GPR_PCM, SNDCTL_MIX_WRITE, val); + break; + } + + return 0; +} + +static const char *port_names[] = + { "front out", "side out", "center/lfe out", "rear out" }; + +static const __inline__ char * +get_port_name (sblive_devc * devc, int n) +{ + int max_names = 3; + + if (devc->feature_mask & SB_AUDIGY) + max_names = 3; + if (devc->feature_mask & SB_LIVE) + max_names = 2; + + n = n - 1; + + if (n > max_names) + return "extra out"; + + return port_names[n]; +} + +static void +unload_mpu (sblive_devc * devc) +{ + if (devc == NULL) + return; + + if (devc->feature_mask & SB_AUDIGY) + unload_audigyuart (devc); + else + uart401_disable (&devc->uart401devc); +} + +int +oss_sblive_attach (oss_device_t * osdev) +{ + sblive_devc *devc; + int i, err; + int frontdev = -1, ndevs = 0; + int first_dev = -1; + unsigned char pci_irq_line, pci_revision /*, pci_latency */ ; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr; + unsigned int subvendor; + adev_p adev; + extern int sblive_devices; + + int audiodevs_to_create = sblive_devices; + + char tmp[64]; + + DDB (cmn_err (CE_CONT, "sblive_attach entered\n")); + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != PCI_VENDOR_ID_CREATIVE) + { + cmn_err (CE_WARN, "Unrecognized SB live vendor %x\n", vendor); + return 0; + } + + if (device != PCI_DEVICE_ID_SBLIVE + && device != PCI_DEVICE_ID_AUDIGY + && device != PCI_DEVICE_ID_AUDIGY_CARDBUS + && device != PCI_DEVICE_ID_AUDIGYVALUE) + { + cmn_err (CE_WARN, "Unrecognized SB live device %x:%x\n", vendor, + device); + return 0; + } + +#ifdef AUDIGY_ONLY + if (device == PCI_DEVICE_ID_SBLIVE) + { + cmn_err (CE_CONT, + "Error: Due to hardware limitations SB Live is not\n"); + cmn_err (CE_CONT, "supported under this hardware architecture.\n"); + cmn_err (CE_CONT, + "Consider upgrading to SB Audigy which is supported.\n"); + return 0; + } +#endif + + pci_read_config_dword (osdev, 0x2c, &subvendor); + pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision); + pci_read_config_word (osdev, PCI_COMMAND, &pci_command); + pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line); + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr); + + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_command &= ~(PCI_COMMAND_SERR | PCI_COMMAND_PARITY); + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + + if (pci_ioaddr == 0) + { + cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n"); + return 0; + } + + if (pci_irq_line == 0) + { + cmn_err (CE_WARN, "IRQ not assigned by BIOS.\n"); + return 0; + } + + if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) + { + cmn_err (CE_WARN, "Out of memory\n"); + return 0; + } + + memset (devc, 0, sizeof (*devc)); + devc->osdev = osdev; + osdev->devc = devc; + MUTEX_INIT (osdev, devc->mutex, MH_DRV); + MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1); + + devc->emu_page_shift = 1; /* Default page shift */ + + devc->card_name = "Generic SB Live!"; + devc->subvendor = subvendor; + + devc->min_audiodevs = 5; /* Audigy supports 7.1 */ + + if (device == PCI_DEVICE_ID_AUDIGYVALUE) + { + /* SOLWAY subvendor id is 0x10211103 */ + if ((devc->subvendor == 0x10211102) || (devc->subvendor == 0x10211103)) + devc->card_name = "SB Audigy4"; + else + devc->card_name = "SB Audigy2 Value"; + devc->feature_mask = SB_AUDIGY | SB_AUDIGY2 | SB_AUDIGY2VAL; + } + else if (device == PCI_DEVICE_ID_AUDIGY) + { + if (devc->subvendor >= 0x10021102 && devc->subvendor <= 0x20051102) + { + devc->card_name = "SB Audigy2"; + devc->feature_mask = SB_AUDIGY | SB_AUDIGY2; + } + else + { + devc->card_name = "SB Audigy"; + devc->feature_mask = SB_AUDIGY; + } + } + else if (device == PCI_DEVICE_ID_AUDIGY_CARDBUS) + { + if (devc->subvendor >= 0x10021102 && devc->subvendor <= 0x20051102) + { + devc->card_name = "SB Audigy2 ZS Notebook"; + devc->feature_mask = SB_AUDIGY | SB_AUDIGY2; + } + else + { + devc->card_name = "SB Audigy"; + devc->feature_mask = SB_AUDIGY; + } + DDB (cmn_err (CE_CONT, + "emu10k2 chip rev %d, pcb rev %d\n", pci_revision, + sblive_read_reg (devc, 0x5f, 0))); + } + else + { + devc->card_name = "SB Live"; + devc->feature_mask = SB_LIVE; + devc->min_audiodevs = 4; /* Just 5.1 */ + } + + if (audiodevs_to_create < devc->min_audiodevs) + audiodevs_to_create = devc->min_audiodevs; + if (audiodevs_to_create > MAX_ADEV) + audiodevs_to_create = MAX_ADEV; + + devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr); + devc->base &= ~0x3; + + devc->gpr = NULL; + oss_register_device (osdev, devc->card_name); + + devc->irq = pci_irq_line; + + devc->page_map = NULL; + devc->vpage_map = NULL; + devc->nr_pages = 0; + devc->max_pages = 0; + devc->max_mem = 0; + devc->silent_page = NULL; + devc->subvendor = subvendor; + devc->passthrough_gpr = -1; + + if ((err = oss_register_interrupts (devc->osdev, 0, sbliveintr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); + return 0; + } + + devc->mixer_group = -1; + devc->extinfo_loaded = 0; + devc->autoreset = 1; + devc->speaker_mode = SMODE_FRONTREAR; + +/* + * Init mixer + */ + devc->mixer_dev = + ac97_install (&devc->ac97devc, devc->card_name, ac97_read, ac97_write, + devc, devc->osdev); + if (devc->mixer_dev < 0) + { + cmn_err (CE_WARN, "Mixer install failed - cannot continue\n"); + return 0; + } + + devc->ac97devc.mixer_ext = 0; + devc->ac97devc.spdifout_support = 0; + devc->ac97devc.spdifin_support = 0; + if (ac97_init_ext + (devc->mixer_dev, &devc->ac97devc, mixer_ext_init, 100) < 0) + { + cmn_err (CE_WARN, "Mixer ext install failed\n"); + } + + /* first set the AC97 PCM to max - otherwise sound is too low */ + ac97_mixer_set (&devc->ac97devc, SOUND_MIXER_PCM, 100 | (100 << 8)); + + ac97_remove_control (&devc->ac97devc, BOGUS_MIXER_CONTROLS, 0); + ac97_override_control (&devc->ac97devc, SOUND_MIXER_VOLUME, + mixer_override, 100 | (100 << 8)); + ac97_override_control (&devc->ac97devc, SOUND_MIXER_PCM, + mixer_override, 100 | (100 << 8)); + + attach_mpu (devc); + +/* + * Audio initialization + */ + init_emu10k1 (devc); + + for (i = 0; i < audiodevs_to_create; i++) + { + sblive_portc *portc = &devc->portc[i]; + int caps = ADEV_AUTOMODE; + int fmts = 0; + devc->n_audiodevs = i + 1; + + portc->memptr = devc->audio_memptr; + devc->audio_memptr += (DMABUF_SIZE + 4095) & ~4095; + + if (devc->audio_memptr > AUDIO_MEMSIZE) + { + cmn_err (CE_WARN, "Audio memory block exhausted (%d/%d)\n", + devc->audio_memptr, AUDIO_MEMSIZE); + return OSS_ENOSPC; + } + + if (i == 0) + { + strcpy (tmp, devc->card_name); + sprintf (tmp, "%s main", devc->card_name); + caps |= ADEV_DUPLEX; + } + else + { + sprintf (tmp, "%s %s", devc->card_name, get_port_name (devc, i)); + caps |= ADEV_NOINPUT; +#if 0 + if (i >= devc->min_audiodevs) + caps |= ADEV_HWMIX; +#endif + if (i >= devc->min_audiodevs + 1) + caps |= ADEV_SHADOW; + } + if ((devc->feature_mask & SB_AUDIGY) && i == audiodevs_to_create - 1) + { + sprintf (tmp, "%s raw S/PDIF (output only)", devc->card_name); + caps &= ~(ADEV_SHADOW /* | ADEV_HWMIX*/); + caps |= ADEV_SPECIAL; + fmts |= AFMT_AC3; + } +#if 0 + if (devc->feature_mask & SB_AUDIGY) + caps |= ADEV_COLD; +#endif + if ((portc->audiodev = + oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp, + &sblive_audio_driver, + sizeof (audiodrv_t), caps, + fmts | AFMT_U8 | AFMT_S16_LE, devc, -1)) < 0) + { + portc->audiodev = -1; + return (i > 0); + } + else + { + int x; + + adev = audio_engines[portc->audiodev]; + adev->nrates=0; + for (x = 0; speed_tab[x].speed != 0; x++) + adev->rates[adev->nrates++] = speed_tab[x].speed; + + if (i == 0) + first_dev = portc->audiodev; + adev->devc = devc; + adev->portc = portc; + adev->rate_source = first_dev; + adev->mixer_dev = devc->mixer_dev; + adev->min_rate = 8000; + adev->max_rate = 48000; + + if (!(devc->feature_mask & SB_AUDIGY)) + { + /* + * SB Live supports only 31 PCI address bits + */ + adev->dmabuf_maxaddr = MEMLIMIT_31BITS; + } + + portc->mode = 0; + adev->oformat_mask |= AFMT_AC3; + portc->input_type = ITYPE_ANALOG; + if ((devc->feature_mask & SB_AUDIGY) && i == audiodevs_to_create - 1) + portc->input_type = ITYPE_SPDIF; + if (i == 1) + frontdev = portc->audiodev; + if (i > 0) + ndevs++; + + portc->playvol = 100; + portc->playangle = 0; + portc->playdist = 50; + portc->vu_left = 0; + portc->vu_right = 0; + portc->audio_active = 0; + portc->voice_chn = i * 2; + portc->port_number = i; + devc->voice_busy[i * 2] = 1; + devc->voice_busy[i * 2 + 1] = 1; + portc->resetvol = 0; + if (devc->feature_mask & SB_LIVE) + { +/* + * Do not enable vmix by default on Live! It would cause enormous + * latencies because emu10k1 doesn't have working full/half buffer DMA + * interrupts. + */ + adev->vmix_flags = VMIX_MULTIFRAG; + adev->max_intrate = 50; + adev->min_block = 4096; + } + else + { + adev->max_fragments = 2; + } + + /* + * Hide vmix main volume control and peak meters if no + * real HW mixing devices are enabled. + */ +#if 0 + if (audiodevs_to_create <= devc->min_audiodevs) + adev->vmix_flags |= VMIX_NOMAINVOL; +#endif + adev->iformat_mask = AFMT_S16_LE; /* No 8 bit recording */ + + if (i == 0) + { + if (devc->feature_mask & SB_LIVE) + adev->magic = EMU10K1_MAGIC; + else + adev->magic = EMU10K2_MAGIC; + } +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, first_dev, -1, 0); +#endif + } + adev->mixer_dev = devc->mixer_dev; + } + +#ifdef USE_REMUX + /* Install Remux (only 5.1 support for the time being) */ + sprintf (tmp, "%s 5.1 output device", devc->card_name); + if (frontdev > 0 && ndevs >= 3) /* Have enough devices for 5.1 */ + remux_install (tmp, devc->osdev, frontdev, frontdev + 1, frontdev + 2, + -1); +#endif + +#ifndef NO_EMU10K1_SYNTH + sblive_install_synth (devc); +#endif + + touch_mixer (devc->mixer_dev); + init_effects (devc); + + return 1; +} + +int +oss_sblive_detach (oss_device_t * osdev) +{ + sblive_devc *devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + devc = osdev->devc; + + OUTL (devc->osdev, 0, devc->base + 0x0c); /* Intr enable (all off) */ + OUTL (devc->osdev, + HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | + HCFG_MUTEBUTTONENABLE, devc->base + 0x14); + + sblive_write_reg (devc, ADCSR, 0, 0x0); + sblive_write_reg (devc, ADCBA, 0, 0x0); + sblive_write_reg (devc, ADCBA, 0, 0x0); + + sblive_write_reg (devc, PTBA, 0, 0); + +#ifndef NO_EMU10K1_SYNTH + sblive_remove_synth (devc); +#endif + if (devc->page_map != NULL) + CONTIG_FREE (devc->osdev, devc->page_map, devc->max_pages * 4, devc->page_map_dma_handle); + if (devc->vpage_map != NULL) + KERNEL_FREE (devc->vpage_map); + if (devc->silent_page != NULL) + CONTIG_FREE (devc->osdev, devc->silent_page, 4096, devc->silent_page_dma_handle); + devc->max_pages = 0; + devc->max_mem = 0; + devc->page_map = NULL; + devc->vpage_map = NULL; + devc->silent_page = NULL; + unload_mpu (devc); + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + oss_unregister_device (osdev); + + return 1; +} |