mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
28
sound/isa/sb/Makefile
Normal file
28
sound/isa/sb/Makefile
Normal file
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-sb-common-objs := sb_common.o sb_mixer.o
|
||||
snd-sb8-dsp-objs := sb8_main.o sb8_midi.o
|
||||
snd-sb16-dsp-objs := sb16_main.o
|
||||
snd-sb16-csp-objs := sb16_csp.o
|
||||
snd-sb8-objs := sb8.o
|
||||
snd-sb16-objs := sb16.o
|
||||
snd-sbawe-objs := sbawe.o emu8000.o
|
||||
snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o
|
||||
snd-jazz16-objs := jazz16.o
|
||||
|
||||
# Toplevel Module Dependency
|
||||
obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o
|
||||
obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o
|
||||
obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o
|
||||
obj-$(CONFIG_SND_SB8) += snd-sb8.o
|
||||
obj-$(CONFIG_SND_SB16) += snd-sb16.o
|
||||
obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o
|
||||
obj-$(CONFIG_SND_JAZZ16) += snd-jazz16.o
|
||||
ifeq ($(CONFIG_SND_SB16_CSP),y)
|
||||
obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o
|
||||
obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o
|
||||
endif
|
||||
obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emu8000-synth.o
|
1172
sound/isa/sb/emu8000.c
Normal file
1172
sound/isa/sb/emu8000.c
Normal file
File diff suppressed because it is too large
Load diff
547
sound/isa/sb/emu8000_callback.c
Normal file
547
sound/isa/sb/emu8000_callback.c
Normal file
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* synth callback routines for the emu8000 (AWE32/64)
|
||||
*
|
||||
* Copyright (C) 1999 Steve Ratcliffe
|
||||
* Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "emu8000_local.h"
|
||||
#include <linux/export.h>
|
||||
#include <sound/asoundef.h>
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static struct snd_emux_voice *get_voice(struct snd_emux *emu,
|
||||
struct snd_emux_port *port);
|
||||
static int start_voice(struct snd_emux_voice *vp);
|
||||
static void trigger_voice(struct snd_emux_voice *vp);
|
||||
static void release_voice(struct snd_emux_voice *vp);
|
||||
static void update_voice(struct snd_emux_voice *vp, int update);
|
||||
static void reset_voice(struct snd_emux *emu, int ch);
|
||||
static void terminate_voice(struct snd_emux_voice *vp);
|
||||
static void sysex(struct snd_emux *emu, char *buf, int len, int parsed,
|
||||
struct snd_midi_channel_set *chset);
|
||||
#ifdef CONFIG_SND_SEQUENCER_OSS
|
||||
static int oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2);
|
||||
#endif
|
||||
static int load_fx(struct snd_emux *emu, int type, int mode,
|
||||
const void __user *buf, long len);
|
||||
|
||||
static void set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
|
||||
static void snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int ch);
|
||||
|
||||
/*
|
||||
* Ensure a value is between two points
|
||||
* macro evaluates its args more than once, so changed to upper-case.
|
||||
*/
|
||||
#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
|
||||
#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
|
||||
|
||||
|
||||
/*
|
||||
* set up operators
|
||||
*/
|
||||
static struct snd_emux_operators emu8000_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.get_voice = get_voice,
|
||||
.prepare = start_voice,
|
||||
.trigger = trigger_voice,
|
||||
.release = release_voice,
|
||||
.update = update_voice,
|
||||
.terminate = terminate_voice,
|
||||
.reset = reset_voice,
|
||||
.sample_new = snd_emu8000_sample_new,
|
||||
.sample_free = snd_emu8000_sample_free,
|
||||
.sample_reset = snd_emu8000_sample_reset,
|
||||
.load_fx = load_fx,
|
||||
.sysex = sysex,
|
||||
#ifdef CONFIG_SND_SEQUENCER_OSS
|
||||
.oss_ioctl = oss_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
void
|
||||
snd_emu8000_ops_setup(struct snd_emu8000 *hw)
|
||||
{
|
||||
hw->emu->ops = emu8000_ops;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Terminate a voice
|
||||
*/
|
||||
static void
|
||||
release_voice(struct snd_emux_voice *vp)
|
||||
{
|
||||
int dcysusv;
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = vp->hw;
|
||||
dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
|
||||
EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv);
|
||||
dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease;
|
||||
EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
static void
|
||||
terminate_voice(struct snd_emux_voice *vp)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = vp->hw;
|
||||
EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
static void
|
||||
update_voice(struct snd_emux_voice *vp, int update)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = vp->hw;
|
||||
if (update & SNDRV_EMUX_UPDATE_VOLUME)
|
||||
set_volume(hw, vp);
|
||||
if (update & SNDRV_EMUX_UPDATE_PITCH)
|
||||
set_pitch(hw, vp);
|
||||
if ((update & SNDRV_EMUX_UPDATE_PAN) &&
|
||||
vp->port->ctrls[EMUX_MD_REALTIME_PAN])
|
||||
set_pan(hw, vp);
|
||||
if (update & SNDRV_EMUX_UPDATE_FMMOD)
|
||||
set_fmmod(hw, vp);
|
||||
if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
|
||||
set_tremfreq(hw, vp);
|
||||
if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
|
||||
set_fm2frq2(hw, vp);
|
||||
if (update & SNDRV_EMUX_UPDATE_Q)
|
||||
set_filterQ(hw, vp);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find a channel (voice) within the EMU that is not in use or at least
|
||||
* less in use than other channels. Always returns a valid pointer
|
||||
* no matter what. If there is a real shortage of voices then one
|
||||
* will be cut. Such is life.
|
||||
*
|
||||
* The channel index (vp->ch) must be initialized in this routine.
|
||||
* In Emu8k, it is identical with the array index.
|
||||
*/
|
||||
static struct snd_emux_voice *
|
||||
get_voice(struct snd_emux *emu, struct snd_emux_port *port)
|
||||
{
|
||||
int i;
|
||||
struct snd_emux_voice *vp;
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
/* what we are looking for, in order of preference */
|
||||
enum {
|
||||
OFF=0, RELEASED, PLAYING, END
|
||||
};
|
||||
|
||||
/* Keeps track of what we are finding */
|
||||
struct best {
|
||||
unsigned int time;
|
||||
int voice;
|
||||
} best[END];
|
||||
struct best *bp;
|
||||
|
||||
hw = emu->hw;
|
||||
|
||||
for (i = 0; i < END; i++) {
|
||||
best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */
|
||||
best[i].voice = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through them all and get a best one to use.
|
||||
*/
|
||||
for (i = 0; i < emu->max_voices; i++) {
|
||||
int state, val;
|
||||
|
||||
vp = &emu->voices[i];
|
||||
state = vp->state;
|
||||
|
||||
if (state == SNDRV_EMUX_ST_OFF)
|
||||
bp = best + OFF;
|
||||
else if (state == SNDRV_EMUX_ST_RELEASED ||
|
||||
state == SNDRV_EMUX_ST_PENDING) {
|
||||
bp = best + RELEASED;
|
||||
val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff;
|
||||
if (! val)
|
||||
bp = best + OFF;
|
||||
}
|
||||
else if (state & SNDRV_EMUX_ST_ON)
|
||||
bp = best + PLAYING;
|
||||
else
|
||||
continue;
|
||||
|
||||
/* check if sample is finished playing (non-looping only) */
|
||||
if (state != SNDRV_EMUX_ST_OFF &&
|
||||
(vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
|
||||
val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
|
||||
if (val >= vp->reg.loopstart)
|
||||
bp = best + OFF;
|
||||
}
|
||||
|
||||
if (vp->time < bp->time) {
|
||||
bp->time = vp->time;
|
||||
bp->voice = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < END; i++) {
|
||||
if (best[i].voice >= 0) {
|
||||
vp = &emu->voices[best[i].voice];
|
||||
vp->ch = best[i].voice;
|
||||
return vp;
|
||||
}
|
||||
}
|
||||
|
||||
/* not found */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
static int
|
||||
start_voice(struct snd_emux_voice *vp)
|
||||
{
|
||||
unsigned int temp;
|
||||
int ch;
|
||||
int addr;
|
||||
struct snd_midi_channel *chan;
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = vp->hw;
|
||||
ch = vp->ch;
|
||||
chan = vp->chan;
|
||||
|
||||
/* channel to be silent and idle */
|
||||
EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
|
||||
EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
|
||||
EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
|
||||
EMU8000_PTRX_WRITE(hw, ch, 0);
|
||||
EMU8000_CPF_WRITE(hw, ch, 0);
|
||||
|
||||
/* set pitch offset */
|
||||
set_pitch(hw, vp);
|
||||
|
||||
/* set envelope parameters */
|
||||
EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay);
|
||||
EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld);
|
||||
EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus);
|
||||
EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay);
|
||||
EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld);
|
||||
/* decay/sustain parameter for volume envelope is used
|
||||
for triggerg the voice */
|
||||
|
||||
/* cutoff and volume */
|
||||
set_volume(hw, vp);
|
||||
|
||||
/* modulation envelope heights */
|
||||
EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe);
|
||||
|
||||
/* lfo1/2 delay */
|
||||
EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay);
|
||||
EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay);
|
||||
|
||||
/* lfo1 pitch & cutoff shift */
|
||||
set_fmmod(hw, vp);
|
||||
/* lfo1 volume & freq */
|
||||
set_tremfreq(hw, vp);
|
||||
/* lfo2 pitch & freq */
|
||||
set_fm2frq2(hw, vp);
|
||||
/* pan & loop start */
|
||||
set_pan(hw, vp);
|
||||
|
||||
/* chorus & loop end (chorus 8bit, MSB) */
|
||||
addr = vp->reg.loopend - 1;
|
||||
temp = vp->reg.parm.chorus;
|
||||
temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
|
||||
LIMITMAX(temp, 255);
|
||||
temp = (temp <<24) | (unsigned int)addr;
|
||||
EMU8000_CSL_WRITE(hw, ch, temp);
|
||||
|
||||
/* Q & current address (Q 4bit value, MSB) */
|
||||
addr = vp->reg.start - 1;
|
||||
temp = vp->reg.parm.filterQ;
|
||||
temp = (temp<<28) | (unsigned int)addr;
|
||||
EMU8000_CCCA_WRITE(hw, ch, temp);
|
||||
|
||||
/* clear unknown registers */
|
||||
EMU8000_00A0_WRITE(hw, ch, 0);
|
||||
EMU8000_0080_WRITE(hw, ch, 0);
|
||||
|
||||
/* reset volume */
|
||||
temp = vp->vtarget << 16;
|
||||
EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget);
|
||||
EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start envelope
|
||||
*/
|
||||
static void
|
||||
trigger_voice(struct snd_emux_voice *vp)
|
||||
{
|
||||
int ch = vp->ch;
|
||||
unsigned int temp;
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = vp->hw;
|
||||
|
||||
/* set reverb and pitch target */
|
||||
temp = vp->reg.parm.reverb;
|
||||
temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
|
||||
LIMITMAX(temp, 255);
|
||||
temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux;
|
||||
EMU8000_PTRX_WRITE(hw, ch, temp);
|
||||
EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16);
|
||||
EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus);
|
||||
}
|
||||
|
||||
/*
|
||||
* reset voice parameters
|
||||
*/
|
||||
static void
|
||||
reset_voice(struct snd_emux *emu, int ch)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = emu->hw;
|
||||
EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
|
||||
snd_emu8000_tweak_voice(hw, ch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the pitch of a possibly playing note.
|
||||
*/
|
||||
static void
|
||||
set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
EMU8000_IP_WRITE(hw, vp->ch, vp->apitch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the volume of a possibly already playing note
|
||||
*/
|
||||
static void
|
||||
set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
int ifatn;
|
||||
|
||||
ifatn = (unsigned char)vp->acutoff;
|
||||
ifatn = (ifatn << 8);
|
||||
ifatn |= (unsigned char)vp->avol;
|
||||
EMU8000_IFATN_WRITE(hw, vp->ch, ifatn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set pan and loop start address.
|
||||
*/
|
||||
static void
|
||||
set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
unsigned int temp;
|
||||
|
||||
temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1);
|
||||
EMU8000_PSST_WRITE(hw, vp->ch, temp);
|
||||
}
|
||||
|
||||
#define MOD_SENSE 18
|
||||
|
||||
static void
|
||||
set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
unsigned short fmmod;
|
||||
short pitch;
|
||||
unsigned char cutoff;
|
||||
int modulation;
|
||||
|
||||
pitch = (char)(vp->reg.parm.fmmod>>8);
|
||||
cutoff = (vp->reg.parm.fmmod & 0xff);
|
||||
modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
|
||||
pitch += (MOD_SENSE * modulation) / 1200;
|
||||
LIMITVALUE(pitch, -128, 127);
|
||||
fmmod = ((unsigned char)pitch<<8) | cutoff;
|
||||
EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod);
|
||||
}
|
||||
|
||||
/* set tremolo (lfo1) volume & frequency */
|
||||
static void
|
||||
set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq);
|
||||
}
|
||||
|
||||
/* set lfo2 pitch & frequency */
|
||||
static void
|
||||
set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
unsigned short fm2frq2;
|
||||
short pitch;
|
||||
unsigned char freq;
|
||||
int modulation;
|
||||
|
||||
pitch = (char)(vp->reg.parm.fm2frq2>>8);
|
||||
freq = vp->reg.parm.fm2frq2 & 0xff;
|
||||
modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
|
||||
pitch += (MOD_SENSE * modulation) / 1200;
|
||||
LIMITVALUE(pitch, -128, 127);
|
||||
fm2frq2 = ((unsigned char)pitch<<8) | freq;
|
||||
EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2);
|
||||
}
|
||||
|
||||
/* set filterQ */
|
||||
static void
|
||||
set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
|
||||
{
|
||||
unsigned int addr;
|
||||
addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
|
||||
addr |= (vp->reg.parm.filterQ << 28);
|
||||
EMU8000_CCCA_WRITE(hw, vp->ch, addr);
|
||||
}
|
||||
|
||||
/*
|
||||
* set the envelope & LFO parameters to the default values
|
||||
*/
|
||||
static void
|
||||
snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int i)
|
||||
{
|
||||
/* set all mod/vol envelope shape to minimum */
|
||||
EMU8000_ENVVOL_WRITE(emu, i, 0x8000);
|
||||
EMU8000_ENVVAL_WRITE(emu, i, 0x8000);
|
||||
EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F);
|
||||
EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F);
|
||||
EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F);
|
||||
EMU8000_PEFE_WRITE(emu, i, 0); /* mod envelope height to zero */
|
||||
EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */
|
||||
EMU8000_LFO2VAL_WRITE(emu, i, 0x8000);
|
||||
EMU8000_IP_WRITE(emu, i, 0xE000); /* no pitch shift */
|
||||
EMU8000_IFATN_WRITE(emu, i, 0xFF00); /* volume to minimum */
|
||||
EMU8000_FMMOD_WRITE(emu, i, 0);
|
||||
EMU8000_TREMFRQ_WRITE(emu, i, 0);
|
||||
EMU8000_FM2FRQ2_WRITE(emu, i, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* sysex callback
|
||||
*/
|
||||
static void
|
||||
sysex(struct snd_emux *emu, char *buf, int len, int parsed, struct snd_midi_channel_set *chset)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = emu->hw;
|
||||
|
||||
switch (parsed) {
|
||||
case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE:
|
||||
hw->chorus_mode = chset->gs_chorus_mode;
|
||||
snd_emu8000_update_chorus_mode(hw);
|
||||
break;
|
||||
|
||||
case SNDRV_MIDI_SYSEX_GS_REVERB_MODE:
|
||||
hw->reverb_mode = chset->gs_reverb_mode;
|
||||
snd_emu8000_update_reverb_mode(hw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_SND_SEQUENCER_OSS
|
||||
/*
|
||||
* OSS ioctl callback
|
||||
*/
|
||||
static int
|
||||
oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
hw = emu->hw;
|
||||
|
||||
switch (cmd) {
|
||||
case _EMUX_OSS_REVERB_MODE:
|
||||
hw->reverb_mode = p1;
|
||||
snd_emu8000_update_reverb_mode(hw);
|
||||
break;
|
||||
|
||||
case _EMUX_OSS_CHORUS_MODE:
|
||||
hw->chorus_mode = p1;
|
||||
snd_emu8000_update_chorus_mode(hw);
|
||||
break;
|
||||
|
||||
case _EMUX_OSS_INITIALIZE_CHIP:
|
||||
/* snd_emu8000_init(hw); */ /*ignored*/
|
||||
break;
|
||||
|
||||
case _EMUX_OSS_EQUALIZER:
|
||||
hw->bass_level = p1;
|
||||
hw->treble_level = p2;
|
||||
snd_emu8000_update_equalizer(hw);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* additional patch keys
|
||||
*/
|
||||
|
||||
#define SNDRV_EMU8000_LOAD_CHORUS_FX 0x10 /* optarg=mode */
|
||||
#define SNDRV_EMU8000_LOAD_REVERB_FX 0x11 /* optarg=mode */
|
||||
|
||||
|
||||
/*
|
||||
* callback routine
|
||||
*/
|
||||
|
||||
static int
|
||||
load_fx(struct snd_emux *emu, int type, int mode, const void __user *buf, long len)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
hw = emu->hw;
|
||||
|
||||
/* skip header */
|
||||
buf += 16;
|
||||
len -= 16;
|
||||
|
||||
switch (type) {
|
||||
case SNDRV_EMU8000_LOAD_CHORUS_FX:
|
||||
return snd_emu8000_load_chorus_fx(hw, mode, buf, len);
|
||||
case SNDRV_EMU8000_LOAD_REVERB_FX:
|
||||
return snd_emu8000_load_reverb_fx(hw, mode, buf, len);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
45
sound/isa/sb/emu8000_local.h
Normal file
45
sound/isa/sb/emu8000_local.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef __EMU8000_LOCAL_H
|
||||
#define __EMU8000_LOCAL_H
|
||||
/*
|
||||
* Local defininitons for the emu8000 (AWE32/64)
|
||||
*
|
||||
* Copyright (C) 1999 Steve Ratcliffe
|
||||
* Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/emu8000.h>
|
||||
#include <sound/emu8000_reg.h>
|
||||
|
||||
/* emu8000_patch.c */
|
||||
int snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
|
||||
struct snd_util_memhdr *hdr,
|
||||
const void __user *data, long count);
|
||||
int snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
|
||||
struct snd_util_memhdr *hdr);
|
||||
void snd_emu8000_sample_reset(struct snd_emux *rec);
|
||||
|
||||
/* emu8000_callback.c */
|
||||
void snd_emu8000_ops_setup(struct snd_emu8000 *emu);
|
||||
|
||||
/* emu8000_pcm.c */
|
||||
int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index);
|
||||
|
||||
#endif /* __EMU8000_LOCAL_H */
|
305
sound/isa/sb/emu8000_patch.c
Normal file
305
sound/isa/sb/emu8000_patch.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Patch routines for the emu8000 (AWE32/64)
|
||||
*
|
||||
* Copyright (C) 1999 Steve Ratcliffe
|
||||
* Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "emu8000_local.h"
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/moduleparam.h>
|
||||
|
||||
static int emu8000_reset_addr;
|
||||
module_param(emu8000_reset_addr, int, 0444);
|
||||
MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
|
||||
|
||||
|
||||
/*
|
||||
* Open up channels.
|
||||
*/
|
||||
static int
|
||||
snd_emu8000_open_dma(struct snd_emu8000 *emu, int write)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* reserve all 30 voices for loading */
|
||||
for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
|
||||
snd_emux_lock_voice(emu->emu, i);
|
||||
snd_emu8000_dma_chan(emu, i, write);
|
||||
}
|
||||
|
||||
/* assign voice 31 and 32 to ROM */
|
||||
EMU8000_VTFT_WRITE(emu, 30, 0);
|
||||
EMU8000_PSST_WRITE(emu, 30, 0x1d8);
|
||||
EMU8000_CSL_WRITE(emu, 30, 0x1e0);
|
||||
EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
|
||||
EMU8000_VTFT_WRITE(emu, 31, 0);
|
||||
EMU8000_PSST_WRITE(emu, 31, 0x1d8);
|
||||
EMU8000_CSL_WRITE(emu, 31, 0x1e0);
|
||||
EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Close all dram channels.
|
||||
*/
|
||||
static void
|
||||
snd_emu8000_close_dma(struct snd_emu8000 *emu)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
|
||||
snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
|
||||
snd_emux_unlock_voice(emu->emu, i);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
#define BLANK_LOOP_START 4
|
||||
#define BLANK_LOOP_END 8
|
||||
#define BLANK_LOOP_SIZE 12
|
||||
#define BLANK_HEAD_SIZE 48
|
||||
|
||||
/*
|
||||
* Read a word from userland, taking care of conversions from
|
||||
* 8bit samples etc.
|
||||
*/
|
||||
static unsigned short
|
||||
read_word(const void __user *buf, int offset, int mode)
|
||||
{
|
||||
unsigned short c;
|
||||
if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
|
||||
unsigned char cc;
|
||||
get_user(cc, (unsigned char __user *)buf + offset);
|
||||
c = cc << 8; /* convert 8bit -> 16bit */
|
||||
} else {
|
||||
#ifdef SNDRV_LITTLE_ENDIAN
|
||||
get_user(c, (unsigned short __user *)buf + offset);
|
||||
#else
|
||||
unsigned short cc;
|
||||
get_user(cc, (unsigned short __user *)buf + offset);
|
||||
c = swab16(cc);
|
||||
#endif
|
||||
}
|
||||
if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
|
||||
c ^= 0x8000; /* unsigned -> signed */
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
static void
|
||||
snd_emu8000_write_wait(struct snd_emu8000 *emu)
|
||||
{
|
||||
while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
|
||||
schedule_timeout_interruptible(1);
|
||||
if (signal_pending(current))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* write sample word data
|
||||
*
|
||||
* You should not have to keep resetting the address each time
|
||||
* as the chip is supposed to step on the next address automatically.
|
||||
* It mostly does, but during writes of some samples at random it
|
||||
* completely loses words (every one in 16 roughly but with no
|
||||
* obvious pattern).
|
||||
*
|
||||
* This is therefore much slower than need be, but is at least
|
||||
* working.
|
||||
*/
|
||||
static inline void
|
||||
write_word(struct snd_emu8000 *emu, int *offset, unsigned short data)
|
||||
{
|
||||
if (emu8000_reset_addr) {
|
||||
if (emu8000_reset_addr > 1)
|
||||
snd_emu8000_write_wait(emu);
|
||||
EMU8000_SMALW_WRITE(emu, *offset);
|
||||
}
|
||||
EMU8000_SMLD_WRITE(emu, data);
|
||||
*offset += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the sample to EMU800 memory. This routine is invoked out of
|
||||
* the generic soundfont routines as a callback.
|
||||
*/
|
||||
int
|
||||
snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
|
||||
struct snd_util_memhdr *hdr,
|
||||
const void __user *data, long count)
|
||||
{
|
||||
int i;
|
||||
int rc;
|
||||
int offset;
|
||||
int truesize;
|
||||
int dram_offset, dram_start;
|
||||
struct snd_emu8000 *emu;
|
||||
|
||||
emu = rec->hw;
|
||||
if (snd_BUG_ON(!sp))
|
||||
return -EINVAL;
|
||||
|
||||
if (sp->v.size == 0)
|
||||
return 0;
|
||||
|
||||
/* be sure loop points start < end */
|
||||
if (sp->v.loopstart > sp->v.loopend) {
|
||||
int tmp = sp->v.loopstart;
|
||||
sp->v.loopstart = sp->v.loopend;
|
||||
sp->v.loopend = tmp;
|
||||
}
|
||||
|
||||
/* compute true data size to be loaded */
|
||||
truesize = sp->v.size;
|
||||
if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
|
||||
truesize += sp->v.loopend - sp->v.loopstart;
|
||||
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
|
||||
truesize += BLANK_LOOP_SIZE;
|
||||
|
||||
sp->block = snd_util_mem_alloc(hdr, truesize * 2);
|
||||
if (sp->block == NULL) {
|
||||
/*snd_printd("EMU8000: out of memory\n");*/
|
||||
/* not ENOMEM (for compatibility) */
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
|
||||
if (!access_ok(VERIFY_READ, data, sp->v.size))
|
||||
return -EFAULT;
|
||||
} else {
|
||||
if (!access_ok(VERIFY_READ, data, sp->v.size * 2))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* recalculate address offset */
|
||||
sp->v.end -= sp->v.start;
|
||||
sp->v.loopstart -= sp->v.start;
|
||||
sp->v.loopend -= sp->v.start;
|
||||
sp->v.start = 0;
|
||||
|
||||
/* dram position (in word) -- mem_offset is byte */
|
||||
dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
|
||||
dram_start = dram_offset;
|
||||
|
||||
/* set the total size (store onto obsolete checksum value) */
|
||||
sp->v.truesize = truesize * 2; /* in bytes */
|
||||
|
||||
snd_emux_terminate_all(emu->emu);
|
||||
if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0)
|
||||
return rc;
|
||||
|
||||
/* Set the address to start writing at */
|
||||
snd_emu8000_write_wait(emu);
|
||||
EMU8000_SMALW_WRITE(emu, dram_offset);
|
||||
|
||||
/*snd_emu8000_init_fm(emu);*/
|
||||
|
||||
#if 0
|
||||
/* first block - write 48 samples for silence */
|
||||
if (! sp->block->offset) {
|
||||
for (i = 0; i < BLANK_HEAD_SIZE; i++) {
|
||||
write_word(emu, &dram_offset, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
offset = 0;
|
||||
for (i = 0; i < sp->v.size; i++) {
|
||||
unsigned short s;
|
||||
|
||||
s = read_word(data, offset, sp->v.mode_flags);
|
||||
offset++;
|
||||
write_word(emu, &dram_offset, s);
|
||||
|
||||
/* we may take too long time in this loop.
|
||||
* so give controls back to kernel if needed.
|
||||
*/
|
||||
cond_resched();
|
||||
|
||||
if (i == sp->v.loopend &&
|
||||
(sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
|
||||
{
|
||||
int looplen = sp->v.loopend - sp->v.loopstart;
|
||||
int k;
|
||||
|
||||
/* copy reverse loop */
|
||||
for (k = 1; k <= looplen; k++) {
|
||||
s = read_word(data, offset - k, sp->v.mode_flags);
|
||||
write_word(emu, &dram_offset, s);
|
||||
}
|
||||
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
|
||||
sp->v.loopend += looplen;
|
||||
} else {
|
||||
sp->v.loopstart += looplen;
|
||||
sp->v.loopend += looplen;
|
||||
}
|
||||
sp->v.end += looplen;
|
||||
}
|
||||
}
|
||||
|
||||
/* if no blank loop is attached in the sample, add it */
|
||||
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
|
||||
for (i = 0; i < BLANK_LOOP_SIZE; i++) {
|
||||
write_word(emu, &dram_offset, 0);
|
||||
}
|
||||
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
|
||||
sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
|
||||
sp->v.loopend = sp->v.end + BLANK_LOOP_END;
|
||||
}
|
||||
}
|
||||
|
||||
/* add dram offset */
|
||||
sp->v.start += dram_start;
|
||||
sp->v.end += dram_start;
|
||||
sp->v.loopstart += dram_start;
|
||||
sp->v.loopend += dram_start;
|
||||
|
||||
snd_emu8000_close_dma(emu);
|
||||
snd_emu8000_init_fm(emu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* free a sample block
|
||||
*/
|
||||
int
|
||||
snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
|
||||
struct snd_util_memhdr *hdr)
|
||||
{
|
||||
if (sp->block) {
|
||||
snd_util_mem_free(hdr, sp->block);
|
||||
sp->block = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* sample_reset callback - terminate voices
|
||||
*/
|
||||
void
|
||||
snd_emu8000_sample_reset(struct snd_emux *rec)
|
||||
{
|
||||
snd_emux_terminate_all(rec);
|
||||
}
|
705
sound/isa/sb/emu8000_pcm.c
Normal file
705
sound/isa/sb/emu8000_pcm.c
Normal file
|
@ -0,0 +1,705 @@
|
|||
/*
|
||||
* pcm emulation on emu8000 wavetable
|
||||
*
|
||||
* Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "emu8000_local.h"
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
|
||||
/*
|
||||
* define the following if you want to use this pcm with non-interleaved mode
|
||||
*/
|
||||
/* #define USE_NONINTERLEAVE */
|
||||
|
||||
/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
|
||||
* mmap_emulation flag to 1 in your .asoundrc, such like
|
||||
*
|
||||
* pcm.emu8k {
|
||||
* type plug
|
||||
* slave.pcm {
|
||||
* type hw
|
||||
* card 0
|
||||
* device 1
|
||||
* mmap_emulation 1
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* besides, for the time being, the non-interleaved mode doesn't work well on
|
||||
* alsa-lib...
|
||||
*/
|
||||
|
||||
|
||||
struct snd_emu8k_pcm {
|
||||
struct snd_emu8000 *emu;
|
||||
struct snd_pcm_substream *substream;
|
||||
|
||||
unsigned int allocated_bytes;
|
||||
struct snd_util_memblk *block;
|
||||
unsigned int offset;
|
||||
unsigned int buf_size;
|
||||
unsigned int period_size;
|
||||
unsigned int loop_start[2];
|
||||
unsigned int pitch;
|
||||
int panning[2];
|
||||
int last_ptr;
|
||||
int period_pos;
|
||||
int voices;
|
||||
unsigned int dram_opened: 1;
|
||||
unsigned int running: 1;
|
||||
unsigned int timer_running: 1;
|
||||
struct timer_list timer;
|
||||
spinlock_t timer_lock;
|
||||
};
|
||||
|
||||
#define LOOP_BLANK_SIZE 8
|
||||
|
||||
|
||||
/*
|
||||
* open up channels for the simultaneous data transfer and playback
|
||||
*/
|
||||
static int
|
||||
emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* reserve up to 2 voices for playback */
|
||||
snd_emux_lock_voice(emu->emu, 0);
|
||||
if (channels > 1)
|
||||
snd_emux_lock_voice(emu->emu, 1);
|
||||
|
||||
/* reserve 28 voices for loading */
|
||||
for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {
|
||||
unsigned int mode = EMU8000_RAM_WRITE;
|
||||
snd_emux_lock_voice(emu->emu, i);
|
||||
#ifndef USE_NONINTERLEAVE
|
||||
if (channels > 1 && (i & 1) != 0)
|
||||
mode |= EMU8000_RAM_RIGHT;
|
||||
#endif
|
||||
snd_emu8000_dma_chan(emu, i, mode);
|
||||
}
|
||||
|
||||
/* assign voice 31 and 32 to ROM */
|
||||
EMU8000_VTFT_WRITE(emu, 30, 0);
|
||||
EMU8000_PSST_WRITE(emu, 30, 0x1d8);
|
||||
EMU8000_CSL_WRITE(emu, 30, 0x1e0);
|
||||
EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
|
||||
EMU8000_VTFT_WRITE(emu, 31, 0);
|
||||
EMU8000_PSST_WRITE(emu, 31, 0x1d8);
|
||||
EMU8000_CSL_WRITE(emu, 31, 0x1e0);
|
||||
EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
static void
|
||||
snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule)
|
||||
{
|
||||
while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
|
||||
if (can_schedule) {
|
||||
schedule_timeout_interruptible(1);
|
||||
if (signal_pending(current))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* close all channels
|
||||
*/
|
||||
static void
|
||||
emu8k_close_dram(struct snd_emu8000 *emu)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
snd_emux_unlock_voice(emu->emu, i);
|
||||
for (; i < EMU8000_DRAM_VOICES; i++) {
|
||||
snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
|
||||
snd_emux_unlock_voice(emu->emu, i);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* convert Hz to AWE32 rate offset (see emux/soundfont.c)
|
||||
*/
|
||||
|
||||
#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */
|
||||
#define SAMPLERATE_RATIO 4096
|
||||
|
||||
static int calc_rate_offset(int hz)
|
||||
{
|
||||
return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
static struct snd_pcm_hardware emu8k_pcm_hw = {
|
||||
#ifdef USE_NONINTERLEAVE
|
||||
.info = SNDRV_PCM_INFO_NONINTERLEAVED,
|
||||
#else
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED,
|
||||
#endif
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
|
||||
.rate_min = 4000,
|
||||
.rate_max = 48000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = (128*1024),
|
||||
.period_bytes_min = 1024,
|
||||
.period_bytes_max = (128*1024),
|
||||
.periods_min = 2,
|
||||
.periods_max = 1024,
|
||||
.fifo_size = 0,
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* get the current position at the given channel from CCCA register
|
||||
*/
|
||||
static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch)
|
||||
{
|
||||
int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;
|
||||
val -= rec->loop_start[ch] - 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* timer interrupt handler
|
||||
* check the current position and update the period if necessary.
|
||||
*/
|
||||
static void emu8k_pcm_timer_func(unsigned long data)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = (struct snd_emu8k_pcm *)data;
|
||||
int ptr, delta;
|
||||
|
||||
spin_lock(&rec->timer_lock);
|
||||
/* update the current pointer */
|
||||
ptr = emu8k_get_curpos(rec, 0);
|
||||
if (ptr < rec->last_ptr)
|
||||
delta = ptr + rec->buf_size - rec->last_ptr;
|
||||
else
|
||||
delta = ptr - rec->last_ptr;
|
||||
rec->period_pos += delta;
|
||||
rec->last_ptr = ptr;
|
||||
|
||||
/* reprogram timer */
|
||||
rec->timer.expires = jiffies + 1;
|
||||
add_timer(&rec->timer);
|
||||
|
||||
/* update period */
|
||||
if (rec->period_pos >= (int)rec->period_size) {
|
||||
rec->period_pos %= rec->period_size;
|
||||
spin_unlock(&rec->timer_lock);
|
||||
snd_pcm_period_elapsed(rec->substream);
|
||||
return;
|
||||
}
|
||||
spin_unlock(&rec->timer_lock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* open pcm
|
||||
* creating an instance here
|
||||
*/
|
||||
static int emu8k_pcm_open(struct snd_pcm_substream *subs)
|
||||
{
|
||||
struct snd_emu8000 *emu = snd_pcm_substream_chip(subs);
|
||||
struct snd_emu8k_pcm *rec;
|
||||
struct snd_pcm_runtime *runtime = subs->runtime;
|
||||
|
||||
rec = kzalloc(sizeof(*rec), GFP_KERNEL);
|
||||
if (! rec)
|
||||
return -ENOMEM;
|
||||
|
||||
rec->emu = emu;
|
||||
rec->substream = subs;
|
||||
runtime->private_data = rec;
|
||||
|
||||
spin_lock_init(&rec->timer_lock);
|
||||
init_timer(&rec->timer);
|
||||
rec->timer.function = emu8k_pcm_timer_func;
|
||||
rec->timer.data = (unsigned long)rec;
|
||||
|
||||
runtime->hw = emu8k_pcm_hw;
|
||||
runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;
|
||||
runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;
|
||||
|
||||
/* use timer to update periods.. (specified in msec) */
|
||||
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
||||
(1000000 + HZ - 1) / HZ, UINT_MAX);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emu8k_pcm_close(struct snd_pcm_substream *subs)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
kfree(rec);
|
||||
subs->runtime->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* calculate pitch target
|
||||
*/
|
||||
static int calc_pitch_target(int pitch)
|
||||
{
|
||||
int ptarget = 1 << (pitch >> 12);
|
||||
if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710;
|
||||
if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710;
|
||||
if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710;
|
||||
ptarget += (ptarget >> 1);
|
||||
if (ptarget > 0xffff) ptarget = 0xffff;
|
||||
return ptarget;
|
||||
}
|
||||
|
||||
/*
|
||||
* set up the voice
|
||||
*/
|
||||
static void setup_voice(struct snd_emu8k_pcm *rec, int ch)
|
||||
{
|
||||
struct snd_emu8000 *hw = rec->emu;
|
||||
unsigned int temp;
|
||||
|
||||
/* channel to be silent and idle */
|
||||
EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
|
||||
EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
|
||||
EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
|
||||
EMU8000_PTRX_WRITE(hw, ch, 0);
|
||||
EMU8000_CPF_WRITE(hw, ch, 0);
|
||||
|
||||
/* pitch offset */
|
||||
EMU8000_IP_WRITE(hw, ch, rec->pitch);
|
||||
/* set envelope parameters */
|
||||
EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);
|
||||
EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);
|
||||
EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);
|
||||
EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);
|
||||
EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);
|
||||
/* decay/sustain parameter for volume envelope is used
|
||||
for triggerg the voice */
|
||||
/* modulation envelope heights */
|
||||
EMU8000_PEFE_WRITE(hw, ch, 0x0);
|
||||
/* lfo1/2 delay */
|
||||
EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);
|
||||
EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);
|
||||
/* lfo1 pitch & cutoff shift */
|
||||
EMU8000_FMMOD_WRITE(hw, ch, 0);
|
||||
/* lfo1 volume & freq */
|
||||
EMU8000_TREMFRQ_WRITE(hw, ch, 0);
|
||||
/* lfo2 pitch & freq */
|
||||
EMU8000_FM2FRQ2_WRITE(hw, ch, 0);
|
||||
/* pan & loop start */
|
||||
temp = rec->panning[ch];
|
||||
temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);
|
||||
EMU8000_PSST_WRITE(hw, ch, temp);
|
||||
/* chorus & loop end (chorus 8bit, MSB) */
|
||||
temp = 0; // chorus
|
||||
temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);
|
||||
EMU8000_CSL_WRITE(hw, ch, temp);
|
||||
/* Q & current address (Q 4bit value, MSB) */
|
||||
temp = 0; // filterQ
|
||||
temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);
|
||||
EMU8000_CCCA_WRITE(hw, ch, temp);
|
||||
/* clear unknown registers */
|
||||
EMU8000_00A0_WRITE(hw, ch, 0);
|
||||
EMU8000_0080_WRITE(hw, ch, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* trigger the voice
|
||||
*/
|
||||
static void start_voice(struct snd_emu8k_pcm *rec, int ch)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_emu8000 *hw = rec->emu;
|
||||
unsigned int temp, aux;
|
||||
int pt = calc_pitch_target(rec->pitch);
|
||||
|
||||
/* cutoff and volume */
|
||||
EMU8000_IFATN_WRITE(hw, ch, 0xff00);
|
||||
EMU8000_VTFT_WRITE(hw, ch, 0xffff);
|
||||
EMU8000_CVCF_WRITE(hw, ch, 0xffff);
|
||||
/* trigger envelope */
|
||||
EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);
|
||||
/* set reverb and pitch target */
|
||||
temp = 0; // reverb
|
||||
if (rec->panning[ch] == 0)
|
||||
aux = 0xff;
|
||||
else
|
||||
aux = (-rec->panning[ch]) & 0xff;
|
||||
temp = (temp << 8) | (pt << 16) | aux;
|
||||
EMU8000_PTRX_WRITE(hw, ch, temp);
|
||||
EMU8000_CPF_WRITE(hw, ch, pt << 16);
|
||||
|
||||
/* start timer */
|
||||
spin_lock_irqsave(&rec->timer_lock, flags);
|
||||
if (! rec->timer_running) {
|
||||
rec->timer.expires = jiffies + 1;
|
||||
add_timer(&rec->timer);
|
||||
rec->timer_running = 1;
|
||||
}
|
||||
spin_unlock_irqrestore(&rec->timer_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* stop the voice immediately
|
||||
*/
|
||||
static void stop_voice(struct snd_emu8k_pcm *rec, int ch)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_emu8000 *hw = rec->emu;
|
||||
|
||||
EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
|
||||
|
||||
/* stop timer */
|
||||
spin_lock_irqsave(&rec->timer_lock, flags);
|
||||
if (rec->timer_running) {
|
||||
del_timer(&rec->timer);
|
||||
rec->timer_running = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&rec->timer_lock, flags);
|
||||
}
|
||||
|
||||
static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
int ch;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
for (ch = 0; ch < rec->voices; ch++)
|
||||
start_voice(rec, ch);
|
||||
rec->running = 1;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
rec->running = 0;
|
||||
for (ch = 0; ch < rec->voices; ch++)
|
||||
stop_voice(rec, ch);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* copy / silence ops
|
||||
*/
|
||||
|
||||
/*
|
||||
* this macro should be inserted in the copy/silence loops
|
||||
* to reduce the latency. without this, the system will hang up
|
||||
* during the whole loop.
|
||||
*/
|
||||
#define CHECK_SCHEDULER() \
|
||||
do { \
|
||||
cond_resched();\
|
||||
if (signal_pending(current))\
|
||||
return -EAGAIN;\
|
||||
} while (0)
|
||||
|
||||
|
||||
#ifdef USE_NONINTERLEAVE
|
||||
/* copy one channel block */
|
||||
static int emu8k_transfer_block(struct snd_emu8000 *emu, int offset, unsigned short *buf, int count)
|
||||
{
|
||||
EMU8000_SMALW_WRITE(emu, offset);
|
||||
while (count > 0) {
|
||||
unsigned short sval;
|
||||
CHECK_SCHEDULER();
|
||||
if (get_user(sval, buf))
|
||||
return -EFAULT;
|
||||
EMU8000_SMLD_WRITE(emu, sval);
|
||||
buf++;
|
||||
count--;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
|
||||
int voice,
|
||||
snd_pcm_uframes_t pos,
|
||||
void *src,
|
||||
snd_pcm_uframes_t count)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
struct snd_emu8000 *emu = rec->emu;
|
||||
|
||||
snd_emu8000_write_wait(emu, 1);
|
||||
if (voice == -1) {
|
||||
unsigned short *buf = src;
|
||||
int i, err;
|
||||
count /= rec->voices;
|
||||
for (i = 0; i < rec->voices; i++) {
|
||||
err = emu8k_transfer_block(emu, pos + rec->loop_start[i], buf, count);
|
||||
if (err < 0)
|
||||
return err;
|
||||
buf += count;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return emu8k_transfer_block(emu, pos + rec->loop_start[voice], src, count);
|
||||
}
|
||||
}
|
||||
|
||||
/* make a channel block silence */
|
||||
static int emu8k_silence_block(struct snd_emu8000 *emu, int offset, int count)
|
||||
{
|
||||
EMU8000_SMALW_WRITE(emu, offset);
|
||||
while (count > 0) {
|
||||
CHECK_SCHEDULER();
|
||||
EMU8000_SMLD_WRITE(emu, 0);
|
||||
count--;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
|
||||
int voice,
|
||||
snd_pcm_uframes_t pos,
|
||||
snd_pcm_uframes_t count)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
struct snd_emu8000 *emu = rec->emu;
|
||||
|
||||
snd_emu8000_write_wait(emu, 1);
|
||||
if (voice == -1 && rec->voices == 1)
|
||||
voice = 0;
|
||||
if (voice == -1) {
|
||||
int err;
|
||||
err = emu8k_silence_block(emu, pos + rec->loop_start[0], count / 2);
|
||||
if (err < 0)
|
||||
return err;
|
||||
return emu8k_silence_block(emu, pos + rec->loop_start[1], count / 2);
|
||||
} else {
|
||||
return emu8k_silence_block(emu, pos + rec->loop_start[voice], count);
|
||||
}
|
||||
}
|
||||
|
||||
#else /* interleave */
|
||||
|
||||
/*
|
||||
* copy the interleaved data can be done easily by using
|
||||
* DMA "left" and "right" channels on emu8k engine.
|
||||
*/
|
||||
static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
|
||||
int voice,
|
||||
snd_pcm_uframes_t pos,
|
||||
void __user *src,
|
||||
snd_pcm_uframes_t count)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
struct snd_emu8000 *emu = rec->emu;
|
||||
unsigned short __user *buf = src;
|
||||
|
||||
snd_emu8000_write_wait(emu, 1);
|
||||
EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]);
|
||||
if (rec->voices > 1)
|
||||
EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]);
|
||||
|
||||
while (count-- > 0) {
|
||||
unsigned short sval;
|
||||
CHECK_SCHEDULER();
|
||||
if (get_user(sval, buf))
|
||||
return -EFAULT;
|
||||
EMU8000_SMLD_WRITE(emu, sval);
|
||||
buf++;
|
||||
if (rec->voices > 1) {
|
||||
CHECK_SCHEDULER();
|
||||
if (get_user(sval, buf))
|
||||
return -EFAULT;
|
||||
EMU8000_SMRD_WRITE(emu, sval);
|
||||
buf++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
|
||||
int voice,
|
||||
snd_pcm_uframes_t pos,
|
||||
snd_pcm_uframes_t count)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
struct snd_emu8000 *emu = rec->emu;
|
||||
|
||||
snd_emu8000_write_wait(emu, 1);
|
||||
EMU8000_SMALW_WRITE(emu, rec->loop_start[0] + pos);
|
||||
if (rec->voices > 1)
|
||||
EMU8000_SMARW_WRITE(emu, rec->loop_start[1] + pos);
|
||||
while (count-- > 0) {
|
||||
CHECK_SCHEDULER();
|
||||
EMU8000_SMLD_WRITE(emu, 0);
|
||||
if (rec->voices > 1) {
|
||||
CHECK_SCHEDULER();
|
||||
EMU8000_SMRD_WRITE(emu, 0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* allocate a memory block
|
||||
*/
|
||||
static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
|
||||
if (rec->block) {
|
||||
/* reallocation - release the old block */
|
||||
snd_util_mem_free(rec->emu->memhdr, rec->block);
|
||||
rec->block = NULL;
|
||||
}
|
||||
|
||||
rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;
|
||||
rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);
|
||||
if (! rec->block)
|
||||
return -ENOMEM;
|
||||
rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */
|
||||
/* at least dma_bytes must be set for non-interleaved mode */
|
||||
subs->dma_buffer.bytes = params_buffer_bytes(hw_params);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* free the memory block
|
||||
*/
|
||||
static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
|
||||
if (rec->block) {
|
||||
int ch;
|
||||
for (ch = 0; ch < rec->voices; ch++)
|
||||
stop_voice(rec, ch); // to be sure
|
||||
if (rec->dram_opened)
|
||||
emu8k_close_dram(rec->emu);
|
||||
snd_util_mem_free(rec->emu->memhdr, rec->block);
|
||||
rec->block = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
static int emu8k_pcm_prepare(struct snd_pcm_substream *subs)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
|
||||
rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);
|
||||
rec->last_ptr = 0;
|
||||
rec->period_pos = 0;
|
||||
|
||||
rec->buf_size = subs->runtime->buffer_size;
|
||||
rec->period_size = subs->runtime->period_size;
|
||||
rec->voices = subs->runtime->channels;
|
||||
rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;
|
||||
if (rec->voices > 1)
|
||||
rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;
|
||||
if (rec->voices > 1) {
|
||||
rec->panning[0] = 0xff;
|
||||
rec->panning[1] = 0x00;
|
||||
} else
|
||||
rec->panning[0] = 0x80;
|
||||
|
||||
if (! rec->dram_opened) {
|
||||
int err, i, ch;
|
||||
|
||||
snd_emux_terminate_all(rec->emu->emu);
|
||||
if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0)
|
||||
return err;
|
||||
rec->dram_opened = 1;
|
||||
|
||||
/* clear loop blanks */
|
||||
snd_emu8000_write_wait(rec->emu, 0);
|
||||
EMU8000_SMALW_WRITE(rec->emu, rec->offset);
|
||||
for (i = 0; i < LOOP_BLANK_SIZE; i++)
|
||||
EMU8000_SMLD_WRITE(rec->emu, 0);
|
||||
for (ch = 0; ch < rec->voices; ch++) {
|
||||
EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);
|
||||
for (i = 0; i < LOOP_BLANK_SIZE; i++)
|
||||
EMU8000_SMLD_WRITE(rec->emu, 0);
|
||||
}
|
||||
}
|
||||
|
||||
setup_voice(rec, 0);
|
||||
if (rec->voices > 1)
|
||||
setup_voice(rec, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs)
|
||||
{
|
||||
struct snd_emu8k_pcm *rec = subs->runtime->private_data;
|
||||
if (rec->running)
|
||||
return emu8k_get_curpos(rec, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct snd_pcm_ops emu8k_pcm_ops = {
|
||||
.open = emu8k_pcm_open,
|
||||
.close = emu8k_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = emu8k_pcm_hw_params,
|
||||
.hw_free = emu8k_pcm_hw_free,
|
||||
.prepare = emu8k_pcm_prepare,
|
||||
.trigger = emu8k_pcm_trigger,
|
||||
.pointer = emu8k_pcm_pointer,
|
||||
.copy = emu8k_pcm_copy,
|
||||
.silence = emu8k_pcm_silence,
|
||||
};
|
||||
|
||||
|
||||
static void snd_emu8000_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_emu8000 *emu = pcm->private_data;
|
||||
emu->pcm = NULL;
|
||||
}
|
||||
|
||||
int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index)
|
||||
{
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
||||
if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0)
|
||||
return err;
|
||||
pcm->private_data = emu;
|
||||
pcm->private_free = snd_emu8000_pcm_free;
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);
|
||||
emu->pcm = pcm;
|
||||
|
||||
snd_device_register(card, pcm);
|
||||
|
||||
return 0;
|
||||
}
|
136
sound/isa/sb/emu8000_synth.c
Normal file
136
sound/isa/sb/emu8000_synth.c
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
|
||||
* Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* Emu8000 synth plug-in routine
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "emu8000_local.h"
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe");
|
||||
MODULE_DESCRIPTION("Emu8000 synth plug-in routine");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* create a new hardware dependent device for Emu8000
|
||||
*/
|
||||
static int snd_emu8000_new_device(struct snd_seq_device *dev)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
struct snd_emux *emu;
|
||||
|
||||
hw = *(struct snd_emu8000**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
if (hw == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (hw->emu)
|
||||
return -EBUSY; /* already exists..? */
|
||||
|
||||
if (snd_emux_new(&emu) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
hw->emu = emu;
|
||||
snd_emu8000_ops_setup(hw);
|
||||
|
||||
emu->hw = hw;
|
||||
emu->max_voices = EMU8000_DRAM_VOICES;
|
||||
emu->num_ports = hw->seq_ports;
|
||||
|
||||
if (hw->memhdr) {
|
||||
snd_printk(KERN_ERR "memhdr is already initialized!?\n");
|
||||
snd_util_memhdr_free(hw->memhdr);
|
||||
}
|
||||
hw->memhdr = snd_util_memhdr_new(hw->mem_size);
|
||||
if (hw->memhdr == NULL) {
|
||||
snd_emux_free(emu);
|
||||
hw->emu = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
emu->memhdr = hw->memhdr;
|
||||
emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */
|
||||
emu->midi_devidx = 1;
|
||||
emu->linear_panning = 1;
|
||||
emu->hwdep_idx = 2; /* FIXED */
|
||||
|
||||
if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) {
|
||||
snd_emux_free(emu);
|
||||
snd_util_memhdr_free(hw->memhdr);
|
||||
hw->emu = NULL;
|
||||
hw->memhdr = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (hw->mem_size > 0)
|
||||
snd_emu8000_pcm_new(dev->card, hw, 1);
|
||||
|
||||
dev->driver_data = hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* free all resources
|
||||
*/
|
||||
static int snd_emu8000_delete_device(struct snd_seq_device *dev)
|
||||
{
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
if (dev->driver_data == NULL)
|
||||
return 0; /* no synth was allocated actually */
|
||||
|
||||
hw = dev->driver_data;
|
||||
if (hw->pcm)
|
||||
snd_device_free(dev->card, hw->pcm);
|
||||
if (hw->emu)
|
||||
snd_emux_free(hw->emu);
|
||||
if (hw->memhdr)
|
||||
snd_util_memhdr_free(hw->memhdr);
|
||||
hw->emu = NULL;
|
||||
hw->memhdr = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_emu8000_init(void)
|
||||
{
|
||||
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_emu8000_new_device,
|
||||
snd_emu8000_delete_device,
|
||||
};
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU8000, &ops,
|
||||
sizeof(struct snd_emu8000*));
|
||||
}
|
||||
|
||||
static void __exit alsa_emu8000_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU8000);
|
||||
}
|
||||
|
||||
module_init(alsa_emu8000_init)
|
||||
module_exit(alsa_emu8000_exit)
|
401
sound/isa/sb/jazz16.c
Normal file
401
sound/isa/sb/jazz16.c
Normal file
|
@ -0,0 +1,401 @@
|
|||
|
||||
/*
|
||||
* jazz16.c - driver for Media Vision Jazz16 based soundcards.
|
||||
* Copyright (C) 2009 Krzysztof Helt <krzysztof.h1@wp.pl>
|
||||
* Based on patches posted by Rask Ingemann Lambertsen and Rene Herman.
|
||||
* Based on OSS Sound Blaster driver.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file COPYING in the main directory of this archive for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <asm/dma.h>
|
||||
#include <linux/isa.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/mpu401.h>
|
||||
#include <sound/opl3.h>
|
||||
#include <sound/sb.h>
|
||||
#define SNDRV_LEGACY_FIND_FREE_IRQ
|
||||
#define SNDRV_LEGACY_FIND_FREE_DMA
|
||||
#include <sound/initval.h>
|
||||
|
||||
#define PFX "jazz16: "
|
||||
|
||||
MODULE_DESCRIPTION("Media Vision Jazz16");
|
||||
MODULE_SUPPORTED_DEVICE("{{Media Vision ??? },"
|
||||
"{RTL,RTL3000}}");
|
||||
|
||||
MODULE_AUTHOR("Krzysztof Helt <krzysztof.h1@wp.pl>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
|
||||
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
|
||||
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
|
||||
static unsigned long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
|
||||
static unsigned long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
|
||||
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
|
||||
static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
|
||||
static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
|
||||
static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
|
||||
|
||||
module_param_array(index, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(index, "Index value for Media Vision Jazz16 based soundcard.");
|
||||
module_param_array(id, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(id, "ID string for Media Vision Jazz16 based soundcard.");
|
||||
module_param_array(enable, bool, NULL, 0444);
|
||||
MODULE_PARM_DESC(enable, "Enable Media Vision Jazz16 based soundcard.");
|
||||
module_param_array(port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(port, "Port # for jazz16 driver.");
|
||||
module_param_array(mpu_port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(mpu_port, "MPU-401 port # for jazz16 driver.");
|
||||
module_param_array(irq, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(irq, "IRQ # for jazz16 driver.");
|
||||
module_param_array(mpu_irq, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for jazz16 driver.");
|
||||
module_param_array(dma8, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(dma8, "DMA8 # for jazz16 driver.");
|
||||
module_param_array(dma16, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(dma16, "DMA16 # for jazz16 driver.");
|
||||
|
||||
#define SB_JAZZ16_WAKEUP 0xaf
|
||||
#define SB_JAZZ16_SET_PORTS 0x50
|
||||
#define SB_DSP_GET_JAZZ_BRD_REV 0xfa
|
||||
#define SB_JAZZ16_SET_DMAINTR 0xfb
|
||||
#define SB_DSP_GET_JAZZ_MODEL 0xfe
|
||||
|
||||
struct snd_card_jazz16 {
|
||||
struct snd_sb *chip;
|
||||
};
|
||||
|
||||
static irqreturn_t jazz16_interrupt(int irq, void *chip)
|
||||
{
|
||||
return snd_sb8dsp_interrupt(chip);
|
||||
}
|
||||
|
||||
static int jazz16_configure_ports(unsigned long port,
|
||||
unsigned long mpu_port, int idx)
|
||||
{
|
||||
unsigned char val;
|
||||
|
||||
if (!request_region(0x201, 1, "jazz16 config")) {
|
||||
snd_printk(KERN_ERR "config port region is already in use.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
outb(SB_JAZZ16_WAKEUP - idx, 0x201);
|
||||
udelay(100);
|
||||
outb(SB_JAZZ16_SET_PORTS + idx, 0x201);
|
||||
udelay(100);
|
||||
val = port & 0x70;
|
||||
val |= (mpu_port & 0x30) >> 4;
|
||||
outb(val, 0x201);
|
||||
|
||||
release_region(0x201, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jazz16_detect_board(unsigned long port,
|
||||
unsigned long mpu_port)
|
||||
{
|
||||
int err;
|
||||
int val;
|
||||
struct snd_sb chip;
|
||||
|
||||
if (!request_region(port, 0x10, "jazz16")) {
|
||||
snd_printk(KERN_ERR "I/O port region is already in use.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
/* just to call snd_sbdsp_command/reset/get_byte() */
|
||||
chip.port = port;
|
||||
|
||||
err = snd_sbdsp_reset(&chip);
|
||||
if (err < 0)
|
||||
for (val = 0; val < 4; val++) {
|
||||
err = jazz16_configure_ports(port, mpu_port, val);
|
||||
if (err < 0)
|
||||
break;
|
||||
|
||||
err = snd_sbdsp_reset(&chip);
|
||||
if (!err)
|
||||
break;
|
||||
}
|
||||
if (err < 0) {
|
||||
err = -ENODEV;
|
||||
goto err_unmap;
|
||||
}
|
||||
if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_BRD_REV)) {
|
||||
err = -EBUSY;
|
||||
goto err_unmap;
|
||||
}
|
||||
val = snd_sbdsp_get_byte(&chip);
|
||||
if (val >= 0x30)
|
||||
snd_sbdsp_get_byte(&chip);
|
||||
|
||||
if ((val & 0xf0) != 0x10) {
|
||||
err = -ENODEV;
|
||||
goto err_unmap;
|
||||
}
|
||||
if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_MODEL)) {
|
||||
err = -EBUSY;
|
||||
goto err_unmap;
|
||||
}
|
||||
snd_sbdsp_get_byte(&chip);
|
||||
err = snd_sbdsp_get_byte(&chip);
|
||||
snd_printd("Media Vision Jazz16 board detected: rev 0x%x, model 0x%x\n",
|
||||
val, err);
|
||||
|
||||
err = 0;
|
||||
|
||||
err_unmap:
|
||||
release_region(port, 0x10);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int jazz16_configure_board(struct snd_sb *chip, int mpu_irq)
|
||||
{
|
||||
static unsigned char jazz_irq_bits[] = { 0, 0, 2, 3, 0, 1, 0, 4,
|
||||
0, 2, 5, 0, 0, 0, 0, 6 };
|
||||
static unsigned char jazz_dma_bits[] = { 0, 1, 0, 2, 0, 3, 0, 4 };
|
||||
|
||||
if (jazz_dma_bits[chip->dma8] == 0 ||
|
||||
jazz_dma_bits[chip->dma16] == 0 ||
|
||||
jazz_irq_bits[chip->irq] == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!snd_sbdsp_command(chip, SB_JAZZ16_SET_DMAINTR))
|
||||
return -EBUSY;
|
||||
|
||||
if (!snd_sbdsp_command(chip,
|
||||
jazz_dma_bits[chip->dma8] |
|
||||
(jazz_dma_bits[chip->dma16] << 4)))
|
||||
return -EBUSY;
|
||||
|
||||
if (!snd_sbdsp_command(chip,
|
||||
jazz_irq_bits[chip->irq] |
|
||||
(jazz_irq_bits[mpu_irq] << 4)))
|
||||
return -EBUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_jazz16_match(struct device *devptr, unsigned int dev)
|
||||
{
|
||||
if (!enable[dev])
|
||||
return 0;
|
||||
if (port[dev] == SNDRV_AUTO_PORT) {
|
||||
snd_printk(KERN_ERR "please specify port\n");
|
||||
return 0;
|
||||
} else if (port[dev] == 0x200 || (port[dev] & ~0x270)) {
|
||||
snd_printk(KERN_ERR "incorrect port specified\n");
|
||||
return 0;
|
||||
}
|
||||
if (dma8[dev] != SNDRV_AUTO_DMA &&
|
||||
dma8[dev] != 1 && dma8[dev] != 3) {
|
||||
snd_printk(KERN_ERR "dma8 must be 1 or 3\n");
|
||||
return 0;
|
||||
}
|
||||
if (dma16[dev] != SNDRV_AUTO_DMA &&
|
||||
dma16[dev] != 5 && dma16[dev] != 7) {
|
||||
snd_printk(KERN_ERR "dma16 must be 5 or 7\n");
|
||||
return 0;
|
||||
}
|
||||
if (mpu_port[dev] != SNDRV_AUTO_PORT &&
|
||||
(mpu_port[dev] & ~0x030) != 0x300) {
|
||||
snd_printk(KERN_ERR "incorrect mpu_port specified\n");
|
||||
return 0;
|
||||
}
|
||||
if (mpu_irq[dev] != SNDRV_AUTO_DMA &&
|
||||
mpu_irq[dev] != 2 && mpu_irq[dev] != 3 &&
|
||||
mpu_irq[dev] != 5 && mpu_irq[dev] != 7) {
|
||||
snd_printk(KERN_ERR "mpu_irq must be 2, 3, 5 or 7\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int snd_jazz16_probe(struct device *devptr, unsigned int dev)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct snd_card_jazz16 *jazz16;
|
||||
struct snd_sb *chip;
|
||||
struct snd_opl3 *opl3;
|
||||
static int possible_irqs[] = {2, 3, 5, 7, 9, 10, 15, -1};
|
||||
static int possible_dmas8[] = {1, 3, -1};
|
||||
static int possible_dmas16[] = {5, 7, -1};
|
||||
int err, xirq, xdma8, xdma16, xmpu_port, xmpu_irq;
|
||||
|
||||
err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
|
||||
sizeof(struct snd_card_jazz16), &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
jazz16 = card->private_data;
|
||||
|
||||
xirq = irq[dev];
|
||||
if (xirq == SNDRV_AUTO_IRQ) {
|
||||
xirq = snd_legacy_find_free_irq(possible_irqs);
|
||||
if (xirq < 0) {
|
||||
snd_printk(KERN_ERR "unable to find a free IRQ\n");
|
||||
err = -EBUSY;
|
||||
goto err_free;
|
||||
}
|
||||
}
|
||||
xdma8 = dma8[dev];
|
||||
if (xdma8 == SNDRV_AUTO_DMA) {
|
||||
xdma8 = snd_legacy_find_free_dma(possible_dmas8);
|
||||
if (xdma8 < 0) {
|
||||
snd_printk(KERN_ERR "unable to find a free DMA8\n");
|
||||
err = -EBUSY;
|
||||
goto err_free;
|
||||
}
|
||||
}
|
||||
xdma16 = dma16[dev];
|
||||
if (xdma16 == SNDRV_AUTO_DMA) {
|
||||
xdma16 = snd_legacy_find_free_dma(possible_dmas16);
|
||||
if (xdma16 < 0) {
|
||||
snd_printk(KERN_ERR "unable to find a free DMA16\n");
|
||||
err = -EBUSY;
|
||||
goto err_free;
|
||||
}
|
||||
}
|
||||
|
||||
xmpu_port = mpu_port[dev];
|
||||
if (xmpu_port == SNDRV_AUTO_PORT)
|
||||
xmpu_port = 0;
|
||||
err = jazz16_detect_board(port[dev], xmpu_port);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "Media Vision Jazz16 board not detected\n");
|
||||
goto err_free;
|
||||
}
|
||||
err = snd_sbdsp_create(card, port[dev], irq[dev],
|
||||
jazz16_interrupt,
|
||||
dma8[dev], dma16[dev],
|
||||
SB_HW_JAZZ16,
|
||||
&chip);
|
||||
if (err < 0)
|
||||
goto err_free;
|
||||
|
||||
xmpu_irq = mpu_irq[dev];
|
||||
if (xmpu_irq == SNDRV_AUTO_IRQ || mpu_port[dev] == SNDRV_AUTO_PORT)
|
||||
xmpu_irq = 0;
|
||||
err = jazz16_configure_board(chip, xmpu_irq);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "Media Vision Jazz16 configuration failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
jazz16->chip = chip;
|
||||
|
||||
strcpy(card->driver, "jazz16");
|
||||
strcpy(card->shortname, "Media Vision Jazz16");
|
||||
sprintf(card->longname,
|
||||
"Media Vision Jazz16 at 0x%lx, irq %d, dma8 %d, dma16 %d",
|
||||
port[dev], xirq, xdma8, xdma16);
|
||||
|
||||
err = snd_sb8dsp_pcm(chip, 0, NULL);
|
||||
if (err < 0)
|
||||
goto err_free;
|
||||
err = snd_sbmixer_new(chip);
|
||||
if (err < 0)
|
||||
goto err_free;
|
||||
|
||||
err = snd_opl3_create(card, chip->port, chip->port + 2,
|
||||
OPL3_HW_AUTO, 1, &opl3);
|
||||
if (err < 0)
|
||||
snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
|
||||
chip->port, chip->port + 2);
|
||||
else {
|
||||
err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
|
||||
if (err < 0)
|
||||
goto err_free;
|
||||
}
|
||||
if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
|
||||
if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
|
||||
mpu_irq[dev] = -1;
|
||||
|
||||
if (snd_mpu401_uart_new(card, 0,
|
||||
MPU401_HW_MPU401,
|
||||
mpu_port[dev], 0,
|
||||
mpu_irq[dev],
|
||||
NULL) < 0)
|
||||
snd_printk(KERN_ERR "no MPU-401 device at 0x%lx\n",
|
||||
mpu_port[dev]);
|
||||
}
|
||||
|
||||
err = snd_card_register(card);
|
||||
if (err < 0)
|
||||
goto err_free;
|
||||
|
||||
dev_set_drvdata(devptr, card);
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_jazz16_remove(struct device *devptr, unsigned int dev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(devptr);
|
||||
|
||||
snd_card_free(card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int snd_jazz16_suspend(struct device *pdev, unsigned int n,
|
||||
pm_message_t state)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct snd_card_jazz16 *acard = card->private_data;
|
||||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
snd_sbmixer_suspend(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_jazz16_resume(struct device *pdev, unsigned int n)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct snd_card_jazz16 *acard = card->private_data;
|
||||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_sbdsp_reset(chip);
|
||||
snd_sbmixer_resume(chip);
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct isa_driver snd_jazz16_driver = {
|
||||
.match = snd_jazz16_match,
|
||||
.probe = snd_jazz16_probe,
|
||||
.remove = snd_jazz16_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = snd_jazz16_suspend,
|
||||
.resume = snd_jazz16_resume,
|
||||
#endif
|
||||
.driver = {
|
||||
.name = "jazz16"
|
||||
},
|
||||
};
|
||||
|
||||
static int __init alsa_card_jazz16_init(void)
|
||||
{
|
||||
return isa_register_driver(&snd_jazz16_driver, SNDRV_CARDS);
|
||||
}
|
||||
|
||||
static void __exit alsa_card_jazz16_exit(void)
|
||||
{
|
||||
isa_unregister_driver(&snd_jazz16_driver);
|
||||
}
|
||||
|
||||
module_init(alsa_card_jazz16_init)
|
||||
module_exit(alsa_card_jazz16_exit)
|
697
sound/isa/sb/sb16.c
Normal file
697
sound/isa/sb/sb16.c
Normal file
|
@ -0,0 +1,697 @@
|
|||
/*
|
||||
* Driver for SoundBlaster 16/AWE32/AWE64 soundcards
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <asm/dma.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/pnp.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/isa.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
#include <sound/sb16_csp.h>
|
||||
#include <sound/mpu401.h>
|
||||
#include <sound/opl3.h>
|
||||
#include <sound/emu8000.h>
|
||||
#include <sound/seq_device.h>
|
||||
#define SNDRV_LEGACY_FIND_FREE_IRQ
|
||||
#define SNDRV_LEGACY_FIND_FREE_DMA
|
||||
#include <sound/initval.h>
|
||||
|
||||
#ifdef SNDRV_SBAWE
|
||||
#define PFX "sbawe: "
|
||||
#else
|
||||
#define PFX "sb16: "
|
||||
#endif
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_LICENSE("GPL");
|
||||
#ifndef SNDRV_SBAWE
|
||||
MODULE_DESCRIPTION("Sound Blaster 16");
|
||||
MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 16},"
|
||||
"{Creative Labs,SB Vibra16S},"
|
||||
"{Creative Labs,SB Vibra16C},"
|
||||
"{Creative Labs,SB Vibra16CL},"
|
||||
"{Creative Labs,SB Vibra16X}}");
|
||||
#else
|
||||
MODULE_DESCRIPTION("Sound Blaster AWE");
|
||||
MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB AWE 32},"
|
||||
"{Creative Labs,SB AWE 64},"
|
||||
"{Creative Labs,SB AWE 64 Gold}}");
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define SNDRV_DEBUG_IRQ
|
||||
#endif
|
||||
|
||||
#if defined(SNDRV_SBAWE) && (defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)))
|
||||
#define SNDRV_SBAWE_EMU8000
|
||||
#endif
|
||||
|
||||
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
|
||||
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
|
||||
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
|
||||
#ifdef CONFIG_PNP
|
||||
static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
|
||||
#endif
|
||||
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260,0x280 */
|
||||
static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x330,0x300 */
|
||||
static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
|
||||
#endif
|
||||
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
|
||||
static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */
|
||||
static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 5,6,7 */
|
||||
static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
|
||||
#ifdef CONFIG_SND_SB16_CSP
|
||||
static int csp[SNDRV_CARDS];
|
||||
#endif
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
|
||||
#endif
|
||||
|
||||
module_param_array(index, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard.");
|
||||
module_param_array(id, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard.");
|
||||
module_param_array(enable, bool, NULL, 0444);
|
||||
MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard.");
|
||||
#ifdef CONFIG_PNP
|
||||
module_param_array(isapnp, bool, NULL, 0444);
|
||||
MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
|
||||
#endif
|
||||
module_param_array(port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(port, "Port # for SB16 driver.");
|
||||
module_param_array(mpu_port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver.");
|
||||
module_param_array(fm_port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver.");
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
module_param_array(awe_port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver.");
|
||||
#endif
|
||||
module_param_array(irq, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(irq, "IRQ # for SB16 driver.");
|
||||
module_param_array(dma8, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver.");
|
||||
module_param_array(dma16, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver.");
|
||||
module_param_array(mic_agc, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch.");
|
||||
#ifdef CONFIG_SND_SB16_CSP
|
||||
module_param_array(csp, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(csp, "ASP/CSP chip support.");
|
||||
#endif
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
module_param_array(seq_ports, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth.");
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
static int isa_registered;
|
||||
static int pnp_registered;
|
||||
#endif
|
||||
|
||||
struct snd_card_sb16 {
|
||||
struct resource *fm_res; /* used to block FM i/o region for legacy cards */
|
||||
struct snd_sb *chip;
|
||||
#ifdef CONFIG_PNP
|
||||
int dev_no;
|
||||
struct pnp_dev *dev;
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
struct pnp_dev *devwt;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
|
||||
static struct pnp_card_device_id snd_sb16_pnpids[] = {
|
||||
#ifndef SNDRV_SBAWE
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL0024", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL0025", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL0026", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL0027", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL0028", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL0029", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL002a", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
/* Note: This card has also a CTL0051:StereoEnhance device!!! */
|
||||
{ .id = "CTL002b", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster 16 PnP */
|
||||
{ .id = "CTL002c", .devs = { { "CTL0031" } } },
|
||||
/* Sound Blaster Vibra16S */
|
||||
{ .id = "CTL0051", .devs = { { "CTL0001" } } },
|
||||
/* Sound Blaster Vibra16C */
|
||||
{ .id = "CTL0070", .devs = { { "CTL0001" } } },
|
||||
/* Sound Blaster Vibra16CL - added by ctm@ardi.com */
|
||||
{ .id = "CTL0080", .devs = { { "CTL0041" } } },
|
||||
/* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */
|
||||
/* but ct4131 on a sticker on the board.. */
|
||||
{ .id = "CTL0086", .devs = { { "CTL0041" } } },
|
||||
/* Sound Blaster Vibra16X */
|
||||
{ .id = "CTL00f0", .devs = { { "CTL0043" } } },
|
||||
/* Sound Blaster 16 (Virtual PC 2004) */
|
||||
{ .id = "tBA03b0", .devs = { {.id="PNPb003" } } },
|
||||
#else /* SNDRV_SBAWE defined */
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
/* Note: This card has also a CTL0051:StereoEnhance device!!! */
|
||||
{ .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
/* Note: This card has also a CTL0051:StereoEnhance device!!! */
|
||||
{ .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 32 PnP */
|
||||
{ .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } },
|
||||
/* Sound Blaster 32 PnP */
|
||||
{ .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } },
|
||||
/* Sound Blaster AWE 64 PnP Gold */
|
||||
{ .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } },
|
||||
/* Sound Blaster AWE 64 PnP Gold */
|
||||
{ .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } },
|
||||
/* Sound Blaster AWE 64 PnP */
|
||||
{ .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } },
|
||||
/* Sound Blaster 16 PnP (AWE) */
|
||||
{ .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } },
|
||||
/* Generic entries */
|
||||
{ .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } },
|
||||
{ .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } },
|
||||
{ .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } },
|
||||
{ .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } },
|
||||
{ .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } },
|
||||
#endif /* SNDRV_SBAWE */
|
||||
{ .id = "", }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids);
|
||||
|
||||
#endif /* CONFIG_PNP */
|
||||
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
#define DRIVER_NAME "snd-card-sbawe"
|
||||
#else
|
||||
#define DRIVER_NAME "snd-card-sb16"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
|
||||
static int snd_card_sb16_pnp(int dev, struct snd_card_sb16 *acard,
|
||||
struct pnp_card_link *card,
|
||||
const struct pnp_card_device_id *id)
|
||||
{
|
||||
struct pnp_dev *pdev;
|
||||
int err;
|
||||
|
||||
acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
|
||||
if (acard->dev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev);
|
||||
#endif
|
||||
/* Audio initialization */
|
||||
pdev = acard->dev;
|
||||
|
||||
err = pnp_activate_dev(pdev);
|
||||
if (err < 0) {
|
||||
snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
|
||||
return err;
|
||||
}
|
||||
port[dev] = pnp_port_start(pdev, 0);
|
||||
mpu_port[dev] = pnp_port_start(pdev, 1);
|
||||
fm_port[dev] = pnp_port_start(pdev, 2);
|
||||
dma8[dev] = pnp_dma(pdev, 0);
|
||||
dma16[dev] = pnp_dma(pdev, 1);
|
||||
irq[dev] = pnp_irq(pdev, 0);
|
||||
snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n",
|
||||
port[dev], mpu_port[dev], fm_port[dev]);
|
||||
snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n",
|
||||
dma8[dev], dma16[dev], irq[dev]);
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
/* WaveTable initialization */
|
||||
pdev = acard->devwt;
|
||||
if (pdev != NULL) {
|
||||
err = pnp_activate_dev(pdev);
|
||||
if (err < 0) {
|
||||
goto __wt_error;
|
||||
}
|
||||
awe_port[dev] = pnp_port_start(pdev, 0);
|
||||
snd_printdd("pnp SB16: wavetable port=0x%llx\n",
|
||||
(unsigned long long)pnp_port_start(pdev, 0));
|
||||
} else {
|
||||
__wt_error:
|
||||
if (pdev) {
|
||||
pnp_release_card_device(pdev);
|
||||
snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n");
|
||||
}
|
||||
acard->devwt = NULL;
|
||||
awe_port[dev] = -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PNP */
|
||||
|
||||
static void snd_sb16_free(struct snd_card *card)
|
||||
{
|
||||
struct snd_card_sb16 *acard = card->private_data;
|
||||
|
||||
if (acard == NULL)
|
||||
return;
|
||||
release_and_free_resource(acard->fm_res);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
#define is_isapnp_selected(dev) isapnp[dev]
|
||||
#else
|
||||
#define is_isapnp_selected(dev) 0
|
||||
#endif
|
||||
|
||||
static int snd_sb16_card_new(struct device *devptr, int dev,
|
||||
struct snd_card **cardp)
|
||||
{
|
||||
struct snd_card *card;
|
||||
int err;
|
||||
|
||||
err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
|
||||
sizeof(struct snd_card_sb16), &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
card->private_free = snd_sb16_free;
|
||||
*cardp = card;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_probe(struct snd_card *card, int dev)
|
||||
{
|
||||
int xirq, xdma8, xdma16;
|
||||
struct snd_sb *chip;
|
||||
struct snd_card_sb16 *acard = card->private_data;
|
||||
struct snd_opl3 *opl3;
|
||||
struct snd_hwdep *synth = NULL;
|
||||
#ifdef CONFIG_SND_SB16_CSP
|
||||
struct snd_hwdep *xcsp = NULL;
|
||||
#endif
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
xirq = irq[dev];
|
||||
xdma8 = dma8[dev];
|
||||
xdma16 = dma16[dev];
|
||||
|
||||
if ((err = snd_sbdsp_create(card,
|
||||
port[dev],
|
||||
xirq,
|
||||
snd_sb16dsp_interrupt,
|
||||
xdma8,
|
||||
xdma16,
|
||||
SB_HW_AUTO,
|
||||
&chip)) < 0)
|
||||
return err;
|
||||
|
||||
acard->chip = chip;
|
||||
if (chip->hardware != SB_HW_16) {
|
||||
snd_printk(KERN_ERR PFX "SB 16 chip was not detected at 0x%lx\n", port[dev]);
|
||||
return -ENODEV;
|
||||
}
|
||||
chip->mpu_port = mpu_port[dev];
|
||||
if (! is_isapnp_selected(dev) && (err = snd_sb16dsp_configure(chip)) < 0)
|
||||
return err;
|
||||
|
||||
if ((err = snd_sb16dsp_pcm(chip, 0, &chip->pcm)) < 0)
|
||||
return err;
|
||||
|
||||
strcpy(card->driver,
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
awe_port[dev] > 0 ? "SB AWE" :
|
||||
#endif
|
||||
"SB16");
|
||||
strcpy(card->shortname, chip->name);
|
||||
sprintf(card->longname, "%s at 0x%lx, irq %i, dma ",
|
||||
chip->name,
|
||||
chip->port,
|
||||
xirq);
|
||||
if (xdma8 >= 0)
|
||||
sprintf(card->longname + strlen(card->longname), "%d", xdma8);
|
||||
if (xdma16 >= 0)
|
||||
sprintf(card->longname + strlen(card->longname), "%s%d",
|
||||
xdma8 >= 0 ? "&" : "", xdma16);
|
||||
|
||||
if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
|
||||
if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB,
|
||||
chip->mpu_port,
|
||||
MPU401_INFO_IRQ_HOOK, -1,
|
||||
&chip->rmidi)) < 0)
|
||||
return err;
|
||||
chip->rmidi_callback = snd_mpu401_uart_interrupt;
|
||||
}
|
||||
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
if (awe_port[dev] == SNDRV_AUTO_PORT)
|
||||
awe_port[dev] = 0; /* disable */
|
||||
#endif
|
||||
|
||||
if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
|
||||
if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
|
||||
OPL3_HW_OPL3,
|
||||
acard->fm_res != NULL || fm_port[dev] == port[dev],
|
||||
&opl3) < 0) {
|
||||
snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
|
||||
fm_port[dev], fm_port[dev] + 2);
|
||||
} else {
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
int seqdev = awe_port[dev] > 0 ? 2 : 1;
|
||||
#else
|
||||
int seqdev = 1;
|
||||
#endif
|
||||
if ((err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth)) < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if ((err = snd_sbmixer_new(chip)) < 0)
|
||||
return err;
|
||||
|
||||
#ifdef CONFIG_SND_SB16_CSP
|
||||
/* CSP chip on SB16ASP/AWE32 */
|
||||
if ((chip->hardware == SB_HW_16) && csp[dev]) {
|
||||
snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp);
|
||||
if (xcsp) {
|
||||
chip->csp = xcsp->private_data;
|
||||
chip->hardware = SB_HW_16CSP;
|
||||
} else {
|
||||
snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
if (awe_port[dev] > 0) {
|
||||
if ((err = snd_emu8000_new(card, 1, awe_port[dev],
|
||||
seq_ports[dev], NULL)) < 0) {
|
||||
snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]);
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* setup Mic AGC */
|
||||
spin_lock_irqsave(&chip->mixer_lock, flags);
|
||||
snd_sbmixer_write(chip, SB_DSP4_MIC_AGC,
|
||||
(snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) |
|
||||
(mic_agc[dev] ? 0x00 : 0x01));
|
||||
spin_unlock_irqrestore(&chip->mixer_lock, flags);
|
||||
|
||||
if ((err = snd_card_register(card)) < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int snd_sb16_suspend(struct snd_card *card, pm_message_t state)
|
||||
{
|
||||
struct snd_card_sb16 *acard = card->private_data;
|
||||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
snd_sbmixer_suspend(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_resume(struct snd_card *card)
|
||||
{
|
||||
struct snd_card_sb16 *acard = card->private_data;
|
||||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_sbdsp_reset(chip);
|
||||
snd_sbmixer_resume(chip);
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int snd_sb16_isa_probe1(int dev, struct device *pdev)
|
||||
{
|
||||
struct snd_card_sb16 *acard;
|
||||
struct snd_card *card;
|
||||
int err;
|
||||
|
||||
err = snd_sb16_card_new(pdev, dev, &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
acard = card->private_data;
|
||||
/* non-PnP FM port address is hardwired with base port address */
|
||||
fm_port[dev] = port[dev];
|
||||
/* block the 0x388 port to avoid PnP conflicts */
|
||||
acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
|
||||
#ifdef SNDRV_SBAWE_EMU8000
|
||||
/* non-PnP AWE port address is hardwired with base port address */
|
||||
awe_port[dev] = port[dev] + 0x400;
|
||||
#endif
|
||||
|
||||
if ((err = snd_sb16_probe(card, dev)) < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
dev_set_drvdata(pdev, card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int snd_sb16_isa_match(struct device *pdev, unsigned int dev)
|
||||
{
|
||||
return enable[dev] && !is_isapnp_selected(dev);
|
||||
}
|
||||
|
||||
static int snd_sb16_isa_probe(struct device *pdev, unsigned int dev)
|
||||
{
|
||||
int err;
|
||||
static int possible_irqs[] = {5, 9, 10, 7, -1};
|
||||
static int possible_dmas8[] = {1, 3, 0, -1};
|
||||
static int possible_dmas16[] = {5, 6, 7, -1};
|
||||
|
||||
if (irq[dev] == SNDRV_AUTO_IRQ) {
|
||||
if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
|
||||
snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
if (dma8[dev] == SNDRV_AUTO_DMA) {
|
||||
if ((dma8[dev] = snd_legacy_find_free_dma(possible_dmas8)) < 0) {
|
||||
snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
if (dma16[dev] == SNDRV_AUTO_DMA) {
|
||||
if ((dma16[dev] = snd_legacy_find_free_dma(possible_dmas16)) < 0) {
|
||||
snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
|
||||
if (port[dev] != SNDRV_AUTO_PORT)
|
||||
return snd_sb16_isa_probe1(dev, pdev);
|
||||
else {
|
||||
static int possible_ports[] = {0x220, 0x240, 0x260, 0x280};
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
|
||||
port[dev] = possible_ports[i];
|
||||
err = snd_sb16_isa_probe1(dev, pdev);
|
||||
if (! err)
|
||||
return 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
static int snd_sb16_isa_remove(struct device *pdev, unsigned int dev)
|
||||
{
|
||||
snd_card_free(dev_get_drvdata(pdev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int snd_sb16_isa_suspend(struct device *dev, unsigned int n,
|
||||
pm_message_t state)
|
||||
{
|
||||
return snd_sb16_suspend(dev_get_drvdata(dev), state);
|
||||
}
|
||||
|
||||
static int snd_sb16_isa_resume(struct device *dev, unsigned int n)
|
||||
{
|
||||
return snd_sb16_resume(dev_get_drvdata(dev));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SNDRV_SBAWE
|
||||
#define DEV_NAME "sbawe"
|
||||
#else
|
||||
#define DEV_NAME "sb16"
|
||||
#endif
|
||||
|
||||
static struct isa_driver snd_sb16_isa_driver = {
|
||||
.match = snd_sb16_isa_match,
|
||||
.probe = snd_sb16_isa_probe,
|
||||
.remove = snd_sb16_isa_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = snd_sb16_isa_suspend,
|
||||
.resume = snd_sb16_isa_resume,
|
||||
#endif
|
||||
.driver = {
|
||||
.name = DEV_NAME
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
static int snd_sb16_pnp_detect(struct pnp_card_link *pcard,
|
||||
const struct pnp_card_device_id *pid)
|
||||
{
|
||||
static int dev;
|
||||
struct snd_card *card;
|
||||
int res;
|
||||
|
||||
for ( ; dev < SNDRV_CARDS; dev++) {
|
||||
if (!enable[dev] || !isapnp[dev])
|
||||
continue;
|
||||
res = snd_sb16_card_new(&pcard->card->dev, dev, &card);
|
||||
if (res < 0)
|
||||
return res;
|
||||
if ((res = snd_card_sb16_pnp(dev, card->private_data, pcard, pid)) < 0 ||
|
||||
(res = snd_sb16_probe(card, dev)) < 0) {
|
||||
snd_card_free(card);
|
||||
return res;
|
||||
}
|
||||
pnp_set_card_drvdata(pcard, card);
|
||||
dev++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static void snd_sb16_pnp_remove(struct pnp_card_link *pcard)
|
||||
{
|
||||
snd_card_free(pnp_get_card_drvdata(pcard));
|
||||
pnp_set_card_drvdata(pcard, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int snd_sb16_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
|
||||
{
|
||||
return snd_sb16_suspend(pnp_get_card_drvdata(pcard), state);
|
||||
}
|
||||
static int snd_sb16_pnp_resume(struct pnp_card_link *pcard)
|
||||
{
|
||||
return snd_sb16_resume(pnp_get_card_drvdata(pcard));
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct pnp_card_driver sb16_pnpc_driver = {
|
||||
.flags = PNP_DRIVER_RES_DISABLE,
|
||||
#ifdef SNDRV_SBAWE
|
||||
.name = "sbawe",
|
||||
#else
|
||||
.name = "sb16",
|
||||
#endif
|
||||
.id_table = snd_sb16_pnpids,
|
||||
.probe = snd_sb16_pnp_detect,
|
||||
.remove = snd_sb16_pnp_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = snd_sb16_pnp_suspend,
|
||||
.resume = snd_sb16_pnp_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* CONFIG_PNP */
|
||||
|
||||
static int __init alsa_card_sb16_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = isa_register_driver(&snd_sb16_isa_driver, SNDRV_CARDS);
|
||||
#ifdef CONFIG_PNP
|
||||
if (!err)
|
||||
isa_registered = 1;
|
||||
|
||||
err = pnp_register_card_driver(&sb16_pnpc_driver);
|
||||
if (!err)
|
||||
pnp_registered = 1;
|
||||
|
||||
if (isa_registered)
|
||||
err = 0;
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit alsa_card_sb16_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_PNP
|
||||
if (pnp_registered)
|
||||
pnp_unregister_card_driver(&sb16_pnpc_driver);
|
||||
if (isa_registered)
|
||||
#endif
|
||||
isa_unregister_driver(&snd_sb16_isa_driver);
|
||||
}
|
||||
|
||||
module_init(alsa_card_sb16_init)
|
||||
module_exit(alsa_card_sb16_exit)
|
1203
sound/isa/sb/sb16_csp.c
Normal file
1203
sound/isa/sb/sb16_csp.c
Normal file
File diff suppressed because it is too large
Load diff
925
sound/isa/sb/sb16_main.c
Normal file
925
sound/isa/sb/sb16_main.c
Normal file
|
@ -0,0 +1,925 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* Routines for control of 16-bit SoundBlaster cards and clones
|
||||
* Note: This is very ugly hardware which uses one 8-bit DMA channel and
|
||||
* second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't
|
||||
* transfer 16-bit samples and 16-bit DMA channels can't transfer
|
||||
* 8-bit samples. This make full duplex more complicated than
|
||||
* can be... People, don't buy these soundcards for full 16-bit
|
||||
* duplex!!!
|
||||
* Note: 16-bit wide is assigned to first direction which made request.
|
||||
* With full duplex - playback is preferred with abstract layer.
|
||||
*
|
||||
* Note: Some chip revisions have hardware bug. Changing capture
|
||||
* channel from full-duplex 8bit DMA to 16bit DMA will block
|
||||
* 16bit DMA transfers from DSP chip (capture) until 8bit transfer
|
||||
* to DSP chip (playback) starts. This bug can be avoided with
|
||||
* "16bit DMA Allocation" setting set to Playback or Capture.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/dma.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
#include <sound/sb16_csp.h>
|
||||
#include <sound/mpu401.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/info.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#ifdef CONFIG_SND_SB16_CSP
|
||||
static void snd_sb16_csp_playback_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
if (chip->hardware == SB_HW_16CSP) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
|
||||
/* manually loaded codec */
|
||||
if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) &&
|
||||
((1U << runtime->format) == csp->acc_format)) {
|
||||
/* Supported runtime PCM format for playback */
|
||||
if (csp->ops.csp_use(csp) == 0) {
|
||||
/* If CSP was successfully acquired */
|
||||
goto __start_CSP;
|
||||
}
|
||||
} else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) {
|
||||
/* QSound decoder is loaded and enabled */
|
||||
if ((1 << runtime->format) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
|
||||
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) {
|
||||
/* Only for simple PCM formats */
|
||||
if (csp->ops.csp_use(csp) == 0) {
|
||||
/* If CSP was successfully acquired */
|
||||
goto __start_CSP;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (csp->ops.csp_use(csp) == 0) {
|
||||
/* Acquire CSP and try to autoload hardware codec */
|
||||
if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) {
|
||||
/* Unsupported format, release CSP */
|
||||
csp->ops.csp_unuse(csp);
|
||||
} else {
|
||||
__start_CSP:
|
||||
/* Try to start CSP */
|
||||
if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ?
|
||||
SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
|
||||
(runtime->channels > 1) ?
|
||||
SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
|
||||
/* Failed, release CSP */
|
||||
csp->ops.csp_unuse(csp);
|
||||
} else {
|
||||
/* Success, CSP acquired and running */
|
||||
chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb16_csp_capture_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
if (chip->hardware == SB_HW_16CSP) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
|
||||
/* manually loaded codec */
|
||||
if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) &&
|
||||
((1U << runtime->format) == csp->acc_format)) {
|
||||
/* Supported runtime PCM format for capture */
|
||||
if (csp->ops.csp_use(csp) == 0) {
|
||||
/* If CSP was successfully acquired */
|
||||
goto __start_CSP;
|
||||
}
|
||||
}
|
||||
} else if (csp->ops.csp_use(csp) == 0) {
|
||||
/* Acquire CSP and try to autoload hardware codec */
|
||||
if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) {
|
||||
/* Unsupported format, release CSP */
|
||||
csp->ops.csp_unuse(csp);
|
||||
} else {
|
||||
__start_CSP:
|
||||
/* Try to start CSP */
|
||||
if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ?
|
||||
SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
|
||||
(runtime->channels > 1) ?
|
||||
SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
|
||||
/* Failed, release CSP */
|
||||
csp->ops.csp_unuse(csp);
|
||||
} else {
|
||||
/* Success, CSP acquired and running */
|
||||
chip->open = SNDRV_SB_CSP_MODE_DSP_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb16_csp_update(struct snd_sb *chip)
|
||||
{
|
||||
if (chip->hardware == SB_HW_16CSP) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->qpos_changed) {
|
||||
spin_lock(&chip->reg_lock);
|
||||
csp->ops.csp_qsound_transfer (csp);
|
||||
spin_unlock(&chip->reg_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb16_csp_playback_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
/* CSP decoders (QSound excluded) support only 16bit transfers */
|
||||
if (chip->hardware == SB_HW_16CSP) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
|
||||
/* manually loaded codec */
|
||||
if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) {
|
||||
runtime->hw.formats |= csp->acc_format;
|
||||
}
|
||||
} else {
|
||||
/* autoloaded codecs */
|
||||
runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
|
||||
SNDRV_PCM_FMTBIT_IMA_ADPCM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb16_csp_playback_close(struct snd_sb *chip)
|
||||
{
|
||||
if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->ops.csp_stop(csp) == 0) {
|
||||
csp->ops.csp_unuse(csp);
|
||||
chip->open = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb16_csp_capture_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
/* CSP coders support only 16bit transfers */
|
||||
if (chip->hardware == SB_HW_16CSP) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
|
||||
/* manually loaded codec */
|
||||
if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) {
|
||||
runtime->hw.formats |= csp->acc_format;
|
||||
}
|
||||
} else {
|
||||
/* autoloaded codecs */
|
||||
runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
|
||||
SNDRV_PCM_FMTBIT_IMA_ADPCM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb16_csp_capture_close(struct snd_sb *chip)
|
||||
{
|
||||
if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) {
|
||||
struct snd_sb_csp *csp = chip->csp;
|
||||
|
||||
if (csp->ops.csp_stop(csp) == 0) {
|
||||
csp->ops.csp_unuse(csp);
|
||||
chip->open = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define snd_sb16_csp_playback_prepare(chip, runtime) /*nop*/
|
||||
#define snd_sb16_csp_capture_prepare(chip, runtime) /*nop*/
|
||||
#define snd_sb16_csp_update(chip) /*nop*/
|
||||
#define snd_sb16_csp_playback_open(chip, runtime) /*nop*/
|
||||
#define snd_sb16_csp_playback_close(chip) /*nop*/
|
||||
#define snd_sb16_csp_capture_open(chip, runtime) /*nop*/
|
||||
#define snd_sb16_csp_capture_close(chip) /*nop*/
|
||||
#endif
|
||||
|
||||
|
||||
static void snd_sb16_setup_rate(struct snd_sb *chip,
|
||||
unsigned short rate,
|
||||
int channel)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16))
|
||||
snd_sb_ack_16bit(chip);
|
||||
else
|
||||
snd_sb_ack_8bit(chip);
|
||||
if (!(chip->mode & SB_RATE_LOCK)) {
|
||||
chip->locked_rate = rate;
|
||||
snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN);
|
||||
snd_sbdsp_command(chip, rate >> 8);
|
||||
snd_sbdsp_command(chip, rate & 0xff);
|
||||
snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);
|
||||
snd_sbdsp_command(chip, rate >> 8);
|
||||
snd_sbdsp_command(chip, rate & 0xff);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
}
|
||||
|
||||
static int snd_sb16_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
||||
static int snd_sb16_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned char format;
|
||||
unsigned int size, count, dma;
|
||||
|
||||
snd_sb16_csp_playback_prepare(chip, runtime);
|
||||
if (snd_pcm_format_unsigned(runtime->format) > 0) {
|
||||
format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
|
||||
} else {
|
||||
format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
|
||||
}
|
||||
|
||||
snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK);
|
||||
size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
|
||||
dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
|
||||
snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
|
||||
|
||||
count = snd_pcm_lib_period_bytes(substream);
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
if (chip->mode & SB_MODE_PLAYBACK_16) {
|
||||
count >>= 1;
|
||||
count--;
|
||||
snd_sbdsp_command(chip, SB_DSP4_OUT16_AI);
|
||||
snd_sbdsp_command(chip, format);
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
|
||||
} else {
|
||||
count--;
|
||||
snd_sbdsp_command(chip, SB_DSP4_OUT8_AI);
|
||||
snd_sbdsp_command(chip, format);
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_playback_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
int result = 0;
|
||||
|
||||
spin_lock(&chip->reg_lock);
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
chip->mode |= SB_RATE_LOCK_PLAYBACK;
|
||||
snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
|
||||
/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
|
||||
if (chip->mode & SB_RATE_LOCK_CAPTURE)
|
||||
snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
|
||||
chip->mode &= ~SB_RATE_LOCK_PLAYBACK;
|
||||
break;
|
||||
default:
|
||||
result = -EINVAL;
|
||||
}
|
||||
spin_unlock(&chip->reg_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int snd_sb16_capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned char format;
|
||||
unsigned int size, count, dma;
|
||||
|
||||
snd_sb16_csp_capture_prepare(chip, runtime);
|
||||
if (snd_pcm_format_unsigned(runtime->format) > 0) {
|
||||
format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
|
||||
} else {
|
||||
format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
|
||||
}
|
||||
snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE);
|
||||
size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
|
||||
dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
|
||||
snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
|
||||
|
||||
count = snd_pcm_lib_period_bytes(substream);
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
if (chip->mode & SB_MODE_CAPTURE_16) {
|
||||
count >>= 1;
|
||||
count--;
|
||||
snd_sbdsp_command(chip, SB_DSP4_IN16_AI);
|
||||
snd_sbdsp_command(chip, format);
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
|
||||
} else {
|
||||
count--;
|
||||
snd_sbdsp_command(chip, SB_DSP4_IN8_AI);
|
||||
snd_sbdsp_command(chip, format);
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_capture_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
int result = 0;
|
||||
|
||||
spin_lock(&chip->reg_lock);
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
chip->mode |= SB_RATE_LOCK_CAPTURE;
|
||||
snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
|
||||
/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
|
||||
if (chip->mode & SB_RATE_LOCK_PLAYBACK)
|
||||
snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
|
||||
chip->mode &= ~SB_RATE_LOCK_CAPTURE;
|
||||
break;
|
||||
default:
|
||||
result = -EINVAL;
|
||||
}
|
||||
spin_unlock(&chip->reg_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct snd_sb *chip = dev_id;
|
||||
unsigned char status;
|
||||
int ok;
|
||||
|
||||
spin_lock(&chip->mixer_lock);
|
||||
status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
|
||||
spin_unlock(&chip->mixer_lock);
|
||||
if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback)
|
||||
chip->rmidi_callback(irq, chip->rmidi->private_data);
|
||||
if (status & SB_IRQTYPE_8BIT) {
|
||||
ok = 0;
|
||||
if (chip->mode & SB_MODE_PLAYBACK_8) {
|
||||
snd_pcm_period_elapsed(chip->playback_substream);
|
||||
snd_sb16_csp_update(chip);
|
||||
ok++;
|
||||
}
|
||||
if (chip->mode & SB_MODE_CAPTURE_8) {
|
||||
snd_pcm_period_elapsed(chip->capture_substream);
|
||||
ok++;
|
||||
}
|
||||
spin_lock(&chip->reg_lock);
|
||||
if (!ok)
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
|
||||
snd_sb_ack_8bit(chip);
|
||||
spin_unlock(&chip->reg_lock);
|
||||
}
|
||||
if (status & SB_IRQTYPE_16BIT) {
|
||||
ok = 0;
|
||||
if (chip->mode & SB_MODE_PLAYBACK_16) {
|
||||
snd_pcm_period_elapsed(chip->playback_substream);
|
||||
snd_sb16_csp_update(chip);
|
||||
ok++;
|
||||
}
|
||||
if (chip->mode & SB_MODE_CAPTURE_16) {
|
||||
snd_pcm_period_elapsed(chip->capture_substream);
|
||||
ok++;
|
||||
}
|
||||
spin_lock(&chip->reg_lock);
|
||||
if (!ok)
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
|
||||
snd_sb_ack_16bit(chip);
|
||||
spin_unlock(&chip->reg_lock);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
static snd_pcm_uframes_t snd_sb16_playback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
unsigned int dma;
|
||||
size_t ptr;
|
||||
|
||||
dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
|
||||
ptr = snd_dma_pointer(dma, chip->p_dma_size);
|
||||
return bytes_to_frames(substream->runtime, ptr);
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t snd_sb16_capture_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
unsigned int dma;
|
||||
size_t ptr;
|
||||
|
||||
dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
|
||||
ptr = snd_dma_pointer(dma, chip->c_dma_size);
|
||||
return bytes_to_frames(substream->runtime, ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
static struct snd_pcm_hardware snd_sb16_playback =
|
||||
{
|
||||
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP_VALID),
|
||||
.formats = 0,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
|
||||
.rate_min = 4000,
|
||||
.rate_max = 44100,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = (128*1024),
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = (128*1024),
|
||||
.periods_min = 1,
|
||||
.periods_max = 1024,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hardware snd_sb16_capture =
|
||||
{
|
||||
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP_VALID),
|
||||
.formats = 0,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
|
||||
.rate_min = 4000,
|
||||
.rate_max = 44100,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = (128*1024),
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = (128*1024),
|
||||
.periods_min = 1,
|
||||
.periods_max = 1024,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* open/close
|
||||
*/
|
||||
|
||||
static int snd_sb16_playback_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (chip->mode & SB_MODE_PLAYBACK) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
runtime->hw = snd_sb16_playback;
|
||||
|
||||
/* skip if 16 bit DMA was reserved for capture */
|
||||
if (chip->force_mode16 & SB_MODE_CAPTURE_16)
|
||||
goto __skip_16bit;
|
||||
|
||||
if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) {
|
||||
chip->mode |= SB_MODE_PLAYBACK_16;
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
|
||||
/* Vibra16X hack */
|
||||
if (chip->dma16 <= 3) {
|
||||
runtime->hw.buffer_bytes_max =
|
||||
runtime->hw.period_bytes_max = 64 * 1024;
|
||||
} else {
|
||||
snd_sb16_csp_playback_open(chip, runtime);
|
||||
}
|
||||
goto __open_ok;
|
||||
}
|
||||
|
||||
__skip_16bit:
|
||||
if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) {
|
||||
chip->mode |= SB_MODE_PLAYBACK_8;
|
||||
/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
|
||||
if (chip->dma16 < 0) {
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
|
||||
chip->mode |= SB_MODE_PLAYBACK_16;
|
||||
} else {
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
|
||||
}
|
||||
runtime->hw.buffer_bytes_max =
|
||||
runtime->hw.period_bytes_max = 64 * 1024;
|
||||
goto __open_ok;
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
|
||||
__open_ok:
|
||||
if (chip->hardware == SB_HW_ALS100)
|
||||
runtime->hw.rate_max = 48000;
|
||||
if (chip->hardware == SB_HW_CS5530) {
|
||||
runtime->hw.buffer_bytes_max = 32 * 1024;
|
||||
runtime->hw.periods_min = 2;
|
||||
runtime->hw.rate_min = 44100;
|
||||
}
|
||||
if (chip->mode & SB_RATE_LOCK)
|
||||
runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
|
||||
chip->playback_substream = substream;
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_playback_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
|
||||
snd_sb16_csp_playback_close(chip);
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
chip->playback_substream = NULL;
|
||||
chip->mode &= ~SB_MODE_PLAYBACK;
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_capture_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (chip->mode & SB_MODE_CAPTURE) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
runtime->hw = snd_sb16_capture;
|
||||
|
||||
/* skip if 16 bit DMA was reserved for playback */
|
||||
if (chip->force_mode16 & SB_MODE_PLAYBACK_16)
|
||||
goto __skip_16bit;
|
||||
|
||||
if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) {
|
||||
chip->mode |= SB_MODE_CAPTURE_16;
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
|
||||
/* Vibra16X hack */
|
||||
if (chip->dma16 <= 3) {
|
||||
runtime->hw.buffer_bytes_max =
|
||||
runtime->hw.period_bytes_max = 64 * 1024;
|
||||
} else {
|
||||
snd_sb16_csp_capture_open(chip, runtime);
|
||||
}
|
||||
goto __open_ok;
|
||||
}
|
||||
|
||||
__skip_16bit:
|
||||
if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) {
|
||||
chip->mode |= SB_MODE_CAPTURE_8;
|
||||
/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
|
||||
if (chip->dma16 < 0) {
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
|
||||
chip->mode |= SB_MODE_CAPTURE_16;
|
||||
} else {
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
|
||||
}
|
||||
runtime->hw.buffer_bytes_max =
|
||||
runtime->hw.period_bytes_max = 64 * 1024;
|
||||
goto __open_ok;
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
|
||||
__open_ok:
|
||||
if (chip->hardware == SB_HW_ALS100)
|
||||
runtime->hw.rate_max = 48000;
|
||||
if (chip->hardware == SB_HW_CS5530) {
|
||||
runtime->hw.buffer_bytes_max = 32 * 1024;
|
||||
runtime->hw.periods_min = 2;
|
||||
runtime->hw.rate_min = 44100;
|
||||
}
|
||||
if (chip->mode & SB_RATE_LOCK)
|
||||
runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
|
||||
chip->capture_substream = substream;
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_capture_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
|
||||
snd_sb16_csp_capture_close(chip);
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
chip->capture_substream = NULL;
|
||||
chip->mode &= ~SB_MODE_CAPTURE;
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* DMA control interface
|
||||
*/
|
||||
|
||||
static int snd_sb16_set_dma_mode(struct snd_sb *chip, int what)
|
||||
{
|
||||
if (chip->dma8 < 0 || chip->dma16 < 0) {
|
||||
if (snd_BUG_ON(what))
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
if (what == 0) {
|
||||
chip->force_mode16 = 0;
|
||||
} else if (what == 1) {
|
||||
chip->force_mode16 = SB_MODE_PLAYBACK_16;
|
||||
} else if (what == 2) {
|
||||
chip->force_mode16 = SB_MODE_CAPTURE_16;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_get_dma_mode(struct snd_sb *chip)
|
||||
{
|
||||
if (chip->dma8 < 0 || chip->dma16 < 0)
|
||||
return 0;
|
||||
switch (chip->force_mode16) {
|
||||
case SB_MODE_PLAYBACK_16:
|
||||
return 1;
|
||||
case SB_MODE_CAPTURE_16:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int snd_sb16_dma_control_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts[3] = {
|
||||
"Auto", "Playback", "Capture"
|
||||
};
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item > 2)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_dma_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *chip = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip);
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16_dma_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *chip = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
unsigned char nval, oval;
|
||||
int change;
|
||||
|
||||
if ((nval = ucontrol->value.enumerated.item[0]) > 2)
|
||||
return -EINVAL;
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
oval = snd_sb16_get_dma_mode(chip);
|
||||
change = nval != oval;
|
||||
snd_sb16_set_dma_mode(chip, nval);
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_sb16_dma_control = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
||||
.name = "16-bit DMA Allocation",
|
||||
.info = snd_sb16_dma_control_info,
|
||||
.get = snd_sb16_dma_control_get,
|
||||
.put = snd_sb16_dma_control_put
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialization part
|
||||
*/
|
||||
|
||||
int snd_sb16dsp_configure(struct snd_sb * chip)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned char irqreg = 0, dmareg = 0, mpureg;
|
||||
unsigned char realirq, realdma, realmpureg;
|
||||
/* note: mpu register should be present only on SB16 Vibra soundcards */
|
||||
|
||||
// printk(KERN_DEBUG "codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16);
|
||||
spin_lock_irqsave(&chip->mixer_lock, flags);
|
||||
mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06;
|
||||
spin_unlock_irqrestore(&chip->mixer_lock, flags);
|
||||
switch (chip->irq) {
|
||||
case 2:
|
||||
case 9:
|
||||
irqreg |= SB_IRQSETUP_IRQ9;
|
||||
break;
|
||||
case 5:
|
||||
irqreg |= SB_IRQSETUP_IRQ5;
|
||||
break;
|
||||
case 7:
|
||||
irqreg |= SB_IRQSETUP_IRQ7;
|
||||
break;
|
||||
case 10:
|
||||
irqreg |= SB_IRQSETUP_IRQ10;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
if (chip->dma8 >= 0) {
|
||||
switch (chip->dma8) {
|
||||
case 0:
|
||||
dmareg |= SB_DMASETUP_DMA0;
|
||||
break;
|
||||
case 1:
|
||||
dmareg |= SB_DMASETUP_DMA1;
|
||||
break;
|
||||
case 3:
|
||||
dmareg |= SB_DMASETUP_DMA3;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
|
||||
switch (chip->dma16) {
|
||||
case 5:
|
||||
dmareg |= SB_DMASETUP_DMA5;
|
||||
break;
|
||||
case 6:
|
||||
dmareg |= SB_DMASETUP_DMA6;
|
||||
break;
|
||||
case 7:
|
||||
dmareg |= SB_DMASETUP_DMA7;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
switch (chip->mpu_port) {
|
||||
case 0x300:
|
||||
mpureg |= 0x04;
|
||||
break;
|
||||
case 0x330:
|
||||
mpureg |= 0x00;
|
||||
break;
|
||||
default:
|
||||
mpureg |= 0x02; /* disable MPU */
|
||||
}
|
||||
spin_lock_irqsave(&chip->mixer_lock, flags);
|
||||
|
||||
snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg);
|
||||
realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP);
|
||||
|
||||
snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg);
|
||||
realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP);
|
||||
|
||||
snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg);
|
||||
realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP);
|
||||
|
||||
spin_unlock_irqrestore(&chip->mixer_lock, flags);
|
||||
if ((~realirq) & irqreg || (~realdma) & dmareg) {
|
||||
snd_printk(KERN_ERR "SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port);
|
||||
snd_printk(KERN_ERR "SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg);
|
||||
snd_printk(KERN_ERR "SB16 [0x%lx]: got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg);
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops snd_sb16_playback_ops = {
|
||||
.open = snd_sb16_playback_open,
|
||||
.close = snd_sb16_playback_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_sb16_hw_params,
|
||||
.hw_free = snd_sb16_hw_free,
|
||||
.prepare = snd_sb16_playback_prepare,
|
||||
.trigger = snd_sb16_playback_trigger,
|
||||
.pointer = snd_sb16_playback_pointer,
|
||||
};
|
||||
|
||||
static struct snd_pcm_ops snd_sb16_capture_ops = {
|
||||
.open = snd_sb16_capture_open,
|
||||
.close = snd_sb16_capture_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_sb16_hw_params,
|
||||
.hw_free = snd_sb16_hw_free,
|
||||
.prepare = snd_sb16_capture_prepare,
|
||||
.trigger = snd_sb16_capture_trigger,
|
||||
.pointer = snd_sb16_capture_pointer,
|
||||
};
|
||||
|
||||
int snd_sb16dsp_pcm(struct snd_sb * chip, int device, struct snd_pcm ** rpcm)
|
||||
{
|
||||
struct snd_card *card = chip->card;
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
||||
if (rpcm)
|
||||
*rpcm = NULL;
|
||||
if ((err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm)) < 0)
|
||||
return err;
|
||||
sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
|
||||
pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
|
||||
pcm->private_data = chip;
|
||||
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops);
|
||||
|
||||
if (chip->dma16 >= 0 && chip->dma8 != chip->dma16)
|
||||
snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip));
|
||||
else
|
||||
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_isa_data(),
|
||||
64*1024, 128*1024);
|
||||
|
||||
if (rpcm)
|
||||
*rpcm = pcm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct snd_pcm_ops *snd_sb16dsp_get_pcm_ops(int direction)
|
||||
{
|
||||
return direction == SNDRV_PCM_STREAM_PLAYBACK ?
|
||||
&snd_sb16_playback_ops : &snd_sb16_capture_ops;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_sb16dsp_pcm);
|
||||
EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops);
|
||||
EXPORT_SYMBOL(snd_sb16dsp_configure);
|
||||
EXPORT_SYMBOL(snd_sb16dsp_interrupt);
|
||||
|
||||
/*
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_sb16_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_sb16_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_sb16_init)
|
||||
module_exit(alsa_sb16_exit)
|
265
sound/isa/sb/sb8.c
Normal file
265
sound/isa/sb/sb8.c
Normal file
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/isa.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
#include <sound/opl3.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 1.0/SB 2.0/SB Pro}}");
|
||||
|
||||
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
|
||||
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
|
||||
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
|
||||
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */
|
||||
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */
|
||||
static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3 */
|
||||
|
||||
module_param_array(index, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard.");
|
||||
module_param_array(id, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard.");
|
||||
module_param_array(enable, bool, NULL, 0444);
|
||||
MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard.");
|
||||
module_param_array(port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(port, "Port # for SB8 driver.");
|
||||
module_param_array(irq, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(irq, "IRQ # for SB8 driver.");
|
||||
module_param_array(dma8, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver.");
|
||||
|
||||
struct snd_sb8 {
|
||||
struct resource *fm_res; /* used to block FM i/o region for legacy cards */
|
||||
struct snd_sb *chip;
|
||||
};
|
||||
|
||||
static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct snd_sb *chip = dev_id;
|
||||
|
||||
if (chip->open & SB_OPEN_PCM) {
|
||||
return snd_sb8dsp_interrupt(chip);
|
||||
} else {
|
||||
return snd_sb8dsp_midi_interrupt(chip);
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb8_free(struct snd_card *card)
|
||||
{
|
||||
struct snd_sb8 *acard = card->private_data;
|
||||
|
||||
if (acard == NULL)
|
||||
return;
|
||||
release_and_free_resource(acard->fm_res);
|
||||
}
|
||||
|
||||
static int snd_sb8_match(struct device *pdev, unsigned int dev)
|
||||
{
|
||||
if (!enable[dev])
|
||||
return 0;
|
||||
if (irq[dev] == SNDRV_AUTO_IRQ) {
|
||||
dev_err(pdev, "please specify irq\n");
|
||||
return 0;
|
||||
}
|
||||
if (dma8[dev] == SNDRV_AUTO_DMA) {
|
||||
dev_err(pdev, "please specify dma8\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int snd_sb8_probe(struct device *pdev, unsigned int dev)
|
||||
{
|
||||
struct snd_sb *chip;
|
||||
struct snd_card *card;
|
||||
struct snd_sb8 *acard;
|
||||
struct snd_opl3 *opl3;
|
||||
int err;
|
||||
|
||||
err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
|
||||
sizeof(struct snd_sb8), &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
acard = card->private_data;
|
||||
card->private_free = snd_sb8_free;
|
||||
|
||||
/* block the 0x388 port to avoid PnP conflicts */
|
||||
acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
|
||||
|
||||
if (port[dev] != SNDRV_AUTO_PORT) {
|
||||
if ((err = snd_sbdsp_create(card, port[dev], irq[dev],
|
||||
snd_sb8_interrupt,
|
||||
dma8[dev],
|
||||
-1,
|
||||
SB_HW_AUTO,
|
||||
&chip)) < 0)
|
||||
goto _err;
|
||||
} else {
|
||||
/* auto-probe legacy ports */
|
||||
static unsigned long possible_ports[] = {
|
||||
0x220, 0x240, 0x260,
|
||||
};
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
|
||||
err = snd_sbdsp_create(card, possible_ports[i],
|
||||
irq[dev],
|
||||
snd_sb8_interrupt,
|
||||
dma8[dev],
|
||||
-1,
|
||||
SB_HW_AUTO,
|
||||
&chip);
|
||||
if (err >= 0) {
|
||||
port[dev] = possible_ports[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= ARRAY_SIZE(possible_ports)) {
|
||||
err = -EINVAL;
|
||||
goto _err;
|
||||
}
|
||||
}
|
||||
acard->chip = chip;
|
||||
|
||||
if (chip->hardware >= SB_HW_16) {
|
||||
if (chip->hardware == SB_HW_ALS100)
|
||||
snd_printk(KERN_WARNING "ALS100 chip detected at 0x%lx, try snd-als100 module\n",
|
||||
port[dev]);
|
||||
else
|
||||
snd_printk(KERN_WARNING "SB 16 chip detected at 0x%lx, try snd-sb16 module\n",
|
||||
port[dev]);
|
||||
err = -ENODEV;
|
||||
goto _err;
|
||||
}
|
||||
|
||||
if ((err = snd_sb8dsp_pcm(chip, 0, NULL)) < 0)
|
||||
goto _err;
|
||||
|
||||
if ((err = snd_sbmixer_new(chip)) < 0)
|
||||
goto _err;
|
||||
|
||||
if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) {
|
||||
if ((err = snd_opl3_create(card, chip->port + 8, 0,
|
||||
OPL3_HW_AUTO, 1,
|
||||
&opl3)) < 0) {
|
||||
snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx\n", chip->port + 8);
|
||||
}
|
||||
} else {
|
||||
if ((err = snd_opl3_create(card, chip->port, chip->port + 2,
|
||||
OPL3_HW_AUTO, 1,
|
||||
&opl3)) < 0) {
|
||||
snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx-0x%lx\n",
|
||||
chip->port, chip->port + 2);
|
||||
}
|
||||
}
|
||||
if (err >= 0) {
|
||||
if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0)
|
||||
goto _err;
|
||||
}
|
||||
|
||||
if ((err = snd_sb8dsp_midi(chip, 0, NULL)) < 0)
|
||||
goto _err;
|
||||
|
||||
strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8");
|
||||
strcpy(card->shortname, chip->name);
|
||||
sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
|
||||
chip->name,
|
||||
chip->port,
|
||||
irq[dev], dma8[dev]);
|
||||
|
||||
if ((err = snd_card_register(card)) < 0)
|
||||
goto _err;
|
||||
|
||||
dev_set_drvdata(pdev, card);
|
||||
return 0;
|
||||
|
||||
_err:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_sb8_remove(struct device *pdev, unsigned int dev)
|
||||
{
|
||||
snd_card_free(dev_get_drvdata(pdev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int snd_sb8_suspend(struct device *dev, unsigned int n,
|
||||
pm_message_t state)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(dev);
|
||||
struct snd_sb8 *acard = card->private_data;
|
||||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
snd_sbmixer_suspend(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_resume(struct device *dev, unsigned int n)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(dev);
|
||||
struct snd_sb8 *acard = card->private_data;
|
||||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_sbdsp_reset(chip);
|
||||
snd_sbmixer_resume(chip);
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define DEV_NAME "sb8"
|
||||
|
||||
static struct isa_driver snd_sb8_driver = {
|
||||
.match = snd_sb8_match,
|
||||
.probe = snd_sb8_probe,
|
||||
.remove = snd_sb8_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = snd_sb8_suspend,
|
||||
.resume = snd_sb8_resume,
|
||||
#endif
|
||||
.driver = {
|
||||
.name = DEV_NAME
|
||||
},
|
||||
};
|
||||
|
||||
static int __init alsa_card_sb8_init(void)
|
||||
{
|
||||
return isa_register_driver(&snd_sb8_driver, SNDRV_CARDS);
|
||||
}
|
||||
|
||||
static void __exit alsa_card_sb8_exit(void)
|
||||
{
|
||||
isa_unregister_driver(&snd_sb8_driver);
|
||||
}
|
||||
|
||||
module_init(alsa_card_sb8_init)
|
||||
module_exit(alsa_card_sb8_exit)
|
646
sound/isa/sb/sb8_main.c
Normal file
646
sound/isa/sb/sb8_main.c
Normal file
|
@ -0,0 +1,646 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* Routines for control of 8-bit SoundBlaster cards and clones
|
||||
* Please note: I don't have access to old SB8 soundcards.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* --
|
||||
*
|
||||
* Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
|
||||
* DSP can't respond to commands whilst in "high speed" mode. Caused
|
||||
* glitching during playback. Fixed.
|
||||
*
|
||||
* Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si>
|
||||
* Cleaned up and rewrote lowlevel routines.
|
||||
*/
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/dma.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Uros Bizjak <uros@kss-loka.si>");
|
||||
MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define SB8_CLOCK 1000000
|
||||
#define SB8_DEN(v) ((SB8_CLOCK + (v) / 2) / (v))
|
||||
#define SB8_RATE(v) (SB8_CLOCK / SB8_DEN(v))
|
||||
|
||||
static struct snd_ratnum clock = {
|
||||
.num = SB8_CLOCK,
|
||||
.den_min = 1,
|
||||
.den_max = 256,
|
||||
.den_step = 1,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_ratnums hw_constraints_clock = {
|
||||
.nrats = 1,
|
||||
.rats = &clock,
|
||||
};
|
||||
|
||||
static struct snd_ratnum stereo_clocks[] = {
|
||||
{
|
||||
.num = SB8_CLOCK,
|
||||
.den_min = SB8_DEN(22050),
|
||||
.den_max = SB8_DEN(22050),
|
||||
.den_step = 1,
|
||||
},
|
||||
{
|
||||
.num = SB8_CLOCK,
|
||||
.den_min = SB8_DEN(11025),
|
||||
.den_max = SB8_DEN(11025),
|
||||
.den_step = 1,
|
||||
}
|
||||
};
|
||||
|
||||
static int snd_sb8_hw_constraint_rate_channels(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
if (c->min > 1) {
|
||||
unsigned int num = 0, den = 0;
|
||||
int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE),
|
||||
2, stereo_clocks, &num, &den);
|
||||
if (err >= 0 && den) {
|
||||
params->rate_num = num;
|
||||
params->rate_den = den;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_hw_constraint_channels_rate(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
|
||||
struct snd_interval t = { .min = 1, .max = 1 };
|
||||
return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned int mixreg, rate, size, count;
|
||||
unsigned char format;
|
||||
unsigned char stereo = runtime->channels > 1;
|
||||
int dma;
|
||||
|
||||
rate = runtime->rate;
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_JAZZ16:
|
||||
if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
|
||||
if (chip->mode & SB_MODE_CAPTURE_16)
|
||||
return -EBUSY;
|
||||
else
|
||||
chip->mode |= SB_MODE_PLAYBACK_16;
|
||||
}
|
||||
chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
|
||||
break;
|
||||
case SB_HW_PRO:
|
||||
if (runtime->channels > 1) {
|
||||
if (snd_BUG_ON(rate != SB8_RATE(11025) &&
|
||||
rate != SB8_RATE(22050)))
|
||||
return -EINVAL;
|
||||
chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
|
||||
break;
|
||||
}
|
||||
/* fallthru */
|
||||
case SB_HW_201:
|
||||
if (rate > 23000) {
|
||||
chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
|
||||
break;
|
||||
}
|
||||
/* fallthru */
|
||||
case SB_HW_20:
|
||||
chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
|
||||
break;
|
||||
case SB_HW_10:
|
||||
chip->playback_format = SB_DSP_OUTPUT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
if (chip->mode & SB_MODE_PLAYBACK_16) {
|
||||
format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
|
||||
dma = chip->dma16;
|
||||
} else {
|
||||
format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
|
||||
chip->mode |= SB_MODE_PLAYBACK_8;
|
||||
dma = chip->dma8;
|
||||
}
|
||||
size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
|
||||
count = chip->p_period_size = snd_pcm_lib_period_bytes(substream);
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON);
|
||||
if (chip->hardware == SB_HW_JAZZ16)
|
||||
snd_sbdsp_command(chip, format);
|
||||
else if (stereo) {
|
||||
/* set playback stereo mode */
|
||||
spin_lock(&chip->mixer_lock);
|
||||
mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW);
|
||||
snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02);
|
||||
spin_unlock(&chip->mixer_lock);
|
||||
|
||||
/* Soundblaster hardware programming reference guide, 3-23 */
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT);
|
||||
runtime->dma_area[0] = 0x80;
|
||||
snd_dma_program(dma, runtime->dma_addr, 1, DMA_MODE_WRITE);
|
||||
/* force interrupt */
|
||||
snd_sbdsp_command(chip, SB_DSP_OUTPUT);
|
||||
snd_sbdsp_command(chip, 0);
|
||||
snd_sbdsp_command(chip, 0);
|
||||
}
|
||||
snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
|
||||
if (stereo) {
|
||||
snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
|
||||
spin_lock(&chip->mixer_lock);
|
||||
/* save output filter status and turn it off */
|
||||
mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT);
|
||||
snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20);
|
||||
spin_unlock(&chip->mixer_lock);
|
||||
/* just use force_mode16 for temporary storate... */
|
||||
chip->force_mode16 = mixreg;
|
||||
} else {
|
||||
snd_sbdsp_command(chip, 256 - runtime->rate_den);
|
||||
}
|
||||
if (chip->playback_format != SB_DSP_OUTPUT) {
|
||||
if (chip->mode & SB_MODE_PLAYBACK_16)
|
||||
count /= 2;
|
||||
count--;
|
||||
snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
snd_dma_program(dma, runtime->dma_addr,
|
||||
size, DMA_MODE_WRITE | DMA_AUTOINIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_playback_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
unsigned int count;
|
||||
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
snd_sbdsp_command(chip, chip->playback_format);
|
||||
if (chip->playback_format == SB_DSP_OUTPUT) {
|
||||
count = chip->p_period_size - 1;
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) {
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_sbdsp_reset(chip);
|
||||
if (runtime->channels > 1) {
|
||||
spin_lock(&chip->mixer_lock);
|
||||
/* restore output filter and set hardware to mono mode */
|
||||
snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02);
|
||||
spin_unlock(&chip->mixer_lock);
|
||||
}
|
||||
} else {
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
|
||||
}
|
||||
snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
||||
static int snd_sb8_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned int mixreg, rate, size, count;
|
||||
unsigned char format;
|
||||
unsigned char stereo = runtime->channels > 1;
|
||||
int dma;
|
||||
|
||||
rate = runtime->rate;
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_JAZZ16:
|
||||
if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
|
||||
if (chip->mode & SB_MODE_PLAYBACK_16)
|
||||
return -EBUSY;
|
||||
else
|
||||
chip->mode |= SB_MODE_CAPTURE_16;
|
||||
}
|
||||
chip->capture_format = SB_DSP_LO_INPUT_AUTO;
|
||||
break;
|
||||
case SB_HW_PRO:
|
||||
if (runtime->channels > 1) {
|
||||
if (snd_BUG_ON(rate != SB8_RATE(11025) &&
|
||||
rate != SB8_RATE(22050)))
|
||||
return -EINVAL;
|
||||
chip->capture_format = SB_DSP_HI_INPUT_AUTO;
|
||||
break;
|
||||
}
|
||||
chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
|
||||
break;
|
||||
case SB_HW_201:
|
||||
if (rate > 13000) {
|
||||
chip->capture_format = SB_DSP_HI_INPUT_AUTO;
|
||||
break;
|
||||
}
|
||||
/* fallthru */
|
||||
case SB_HW_20:
|
||||
chip->capture_format = SB_DSP_LO_INPUT_AUTO;
|
||||
break;
|
||||
case SB_HW_10:
|
||||
chip->capture_format = SB_DSP_INPUT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
if (chip->mode & SB_MODE_CAPTURE_16) {
|
||||
format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
|
||||
dma = chip->dma16;
|
||||
} else {
|
||||
format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
|
||||
chip->mode |= SB_MODE_CAPTURE_8;
|
||||
dma = chip->dma8;
|
||||
}
|
||||
size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
|
||||
count = chip->c_period_size = snd_pcm_lib_period_bytes(substream);
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
|
||||
if (chip->hardware == SB_HW_JAZZ16)
|
||||
snd_sbdsp_command(chip, format);
|
||||
else if (stereo)
|
||||
snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
|
||||
snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
|
||||
if (stereo) {
|
||||
snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
|
||||
spin_lock(&chip->mixer_lock);
|
||||
/* save input filter status and turn it off */
|
||||
mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT);
|
||||
snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20);
|
||||
spin_unlock(&chip->mixer_lock);
|
||||
/* just use force_mode16 for temporary storate... */
|
||||
chip->force_mode16 = mixreg;
|
||||
} else {
|
||||
snd_sbdsp_command(chip, 256 - runtime->rate_den);
|
||||
}
|
||||
if (chip->capture_format != SB_DSP_INPUT) {
|
||||
if (chip->mode & SB_MODE_PLAYBACK_16)
|
||||
count /= 2;
|
||||
count--;
|
||||
snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
snd_dma_program(dma, runtime->dma_addr,
|
||||
size, DMA_MODE_READ | DMA_AUTOINIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_capture_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
unsigned int count;
|
||||
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
snd_sbdsp_command(chip, chip->capture_format);
|
||||
if (chip->capture_format == SB_DSP_INPUT) {
|
||||
count = chip->c_period_size - 1;
|
||||
snd_sbdsp_command(chip, count & 0xff);
|
||||
snd_sbdsp_command(chip, count >> 8);
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) {
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_sbdsp_reset(chip);
|
||||
if (runtime->channels > 1) {
|
||||
/* restore input filter status */
|
||||
spin_lock(&chip->mixer_lock);
|
||||
snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16);
|
||||
spin_unlock(&chip->mixer_lock);
|
||||
/* set hardware to mono mode */
|
||||
snd_sbdsp_command(chip, SB_DSP_MONO_8BIT);
|
||||
}
|
||||
} else {
|
||||
snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
|
||||
}
|
||||
snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
irqreturn_t snd_sb8dsp_interrupt(struct snd_sb *chip)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
|
||||
snd_sb_ack_8bit(chip);
|
||||
switch (chip->mode) {
|
||||
case SB_MODE_PLAYBACK_16: /* ok.. playback is active */
|
||||
if (chip->hardware != SB_HW_JAZZ16)
|
||||
break;
|
||||
/* fallthru */
|
||||
case SB_MODE_PLAYBACK_8:
|
||||
substream = chip->playback_substream;
|
||||
runtime = substream->runtime;
|
||||
if (chip->playback_format == SB_DSP_OUTPUT)
|
||||
snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START);
|
||||
snd_pcm_period_elapsed(substream);
|
||||
break;
|
||||
case SB_MODE_CAPTURE_16:
|
||||
if (chip->hardware != SB_HW_JAZZ16)
|
||||
break;
|
||||
/* fallthru */
|
||||
case SB_MODE_CAPTURE_8:
|
||||
substream = chip->capture_substream;
|
||||
runtime = substream->runtime;
|
||||
if (chip->capture_format == SB_DSP_INPUT)
|
||||
snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START);
|
||||
snd_pcm_period_elapsed(substream);
|
||||
break;
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t snd_sb8_playback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
size_t ptr;
|
||||
int dma;
|
||||
|
||||
if (chip->mode & SB_MODE_PLAYBACK_8)
|
||||
dma = chip->dma8;
|
||||
else if (chip->mode & SB_MODE_PLAYBACK_16)
|
||||
dma = chip->dma16;
|
||||
else
|
||||
return 0;
|
||||
ptr = snd_dma_pointer(dma, chip->p_dma_size);
|
||||
return bytes_to_frames(substream->runtime, ptr);
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t snd_sb8_capture_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
size_t ptr;
|
||||
int dma;
|
||||
|
||||
if (chip->mode & SB_MODE_CAPTURE_8)
|
||||
dma = chip->dma8;
|
||||
else if (chip->mode & SB_MODE_CAPTURE_16)
|
||||
dma = chip->dma16;
|
||||
else
|
||||
return 0;
|
||||
ptr = snd_dma_pointer(dma, chip->c_dma_size);
|
||||
return bytes_to_frames(substream->runtime, ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
static struct snd_pcm_hardware snd_sb8_playback =
|
||||
{
|
||||
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP_VALID),
|
||||
.formats = SNDRV_PCM_FMTBIT_U8,
|
||||
.rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
|
||||
SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050),
|
||||
.rate_min = 4000,
|
||||
.rate_max = 23000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.buffer_bytes_max = 65536,
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = 65536,
|
||||
.periods_min = 1,
|
||||
.periods_max = 1024,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hardware snd_sb8_capture =
|
||||
{
|
||||
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP_VALID),
|
||||
.formats = SNDRV_PCM_FMTBIT_U8,
|
||||
.rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
|
||||
SNDRV_PCM_RATE_11025),
|
||||
.rate_min = 4000,
|
||||
.rate_max = 13000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.buffer_bytes_max = 65536,
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = 65536,
|
||||
.periods_min = 1,
|
||||
.periods_max = 1024,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
static int snd_sb8_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (chip->open) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
chip->open |= SB_OPEN_PCM;
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
chip->playback_substream = substream;
|
||||
runtime->hw = snd_sb8_playback;
|
||||
} else {
|
||||
chip->capture_substream = substream;
|
||||
runtime->hw = snd_sb8_capture;
|
||||
}
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_JAZZ16:
|
||||
if (chip->dma16 == 5 || chip->dma16 == 7)
|
||||
runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE;
|
||||
runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000;
|
||||
runtime->hw.rate_min = 4000;
|
||||
runtime->hw.rate_max = 50000;
|
||||
runtime->hw.channels_max = 2;
|
||||
break;
|
||||
case SB_HW_PRO:
|
||||
runtime->hw.rate_max = 44100;
|
||||
runtime->hw.channels_max = 2;
|
||||
snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
snd_sb8_hw_constraint_rate_channels, NULL,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
snd_sb8_hw_constraint_channels_rate, NULL,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
break;
|
||||
case SB_HW_201:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
runtime->hw.rate_max = 44100;
|
||||
} else {
|
||||
runtime->hw.rate_max = 15000;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&hw_constraints_clock);
|
||||
if (chip->dma8 > 3 || chip->dma16 >= 0) {
|
||||
snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 2);
|
||||
snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 2);
|
||||
runtime->hw.buffer_bytes_max = 128 * 1024 * 1024;
|
||||
runtime->hw.period_bytes_max = 128 * 1024 * 1024;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip = snd_pcm_substream_chip(substream);
|
||||
|
||||
chip->playback_substream = NULL;
|
||||
chip->capture_substream = NULL;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
chip->open &= ~SB_OPEN_PCM;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
chip->mode &= ~SB_MODE_PLAYBACK;
|
||||
else
|
||||
chip->mode &= ~SB_MODE_CAPTURE;
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialization part
|
||||
*/
|
||||
|
||||
static struct snd_pcm_ops snd_sb8_playback_ops = {
|
||||
.open = snd_sb8_open,
|
||||
.close = snd_sb8_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_sb8_hw_params,
|
||||
.hw_free = snd_sb8_hw_free,
|
||||
.prepare = snd_sb8_playback_prepare,
|
||||
.trigger = snd_sb8_playback_trigger,
|
||||
.pointer = snd_sb8_playback_pointer,
|
||||
};
|
||||
|
||||
static struct snd_pcm_ops snd_sb8_capture_ops = {
|
||||
.open = snd_sb8_open,
|
||||
.close = snd_sb8_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_sb8_hw_params,
|
||||
.hw_free = snd_sb8_hw_free,
|
||||
.prepare = snd_sb8_capture_prepare,
|
||||
.trigger = snd_sb8_capture_trigger,
|
||||
.pointer = snd_sb8_capture_pointer,
|
||||
};
|
||||
|
||||
int snd_sb8dsp_pcm(struct snd_sb *chip, int device, struct snd_pcm ** rpcm)
|
||||
{
|
||||
struct snd_card *card = chip->card;
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
size_t max_prealloc = 64 * 1024;
|
||||
|
||||
if (rpcm)
|
||||
*rpcm = NULL;
|
||||
if ((err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm)) < 0)
|
||||
return err;
|
||||
sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
|
||||
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
|
||||
pcm->private_data = chip;
|
||||
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops);
|
||||
|
||||
if (chip->dma8 > 3 || chip->dma16 >= 0)
|
||||
max_prealloc = 128 * 1024;
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_isa_data(),
|
||||
64*1024, max_prealloc);
|
||||
|
||||
if (rpcm)
|
||||
*rpcm = pcm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_sb8dsp_pcm);
|
||||
EXPORT_SYMBOL(snd_sb8dsp_interrupt);
|
||||
/* sb8_midi.c */
|
||||
EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
|
||||
EXPORT_SYMBOL(snd_sb8dsp_midi);
|
||||
|
||||
/*
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_sb8_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_sb8_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_sb8_init)
|
||||
module_exit(alsa_sb8_exit)
|
286
sound/isa/sb/sb8_midi.c
Normal file
286
sound/isa/sb/sb8_midi.c
Normal file
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* Routines for control of SoundBlaster cards - MIDI interface
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* --
|
||||
*
|
||||
* Sun May 9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
|
||||
* Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from
|
||||
* working.
|
||||
*
|
||||
* Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
|
||||
* Added full duplex UART mode for DSP version 2.0 and later.
|
||||
*/
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
|
||||
|
||||
irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
|
||||
{
|
||||
struct snd_rawmidi *rmidi;
|
||||
int max = 64;
|
||||
char byte;
|
||||
|
||||
if (!chip)
|
||||
return IRQ_NONE;
|
||||
|
||||
rmidi = chip->rmidi;
|
||||
if (!rmidi) {
|
||||
inb(SBP(chip, DATA_AVAIL)); /* ack interrupt */
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
spin_lock(&chip->midi_input_lock);
|
||||
while (max-- > 0) {
|
||||
if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
|
||||
byte = inb(SBP(chip, READ));
|
||||
if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
|
||||
snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
spin_unlock(&chip->midi_input_lock);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
unsigned int valid_open_flags;
|
||||
|
||||
chip = substream->rmidi->private_data;
|
||||
valid_open_flags = chip->hardware >= SB_HW_20
|
||||
? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (chip->open & ~valid_open_flags) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
chip->open |= SB_OPEN_MIDI_INPUT;
|
||||
chip->midi_substream_input = substream;
|
||||
if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
snd_sbdsp_reset(chip); /* reset DSP */
|
||||
if (chip->hardware >= SB_HW_20)
|
||||
snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
|
||||
} else {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
unsigned int valid_open_flags;
|
||||
|
||||
chip = substream->rmidi->private_data;
|
||||
valid_open_flags = chip->hardware >= SB_HW_20
|
||||
? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (chip->open & ~valid_open_flags) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
chip->open |= SB_OPEN_MIDI_OUTPUT;
|
||||
chip->midi_substream_output = substream;
|
||||
if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
snd_sbdsp_reset(chip); /* reset DSP */
|
||||
if (chip->hardware >= SB_HW_20)
|
||||
snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
|
||||
} else {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
|
||||
chip = substream->rmidi->private_data;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
|
||||
chip->midi_substream_input = NULL;
|
||||
if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
snd_sbdsp_reset(chip); /* reset DSP */
|
||||
} else {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
|
||||
chip = substream->rmidi->private_data;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
|
||||
chip->midi_substream_output = NULL;
|
||||
if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
snd_sbdsp_reset(chip); /* reset DSP */
|
||||
} else {
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
|
||||
chip = substream->rmidi->private_data;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (up) {
|
||||
if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
|
||||
if (chip->hardware < SB_HW_20)
|
||||
snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
|
||||
chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
|
||||
}
|
||||
} else {
|
||||
if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
|
||||
if (chip->hardware < SB_HW_20)
|
||||
snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
|
||||
chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
}
|
||||
|
||||
static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
char byte;
|
||||
int max = 32;
|
||||
|
||||
/* how big is Tx FIFO? */
|
||||
chip = substream->rmidi->private_data;
|
||||
while (max-- > 0) {
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
|
||||
chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
|
||||
del_timer(&chip->midi_timer);
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
break;
|
||||
}
|
||||
if (chip->hardware >= SB_HW_20) {
|
||||
int timeout = 8;
|
||||
while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
|
||||
;
|
||||
if (timeout == 0) {
|
||||
/* Tx FIFO full - try again later */
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
break;
|
||||
}
|
||||
outb(byte, SBP(chip, WRITE));
|
||||
} else {
|
||||
snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
|
||||
snd_sbdsp_command(chip, byte);
|
||||
}
|
||||
snd_rawmidi_transmit_ack(substream, 1);
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_sb8dsp_midi_output_timer(unsigned long data)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream = (struct snd_rawmidi_substream *) data;
|
||||
struct snd_sb * chip = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
chip->midi_timer.expires = 1 + jiffies;
|
||||
add_timer(&chip->midi_timer);
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
snd_sb8dsp_midi_output_write(substream);
|
||||
}
|
||||
|
||||
static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_sb *chip;
|
||||
|
||||
chip = substream->rmidi->private_data;
|
||||
spin_lock_irqsave(&chip->open_lock, flags);
|
||||
if (up) {
|
||||
if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
|
||||
init_timer(&chip->midi_timer);
|
||||
chip->midi_timer.function = snd_sb8dsp_midi_output_timer;
|
||||
chip->midi_timer.data = (unsigned long) substream;
|
||||
chip->midi_timer.expires = 1 + jiffies;
|
||||
add_timer(&chip->midi_timer);
|
||||
chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
|
||||
}
|
||||
} else {
|
||||
if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
|
||||
chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->open_lock, flags);
|
||||
|
||||
if (up)
|
||||
snd_sb8dsp_midi_output_write(substream);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops snd_sb8dsp_midi_output =
|
||||
{
|
||||
.open = snd_sb8dsp_midi_output_open,
|
||||
.close = snd_sb8dsp_midi_output_close,
|
||||
.trigger = snd_sb8dsp_midi_output_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops snd_sb8dsp_midi_input =
|
||||
{
|
||||
.open = snd_sb8dsp_midi_input_open,
|
||||
.close = snd_sb8dsp_midi_input_close,
|
||||
.trigger = snd_sb8dsp_midi_input_trigger,
|
||||
};
|
||||
|
||||
int snd_sb8dsp_midi(struct snd_sb *chip, int device, struct snd_rawmidi ** rrawmidi)
|
||||
{
|
||||
struct snd_rawmidi *rmidi;
|
||||
int err;
|
||||
|
||||
if (rrawmidi)
|
||||
*rrawmidi = NULL;
|
||||
if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0)
|
||||
return err;
|
||||
strcpy(rmidi->name, "SB8 MIDI");
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
|
||||
if (chip->hardware >= SB_HW_20)
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
rmidi->private_data = chip;
|
||||
chip->rmidi = rmidi;
|
||||
if (rrawmidi)
|
||||
*rrawmidi = rmidi;
|
||||
return 0;
|
||||
}
|
324
sound/isa/sb/sb_common.c
Normal file
324
sound/isa/sb/sb_common.c
Normal file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* Lowlevel routines for control of Sound Blaster cards
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/dma.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define BUSY_LOOPS 100000
|
||||
|
||||
#undef IO_DEBUG
|
||||
|
||||
int snd_sbdsp_command(struct snd_sb *chip, unsigned char val)
|
||||
{
|
||||
int i;
|
||||
#ifdef IO_DEBUG
|
||||
snd_printk(KERN_DEBUG "command 0x%x\n", val);
|
||||
#endif
|
||||
for (i = BUSY_LOOPS; i; i--)
|
||||
if ((inb(SBP(chip, STATUS)) & 0x80) == 0) {
|
||||
outb(val, SBP(chip, COMMAND));
|
||||
return 1;
|
||||
}
|
||||
snd_printd("%s [0x%lx]: timeout (0x%x)\n", __func__, chip->port, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_sbdsp_get_byte(struct snd_sb *chip)
|
||||
{
|
||||
int val;
|
||||
int i;
|
||||
for (i = BUSY_LOOPS; i; i--) {
|
||||
if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
|
||||
val = inb(SBP(chip, READ));
|
||||
#ifdef IO_DEBUG
|
||||
snd_printk(KERN_DEBUG "get_byte 0x%x\n", val);
|
||||
#endif
|
||||
return val;
|
||||
}
|
||||
}
|
||||
snd_printd("%s [0x%lx]: timeout\n", __func__, chip->port);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int snd_sbdsp_reset(struct snd_sb *chip)
|
||||
{
|
||||
int i;
|
||||
|
||||
outb(1, SBP(chip, RESET));
|
||||
udelay(10);
|
||||
outb(0, SBP(chip, RESET));
|
||||
udelay(30);
|
||||
for (i = BUSY_LOOPS; i; i--)
|
||||
if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
|
||||
if (inb(SBP(chip, READ)) == 0xaa)
|
||||
return 0;
|
||||
else
|
||||
break;
|
||||
}
|
||||
snd_printdd("%s [0x%lx] failed...\n", __func__, chip->port);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int snd_sbdsp_version(struct snd_sb * chip)
|
||||
{
|
||||
unsigned int result = -ENODEV;
|
||||
|
||||
snd_sbdsp_command(chip, SB_DSP_GET_VERSION);
|
||||
result = (short) snd_sbdsp_get_byte(chip) << 8;
|
||||
result |= (short) snd_sbdsp_get_byte(chip);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int snd_sbdsp_probe(struct snd_sb * chip)
|
||||
{
|
||||
int version;
|
||||
int major, minor;
|
||||
char *str;
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* initialization sequence
|
||||
*/
|
||||
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
if (snd_sbdsp_reset(chip) < 0) {
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return -ENODEV;
|
||||
}
|
||||
version = snd_sbdsp_version(chip);
|
||||
if (version < 0) {
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
return -ENODEV;
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
major = version >> 8;
|
||||
minor = version & 0xff;
|
||||
snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n",
|
||||
chip->port, major, minor);
|
||||
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_AUTO:
|
||||
switch (major) {
|
||||
case 1:
|
||||
chip->hardware = SB_HW_10;
|
||||
str = "1.0";
|
||||
break;
|
||||
case 2:
|
||||
if (minor) {
|
||||
chip->hardware = SB_HW_201;
|
||||
str = "2.01+";
|
||||
} else {
|
||||
chip->hardware = SB_HW_20;
|
||||
str = "2.0";
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
chip->hardware = SB_HW_PRO;
|
||||
str = "Pro";
|
||||
break;
|
||||
case 4:
|
||||
chip->hardware = SB_HW_16;
|
||||
str = "16";
|
||||
break;
|
||||
default:
|
||||
snd_printk(KERN_INFO "SB [0x%lx]: unknown DSP chip version %i.%i\n",
|
||||
chip->port, major, minor);
|
||||
return -ENODEV;
|
||||
}
|
||||
break;
|
||||
case SB_HW_ALS100:
|
||||
str = "16 (ALS-100)";
|
||||
break;
|
||||
case SB_HW_ALS4000:
|
||||
str = "16 (ALS-4000)";
|
||||
break;
|
||||
case SB_HW_DT019X:
|
||||
str = "(DT019X/ALS007)";
|
||||
break;
|
||||
case SB_HW_CS5530:
|
||||
str = "16 (CS5530)";
|
||||
break;
|
||||
case SB_HW_JAZZ16:
|
||||
str = "Pro (Jazz16)";
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
sprintf(chip->name, "Sound Blaster %s", str);
|
||||
chip->version = (major << 8) | minor;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sbdsp_free(struct snd_sb *chip)
|
||||
{
|
||||
if (chip->res_port)
|
||||
release_and_free_resource(chip->res_port);
|
||||
if (chip->irq >= 0)
|
||||
free_irq(chip->irq, (void *) chip);
|
||||
#ifdef CONFIG_ISA
|
||||
if (chip->dma8 >= 0) {
|
||||
disable_dma(chip->dma8);
|
||||
free_dma(chip->dma8);
|
||||
}
|
||||
if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
|
||||
disable_dma(chip->dma16);
|
||||
free_dma(chip->dma16);
|
||||
}
|
||||
#endif
|
||||
kfree(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sbdsp_dev_free(struct snd_device *device)
|
||||
{
|
||||
struct snd_sb *chip = device->device_data;
|
||||
return snd_sbdsp_free(chip);
|
||||
}
|
||||
|
||||
int snd_sbdsp_create(struct snd_card *card,
|
||||
unsigned long port,
|
||||
int irq,
|
||||
irq_handler_t irq_handler,
|
||||
int dma8,
|
||||
int dma16,
|
||||
unsigned short hardware,
|
||||
struct snd_sb **r_chip)
|
||||
{
|
||||
struct snd_sb *chip;
|
||||
int err;
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_free = snd_sbdsp_dev_free,
|
||||
};
|
||||
|
||||
if (snd_BUG_ON(!r_chip))
|
||||
return -EINVAL;
|
||||
*r_chip = NULL;
|
||||
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
|
||||
if (chip == NULL)
|
||||
return -ENOMEM;
|
||||
spin_lock_init(&chip->reg_lock);
|
||||
spin_lock_init(&chip->open_lock);
|
||||
spin_lock_init(&chip->midi_input_lock);
|
||||
spin_lock_init(&chip->mixer_lock);
|
||||
chip->irq = -1;
|
||||
chip->dma8 = -1;
|
||||
chip->dma16 = -1;
|
||||
chip->port = port;
|
||||
|
||||
if (request_irq(irq, irq_handler,
|
||||
(hardware == SB_HW_ALS4000 ||
|
||||
hardware == SB_HW_CS5530) ?
|
||||
IRQF_SHARED : 0,
|
||||
"SoundBlaster", (void *) chip)) {
|
||||
snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq);
|
||||
snd_sbdsp_free(chip);
|
||||
return -EBUSY;
|
||||
}
|
||||
chip->irq = irq;
|
||||
|
||||
if (hardware == SB_HW_ALS4000)
|
||||
goto __skip_allocation;
|
||||
|
||||
if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) {
|
||||
snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port);
|
||||
snd_sbdsp_free(chip);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ISA
|
||||
if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) {
|
||||
snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8);
|
||||
snd_sbdsp_free(chip);
|
||||
return -EBUSY;
|
||||
}
|
||||
chip->dma8 = dma8;
|
||||
if (dma16 >= 0) {
|
||||
if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) {
|
||||
/* no duplex */
|
||||
dma16 = -1;
|
||||
} else if (request_dma(dma16, "SoundBlaster - 16bit")) {
|
||||
snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16);
|
||||
snd_sbdsp_free(chip);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
chip->dma16 = dma16;
|
||||
#endif
|
||||
|
||||
__skip_allocation:
|
||||
chip->card = card;
|
||||
chip->hardware = hardware;
|
||||
if ((err = snd_sbdsp_probe(chip)) < 0) {
|
||||
snd_sbdsp_free(chip);
|
||||
return err;
|
||||
}
|
||||
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
|
||||
snd_sbdsp_free(chip);
|
||||
return err;
|
||||
}
|
||||
*r_chip = chip;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_sbdsp_command);
|
||||
EXPORT_SYMBOL(snd_sbdsp_get_byte);
|
||||
EXPORT_SYMBOL(snd_sbdsp_reset);
|
||||
EXPORT_SYMBOL(snd_sbdsp_create);
|
||||
/* sb_mixer.c */
|
||||
EXPORT_SYMBOL(snd_sbmixer_write);
|
||||
EXPORT_SYMBOL(snd_sbmixer_read);
|
||||
EXPORT_SYMBOL(snd_sbmixer_new);
|
||||
EXPORT_SYMBOL(snd_sbmixer_add_ctl);
|
||||
#ifdef CONFIG_PM
|
||||
EXPORT_SYMBOL(snd_sbmixer_suspend);
|
||||
EXPORT_SYMBOL(snd_sbmixer_resume);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_sb_common_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_sb_common_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_sb_common_init)
|
||||
module_exit(alsa_sb_common_exit)
|
980
sound/isa/sb/sb_mixer.c
Normal file
980
sound/isa/sb/sb_mixer.c
Normal file
|
@ -0,0 +1,980 @@
|
|||
/*
|
||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||
* Routines for Sound Blaster mixer control
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/time.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/sb.h>
|
||||
#include <sound/control.h>
|
||||
|
||||
#undef IO_DEBUG
|
||||
|
||||
void snd_sbmixer_write(struct snd_sb *chip, unsigned char reg, unsigned char data)
|
||||
{
|
||||
outb(reg, SBP(chip, MIXER_ADDR));
|
||||
udelay(10);
|
||||
outb(data, SBP(chip, MIXER_DATA));
|
||||
udelay(10);
|
||||
#ifdef IO_DEBUG
|
||||
snd_printk(KERN_DEBUG "mixer_write 0x%x 0x%x\n", reg, data);
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned char snd_sbmixer_read(struct snd_sb *chip, unsigned char reg)
|
||||
{
|
||||
unsigned char result;
|
||||
|
||||
outb(reg, SBP(chip, MIXER_ADDR));
|
||||
udelay(10);
|
||||
result = inb(SBP(chip, MIXER_DATA));
|
||||
udelay(10);
|
||||
#ifdef IO_DEBUG
|
||||
snd_printk(KERN_DEBUG "mixer_read 0x%x 0x%x\n", reg, result);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Single channel mixer element
|
||||
*/
|
||||
|
||||
static int snd_sbmixer_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
int mask = (kcontrol->private_value >> 24) & 0xff;
|
||||
|
||||
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = mask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int reg = kcontrol->private_value & 0xff;
|
||||
int shift = (kcontrol->private_value >> 16) & 0xff;
|
||||
int mask = (kcontrol->private_value >> 24) & 0xff;
|
||||
unsigned char val;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
val = (snd_sbmixer_read(sb, reg) >> shift) & mask;
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
ucontrol->value.integer.value[0] = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sbmixer_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int reg = kcontrol->private_value & 0xff;
|
||||
int shift = (kcontrol->private_value >> 16) & 0x07;
|
||||
int mask = (kcontrol->private_value >> 24) & 0xff;
|
||||
int change;
|
||||
unsigned char val, oval;
|
||||
|
||||
val = (ucontrol->value.integer.value[0] & mask) << shift;
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, reg);
|
||||
val = (oval & ~(mask << shift)) | val;
|
||||
change = val != oval;
|
||||
if (change)
|
||||
snd_sbmixer_write(sb, reg, val);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
/*
|
||||
* Double channel mixer element
|
||||
*/
|
||||
|
||||
static int snd_sbmixer_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
int mask = (kcontrol->private_value >> 24) & 0xff;
|
||||
|
||||
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
uinfo->count = 2;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = mask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sbmixer_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int left_reg = kcontrol->private_value & 0xff;
|
||||
int right_reg = (kcontrol->private_value >> 8) & 0xff;
|
||||
int left_shift = (kcontrol->private_value >> 16) & 0x07;
|
||||
int right_shift = (kcontrol->private_value >> 19) & 0x07;
|
||||
int mask = (kcontrol->private_value >> 24) & 0xff;
|
||||
unsigned char left, right;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask;
|
||||
right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask;
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
ucontrol->value.integer.value[0] = left;
|
||||
ucontrol->value.integer.value[1] = right;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sbmixer_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int left_reg = kcontrol->private_value & 0xff;
|
||||
int right_reg = (kcontrol->private_value >> 8) & 0xff;
|
||||
int left_shift = (kcontrol->private_value >> 16) & 0x07;
|
||||
int right_shift = (kcontrol->private_value >> 19) & 0x07;
|
||||
int mask = (kcontrol->private_value >> 24) & 0xff;
|
||||
int change;
|
||||
unsigned char left, right, oleft, oright;
|
||||
|
||||
left = (ucontrol->value.integer.value[0] & mask) << left_shift;
|
||||
right = (ucontrol->value.integer.value[1] & mask) << right_shift;
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
if (left_reg == right_reg) {
|
||||
oleft = snd_sbmixer_read(sb, left_reg);
|
||||
left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right;
|
||||
change = left != oleft;
|
||||
if (change)
|
||||
snd_sbmixer_write(sb, left_reg, left);
|
||||
} else {
|
||||
oleft = snd_sbmixer_read(sb, left_reg);
|
||||
oright = snd_sbmixer_read(sb, right_reg);
|
||||
left = (oleft & ~(mask << left_shift)) | left;
|
||||
right = (oright & ~(mask << right_shift)) | right;
|
||||
change = left != oleft || right != oright;
|
||||
if (change) {
|
||||
snd_sbmixer_write(sb, left_reg, left);
|
||||
snd_sbmixer_write(sb, right_reg, right);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
/*
|
||||
* DT-019x / ALS-007 capture/input switch
|
||||
*/
|
||||
|
||||
static int snd_dt019x_input_sw_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static const char *texts[5] = {
|
||||
"CD", "Mic", "Line", "Synth", "Master"
|
||||
};
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 5;
|
||||
if (uinfo->value.enumerated.item > 4)
|
||||
uinfo->value.enumerated.item = 4;
|
||||
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_dt019x_input_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
unsigned char oval;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
switch (oval & 0x07) {
|
||||
case SB_DT019X_CAP_CD:
|
||||
ucontrol->value.enumerated.item[0] = 0;
|
||||
break;
|
||||
case SB_DT019X_CAP_MIC:
|
||||
ucontrol->value.enumerated.item[0] = 1;
|
||||
break;
|
||||
case SB_DT019X_CAP_LINE:
|
||||
ucontrol->value.enumerated.item[0] = 2;
|
||||
break;
|
||||
case SB_DT019X_CAP_MAIN:
|
||||
ucontrol->value.enumerated.item[0] = 4;
|
||||
break;
|
||||
/* To record the synth on these cards you must record the main. */
|
||||
/* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */
|
||||
/* duplicate case labels if left uncommented. */
|
||||
/* case SB_DT019X_CAP_SYNTH:
|
||||
* ucontrol->value.enumerated.item[0] = 3;
|
||||
* break;
|
||||
*/
|
||||
default:
|
||||
ucontrol->value.enumerated.item[0] = 4;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_dt019x_input_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int change;
|
||||
unsigned char nval, oval;
|
||||
|
||||
if (ucontrol->value.enumerated.item[0] > 4)
|
||||
return -EINVAL;
|
||||
switch (ucontrol->value.enumerated.item[0]) {
|
||||
case 0:
|
||||
nval = SB_DT019X_CAP_CD;
|
||||
break;
|
||||
case 1:
|
||||
nval = SB_DT019X_CAP_MIC;
|
||||
break;
|
||||
case 2:
|
||||
nval = SB_DT019X_CAP_LINE;
|
||||
break;
|
||||
case 3:
|
||||
nval = SB_DT019X_CAP_SYNTH;
|
||||
break;
|
||||
case 4:
|
||||
nval = SB_DT019X_CAP_MAIN;
|
||||
break;
|
||||
default:
|
||||
nval = SB_DT019X_CAP_MAIN;
|
||||
}
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
|
||||
change = nval != oval;
|
||||
if (change)
|
||||
snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALS4000 mono recording control switch
|
||||
*/
|
||||
|
||||
static int snd_als4k_mono_capture_route_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static const char *texts[3] = {
|
||||
"L chan only", "R chan only", "L ch/2 + R ch/2"
|
||||
};
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item > 2)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name,
|
||||
texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_als4k_mono_capture_route_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
unsigned char oval;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
oval >>= 6;
|
||||
if (oval > 2)
|
||||
oval = 2;
|
||||
|
||||
ucontrol->value.enumerated.item[0] = oval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_als4k_mono_capture_route_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int change;
|
||||
unsigned char nval, oval;
|
||||
|
||||
if (ucontrol->value.enumerated.item[0] > 2)
|
||||
return -EINVAL;
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
|
||||
|
||||
nval = (oval & ~(3 << 6))
|
||||
| (ucontrol->value.enumerated.item[0] << 6);
|
||||
change = nval != oval;
|
||||
if (change)
|
||||
snd_sbmixer_write(sb, SB_ALS4000_MONO_IO_CTRL, nval);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
/*
|
||||
* SBPRO input multiplexer
|
||||
*/
|
||||
|
||||
static int snd_sb8mixer_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static const char *texts[3] = {
|
||||
"Mic", "CD", "Line"
|
||||
};
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item > 2)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int snd_sb8mixer_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
unsigned char oval;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
switch ((oval >> 0x01) & 0x03) {
|
||||
case SB_DSP_MIXS_CD:
|
||||
ucontrol->value.enumerated.item[0] = 1;
|
||||
break;
|
||||
case SB_DSP_MIXS_LINE:
|
||||
ucontrol->value.enumerated.item[0] = 2;
|
||||
break;
|
||||
default:
|
||||
ucontrol->value.enumerated.item[0] = 0;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb8mixer_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int change;
|
||||
unsigned char nval, oval;
|
||||
|
||||
if (ucontrol->value.enumerated.item[0] > 2)
|
||||
return -EINVAL;
|
||||
switch (ucontrol->value.enumerated.item[0]) {
|
||||
case 1:
|
||||
nval = SB_DSP_MIXS_CD;
|
||||
break;
|
||||
case 2:
|
||||
nval = SB_DSP_MIXS_LINE;
|
||||
break;
|
||||
default:
|
||||
nval = SB_DSP_MIXS_MIC;
|
||||
}
|
||||
nval <<= 1;
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
|
||||
nval |= oval & ~0x06;
|
||||
change = nval != oval;
|
||||
if (change)
|
||||
snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
/*
|
||||
* SB16 input switch
|
||||
*/
|
||||
|
||||
static int snd_sb16mixer_info_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
||||
uinfo->count = 4;
|
||||
uinfo->value.integer.min = 0;
|
||||
uinfo->value.integer.max = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16mixer_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int reg1 = kcontrol->private_value & 0xff;
|
||||
int reg2 = (kcontrol->private_value >> 8) & 0xff;
|
||||
int left_shift = (kcontrol->private_value >> 16) & 0x0f;
|
||||
int right_shift = (kcontrol->private_value >> 24) & 0x0f;
|
||||
unsigned char val1, val2;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
val1 = snd_sbmixer_read(sb, reg1);
|
||||
val2 = snd_sbmixer_read(sb, reg2);
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01;
|
||||
ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01;
|
||||
ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01;
|
||||
ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_sb16mixer_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
|
||||
unsigned long flags;
|
||||
int reg1 = kcontrol->private_value & 0xff;
|
||||
int reg2 = (kcontrol->private_value >> 8) & 0xff;
|
||||
int left_shift = (kcontrol->private_value >> 16) & 0x0f;
|
||||
int right_shift = (kcontrol->private_value >> 24) & 0x0f;
|
||||
int change;
|
||||
unsigned char val1, val2, oval1, oval2;
|
||||
|
||||
spin_lock_irqsave(&sb->mixer_lock, flags);
|
||||
oval1 = snd_sbmixer_read(sb, reg1);
|
||||
oval2 = snd_sbmixer_read(sb, reg2);
|
||||
val1 = oval1 & ~((1 << left_shift) | (1 << right_shift));
|
||||
val2 = oval2 & ~((1 << left_shift) | (1 << right_shift));
|
||||
val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
|
||||
val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
|
||||
val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
|
||||
val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
|
||||
change = val1 != oval1 || val2 != oval2;
|
||||
if (change) {
|
||||
snd_sbmixer_write(sb, reg1, val1);
|
||||
snd_sbmixer_write(sb, reg2, val2);
|
||||
}
|
||||
spin_unlock_irqrestore(&sb->mixer_lock, flags);
|
||||
return change;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
/*
|
||||
*/
|
||||
int snd_sbmixer_add_ctl(struct snd_sb *chip, const char *name, int index, int type, unsigned long value)
|
||||
{
|
||||
static struct snd_kcontrol_new newctls[] = {
|
||||
[SB_MIX_SINGLE] = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_sbmixer_info_single,
|
||||
.get = snd_sbmixer_get_single,
|
||||
.put = snd_sbmixer_put_single,
|
||||
},
|
||||
[SB_MIX_DOUBLE] = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_sbmixer_info_double,
|
||||
.get = snd_sbmixer_get_double,
|
||||
.put = snd_sbmixer_put_double,
|
||||
},
|
||||
[SB_MIX_INPUT_SW] = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_sb16mixer_info_input_sw,
|
||||
.get = snd_sb16mixer_get_input_sw,
|
||||
.put = snd_sb16mixer_put_input_sw,
|
||||
},
|
||||
[SB_MIX_CAPTURE_PRO] = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_sb8mixer_info_mux,
|
||||
.get = snd_sb8mixer_get_mux,
|
||||
.put = snd_sb8mixer_put_mux,
|
||||
},
|
||||
[SB_MIX_CAPTURE_DT019X] = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_dt019x_input_sw_info,
|
||||
.get = snd_dt019x_input_sw_get,
|
||||
.put = snd_dt019x_input_sw_put,
|
||||
},
|
||||
[SB_MIX_MONO_CAPTURE_ALS4K] = {
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.info = snd_als4k_mono_capture_route_info,
|
||||
.get = snd_als4k_mono_capture_route_get,
|
||||
.put = snd_als4k_mono_capture_route_put,
|
||||
},
|
||||
};
|
||||
struct snd_kcontrol *ctl;
|
||||
int err;
|
||||
|
||||
ctl = snd_ctl_new1(&newctls[type], chip);
|
||||
if (! ctl)
|
||||
return -ENOMEM;
|
||||
strlcpy(ctl->id.name, name, sizeof(ctl->id.name));
|
||||
ctl->id.index = index;
|
||||
ctl->private_value = value;
|
||||
if ((err = snd_ctl_add(chip->card, ctl)) < 0)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SB 2.0 specific mixer elements
|
||||
*/
|
||||
|
||||
static struct sbmix_elem snd_sb20_controls[] = {
|
||||
SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7),
|
||||
SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3),
|
||||
SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7),
|
||||
SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7)
|
||||
};
|
||||
|
||||
static unsigned char snd_sb20_init_values[][2] = {
|
||||
{ SB_DSP20_MASTER_DEV, 0 },
|
||||
{ SB_DSP20_FM_DEV, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
* SB Pro specific mixer elements
|
||||
*/
|
||||
static struct sbmix_elem snd_sbpro_controls[] = {
|
||||
SB_DOUBLE("Master Playback Volume",
|
||||
SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7),
|
||||
SB_DOUBLE("PCM Playback Volume",
|
||||
SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7),
|
||||
SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1),
|
||||
SB_DOUBLE("Synth Playback Volume",
|
||||
SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7),
|
||||
SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7),
|
||||
SB_DOUBLE("Line Playback Volume",
|
||||
SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7),
|
||||
SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3),
|
||||
{
|
||||
.name = "Capture Source",
|
||||
.type = SB_MIX_CAPTURE_PRO
|
||||
},
|
||||
SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1),
|
||||
SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1)
|
||||
};
|
||||
|
||||
static unsigned char snd_sbpro_init_values[][2] = {
|
||||
{ SB_DSP_MASTER_DEV, 0 },
|
||||
{ SB_DSP_PCM_DEV, 0 },
|
||||
{ SB_DSP_FM_DEV, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
* SB16 specific mixer elements
|
||||
*/
|
||||
static struct sbmix_elem snd_sb16_controls[] = {
|
||||
SB_DOUBLE("Master Playback Volume",
|
||||
SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
|
||||
SB_DOUBLE("PCM Playback Volume",
|
||||
SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
|
||||
SB16_INPUT_SW("Synth Capture Route",
|
||||
SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5),
|
||||
SB_DOUBLE("Synth Playback Volume",
|
||||
SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
|
||||
SB16_INPUT_SW("CD Capture Route",
|
||||
SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1),
|
||||
SB_DOUBLE("CD Playback Switch",
|
||||
SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
|
||||
SB_DOUBLE("CD Playback Volume",
|
||||
SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
|
||||
SB16_INPUT_SW("Mic Capture Route",
|
||||
SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0),
|
||||
SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
|
||||
SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
|
||||
SB_SINGLE("Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
|
||||
SB_DOUBLE("Capture Volume",
|
||||
SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
|
||||
SB_DOUBLE("Playback Volume",
|
||||
SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
|
||||
SB16_INPUT_SW("Line Capture Route",
|
||||
SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3),
|
||||
SB_DOUBLE("Line Playback Switch",
|
||||
SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
|
||||
SB_DOUBLE("Line Playback Volume",
|
||||
SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
|
||||
SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
|
||||
SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1),
|
||||
SB_DOUBLE("Tone Control - Bass",
|
||||
SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15),
|
||||
SB_DOUBLE("Tone Control - Treble",
|
||||
SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15)
|
||||
};
|
||||
|
||||
static unsigned char snd_sb16_init_values[][2] = {
|
||||
{ SB_DSP4_MASTER_DEV + 0, 0 },
|
||||
{ SB_DSP4_MASTER_DEV + 1, 0 },
|
||||
{ SB_DSP4_PCM_DEV + 0, 0 },
|
||||
{ SB_DSP4_PCM_DEV + 1, 0 },
|
||||
{ SB_DSP4_SYNTH_DEV + 0, 0 },
|
||||
{ SB_DSP4_SYNTH_DEV + 1, 0 },
|
||||
{ SB_DSP4_INPUT_LEFT, 0 },
|
||||
{ SB_DSP4_INPUT_RIGHT, 0 },
|
||||
{ SB_DSP4_OUTPUT_SW, 0 },
|
||||
{ SB_DSP4_SPEAKER_DEV, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
* DT019x specific mixer elements
|
||||
*/
|
||||
static struct sbmix_elem snd_dt019x_controls[] = {
|
||||
/* ALS4000 below has some parts which we might be lacking,
|
||||
* e.g. snd_als4000_ctl_mono_playback_switch - check it! */
|
||||
SB_DOUBLE("Master Playback Volume",
|
||||
SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4, 0, 15),
|
||||
SB_DOUBLE("PCM Playback Switch",
|
||||
SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
|
||||
SB_DOUBLE("PCM Playback Volume",
|
||||
SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4, 0, 15),
|
||||
SB_DOUBLE("Synth Playback Switch",
|
||||
SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
|
||||
SB_DOUBLE("Synth Playback Volume",
|
||||
SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4, 0, 15),
|
||||
SB_DOUBLE("CD Playback Switch",
|
||||
SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
|
||||
SB_DOUBLE("CD Playback Volume",
|
||||
SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4, 0, 15),
|
||||
SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
|
||||
SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7),
|
||||
SB_SINGLE("Beep Volume", SB_DT019X_SPKR_DEV, 0, 7),
|
||||
SB_DOUBLE("Line Playback Switch",
|
||||
SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
|
||||
SB_DOUBLE("Line Playback Volume",
|
||||
SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4, 0, 15),
|
||||
{
|
||||
.name = "Capture Source",
|
||||
.type = SB_MIX_CAPTURE_DT019X
|
||||
}
|
||||
};
|
||||
|
||||
static unsigned char snd_dt019x_init_values[][2] = {
|
||||
{ SB_DT019X_MASTER_DEV, 0 },
|
||||
{ SB_DT019X_PCM_DEV, 0 },
|
||||
{ SB_DT019X_SYNTH_DEV, 0 },
|
||||
{ SB_DT019X_CD_DEV, 0 },
|
||||
{ SB_DT019X_MIC_DEV, 0 }, /* Includes PC-speaker in high nibble */
|
||||
{ SB_DT019X_LINE_DEV, 0 },
|
||||
{ SB_DSP4_OUTPUT_SW, 0 },
|
||||
{ SB_DT019X_OUTPUT_SW2, 0 },
|
||||
{ SB_DT019X_CAPTURE_SW, 0x06 },
|
||||
};
|
||||
|
||||
/*
|
||||
* ALS4000 specific mixer elements
|
||||
*/
|
||||
static struct sbmix_elem snd_als4000_controls[] = {
|
||||
SB_DOUBLE("PCM Playback Switch",
|
||||
SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
|
||||
SB_DOUBLE("Synth Playback Switch",
|
||||
SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
|
||||
SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03),
|
||||
SB_SINGLE("Master Mono Playback Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1),
|
||||
{
|
||||
.name = "Master Mono Capture Route",
|
||||
.type = SB_MIX_MONO_CAPTURE_ALS4K
|
||||
},
|
||||
SB_SINGLE("Mono Playback Switch", SB_DT019X_OUTPUT_SW2, 0, 1),
|
||||
SB_SINGLE("Analog Loopback Switch", SB_ALS4000_MIC_IN_GAIN, 7, 0x01),
|
||||
SB_SINGLE("3D Control - Switch", SB_ALS4000_3D_SND_FX, 6, 0x01),
|
||||
SB_SINGLE("Digital Loopback Switch",
|
||||
SB_ALS4000_CR3_CONFIGURATION, 7, 0x01),
|
||||
/* FIXME: functionality of 3D controls might be swapped, I didn't find
|
||||
* a description of how to identify what is supposed to be what */
|
||||
SB_SINGLE("3D Control - Level", SB_ALS4000_3D_SND_FX, 0, 0x07),
|
||||
/* FIXME: maybe there's actually some standard 3D ctrl name for it?? */
|
||||
SB_SINGLE("3D Control - Freq", SB_ALS4000_3D_SND_FX, 4, 0x03),
|
||||
/* FIXME: ALS4000a.pdf mentions BBD (Bucket Brigade Device) time delay,
|
||||
* but what ALSA 3D attribute is that actually? "Center", "Depth",
|
||||
* "Wide" or "Space" or even "Level"? Assuming "Wide" for now... */
|
||||
SB_SINGLE("3D Control - Wide", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f),
|
||||
SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01),
|
||||
SB_SINGLE("Master Playback 8kHz / 20kHz LPF Switch",
|
||||
SB_ALS4000_FMDAC, 5, 0x01),
|
||||
#ifdef NOT_AVAILABLE
|
||||
SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01),
|
||||
SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f),
|
||||
#endif
|
||||
};
|
||||
|
||||
static unsigned char snd_als4000_init_values[][2] = {
|
||||
{ SB_DSP4_MASTER_DEV + 0, 0 },
|
||||
{ SB_DSP4_MASTER_DEV + 1, 0 },
|
||||
{ SB_DSP4_PCM_DEV + 0, 0 },
|
||||
{ SB_DSP4_PCM_DEV + 1, 0 },
|
||||
{ SB_DSP4_SYNTH_DEV + 0, 0 },
|
||||
{ SB_DSP4_SYNTH_DEV + 1, 0 },
|
||||
{ SB_DSP4_SPEAKER_DEV, 0 },
|
||||
{ SB_DSP4_OUTPUT_SW, 0 },
|
||||
{ SB_DSP4_INPUT_LEFT, 0 },
|
||||
{ SB_DSP4_INPUT_RIGHT, 0 },
|
||||
{ SB_DT019X_OUTPUT_SW2, 0 },
|
||||
{ SB_ALS4000_MIC_IN_GAIN, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
static int snd_sbmixer_init(struct snd_sb *chip,
|
||||
struct sbmix_elem *controls,
|
||||
int controls_count,
|
||||
unsigned char map[][2],
|
||||
int map_count,
|
||||
char *name)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_card *card = chip->card;
|
||||
int idx, err;
|
||||
|
||||
/* mixer reset */
|
||||
spin_lock_irqsave(&chip->mixer_lock, flags);
|
||||
snd_sbmixer_write(chip, 0x00, 0x00);
|
||||
spin_unlock_irqrestore(&chip->mixer_lock, flags);
|
||||
|
||||
/* mute and zero volume channels */
|
||||
for (idx = 0; idx < map_count; idx++) {
|
||||
spin_lock_irqsave(&chip->mixer_lock, flags);
|
||||
snd_sbmixer_write(chip, map[idx][0], map[idx][1]);
|
||||
spin_unlock_irqrestore(&chip->mixer_lock, flags);
|
||||
}
|
||||
|
||||
for (idx = 0; idx < controls_count; idx++) {
|
||||
err = snd_sbmixer_add_ctl_elem(chip, &controls[idx]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
snd_component_add(card, name);
|
||||
strcpy(card->mixername, name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_sbmixer_new(struct snd_sb *chip)
|
||||
{
|
||||
struct snd_card *card;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!chip || !chip->card))
|
||||
return -EINVAL;
|
||||
|
||||
card = chip->card;
|
||||
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_10:
|
||||
return 0; /* no mixer chip on SB1.x */
|
||||
case SB_HW_20:
|
||||
case SB_HW_201:
|
||||
if ((err = snd_sbmixer_init(chip,
|
||||
snd_sb20_controls,
|
||||
ARRAY_SIZE(snd_sb20_controls),
|
||||
snd_sb20_init_values,
|
||||
ARRAY_SIZE(snd_sb20_init_values),
|
||||
"CTL1335")) < 0)
|
||||
return err;
|
||||
break;
|
||||
case SB_HW_PRO:
|
||||
case SB_HW_JAZZ16:
|
||||
if ((err = snd_sbmixer_init(chip,
|
||||
snd_sbpro_controls,
|
||||
ARRAY_SIZE(snd_sbpro_controls),
|
||||
snd_sbpro_init_values,
|
||||
ARRAY_SIZE(snd_sbpro_init_values),
|
||||
"CTL1345")) < 0)
|
||||
return err;
|
||||
break;
|
||||
case SB_HW_16:
|
||||
case SB_HW_ALS100:
|
||||
case SB_HW_CS5530:
|
||||
if ((err = snd_sbmixer_init(chip,
|
||||
snd_sb16_controls,
|
||||
ARRAY_SIZE(snd_sb16_controls),
|
||||
snd_sb16_init_values,
|
||||
ARRAY_SIZE(snd_sb16_init_values),
|
||||
"CTL1745")) < 0)
|
||||
return err;
|
||||
break;
|
||||
case SB_HW_ALS4000:
|
||||
/* use only the first 16 controls from SB16 */
|
||||
err = snd_sbmixer_init(chip,
|
||||
snd_sb16_controls,
|
||||
16,
|
||||
snd_sb16_init_values,
|
||||
ARRAY_SIZE(snd_sb16_init_values),
|
||||
"ALS4000");
|
||||
if (err < 0)
|
||||
return err;
|
||||
if ((err = snd_sbmixer_init(chip,
|
||||
snd_als4000_controls,
|
||||
ARRAY_SIZE(snd_als4000_controls),
|
||||
snd_als4000_init_values,
|
||||
ARRAY_SIZE(snd_als4000_init_values),
|
||||
"ALS4000")) < 0)
|
||||
return err;
|
||||
break;
|
||||
case SB_HW_DT019X:
|
||||
err = snd_sbmixer_init(chip,
|
||||
snd_dt019x_controls,
|
||||
ARRAY_SIZE(snd_dt019x_controls),
|
||||
snd_dt019x_init_values,
|
||||
ARRAY_SIZE(snd_dt019x_init_values),
|
||||
"DT019X");
|
||||
if (err < 0)
|
||||
return err;
|
||||
break;
|
||||
default:
|
||||
strcpy(card->mixername, "???");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static unsigned char sb20_saved_regs[] = {
|
||||
SB_DSP20_MASTER_DEV,
|
||||
SB_DSP20_PCM_DEV,
|
||||
SB_DSP20_FM_DEV,
|
||||
SB_DSP20_CD_DEV,
|
||||
};
|
||||
|
||||
static unsigned char sbpro_saved_regs[] = {
|
||||
SB_DSP_MASTER_DEV,
|
||||
SB_DSP_PCM_DEV,
|
||||
SB_DSP_PLAYBACK_FILT,
|
||||
SB_DSP_FM_DEV,
|
||||
SB_DSP_CD_DEV,
|
||||
SB_DSP_LINE_DEV,
|
||||
SB_DSP_MIC_DEV,
|
||||
SB_DSP_CAPTURE_SOURCE,
|
||||
SB_DSP_CAPTURE_FILT,
|
||||
};
|
||||
|
||||
static unsigned char sb16_saved_regs[] = {
|
||||
SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
|
||||
SB_DSP4_3DSE,
|
||||
SB_DSP4_BASS_DEV, SB_DSP4_BASS_DEV + 1,
|
||||
SB_DSP4_TREBLE_DEV, SB_DSP4_TREBLE_DEV + 1,
|
||||
SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
|
||||
SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
|
||||
SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
|
||||
SB_DSP4_OUTPUT_SW,
|
||||
SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
|
||||
SB_DSP4_LINE_DEV, SB_DSP4_LINE_DEV + 1,
|
||||
SB_DSP4_MIC_DEV,
|
||||
SB_DSP4_SPEAKER_DEV,
|
||||
SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
|
||||
SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
|
||||
SB_DSP4_MIC_AGC
|
||||
};
|
||||
|
||||
static unsigned char dt019x_saved_regs[] = {
|
||||
SB_DT019X_MASTER_DEV,
|
||||
SB_DT019X_PCM_DEV,
|
||||
SB_DT019X_SYNTH_DEV,
|
||||
SB_DT019X_CD_DEV,
|
||||
SB_DT019X_MIC_DEV,
|
||||
SB_DT019X_SPKR_DEV,
|
||||
SB_DT019X_LINE_DEV,
|
||||
SB_DSP4_OUTPUT_SW,
|
||||
SB_DT019X_OUTPUT_SW2,
|
||||
SB_DT019X_CAPTURE_SW,
|
||||
};
|
||||
|
||||
static unsigned char als4000_saved_regs[] = {
|
||||
/* please verify in dsheet whether regs to be added
|
||||
are actually real H/W or just dummy */
|
||||
SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
|
||||
SB_DSP4_OUTPUT_SW,
|
||||
SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
|
||||
SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
|
||||
SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
|
||||
SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
|
||||
SB_DSP4_MIC_DEV,
|
||||
SB_DSP4_SPEAKER_DEV,
|
||||
SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
|
||||
SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
|
||||
SB_DT019X_OUTPUT_SW2,
|
||||
SB_ALS4000_MONO_IO_CTRL,
|
||||
SB_ALS4000_MIC_IN_GAIN,
|
||||
SB_ALS4000_FMDAC,
|
||||
SB_ALS4000_3D_SND_FX,
|
||||
SB_ALS4000_3D_TIME_DELAY,
|
||||
SB_ALS4000_CR3_CONFIGURATION,
|
||||
};
|
||||
|
||||
static void save_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs)
|
||||
{
|
||||
unsigned char *val = chip->saved_regs;
|
||||
if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
|
||||
return;
|
||||
for (; num_regs; num_regs--)
|
||||
*val++ = snd_sbmixer_read(chip, *regs++);
|
||||
}
|
||||
|
||||
static void restore_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs)
|
||||
{
|
||||
unsigned char *val = chip->saved_regs;
|
||||
if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
|
||||
return;
|
||||
for (; num_regs; num_regs--)
|
||||
snd_sbmixer_write(chip, *regs++, *val++);
|
||||
}
|
||||
|
||||
void snd_sbmixer_suspend(struct snd_sb *chip)
|
||||
{
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_20:
|
||||
case SB_HW_201:
|
||||
save_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
|
||||
break;
|
||||
case SB_HW_PRO:
|
||||
case SB_HW_JAZZ16:
|
||||
save_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
|
||||
break;
|
||||
case SB_HW_16:
|
||||
case SB_HW_ALS100:
|
||||
case SB_HW_CS5530:
|
||||
save_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
|
||||
break;
|
||||
case SB_HW_ALS4000:
|
||||
save_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
|
||||
break;
|
||||
case SB_HW_DT019X:
|
||||
save_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void snd_sbmixer_resume(struct snd_sb *chip)
|
||||
{
|
||||
switch (chip->hardware) {
|
||||
case SB_HW_20:
|
||||
case SB_HW_201:
|
||||
restore_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
|
||||
break;
|
||||
case SB_HW_PRO:
|
||||
case SB_HW_JAZZ16:
|
||||
restore_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
|
||||
break;
|
||||
case SB_HW_16:
|
||||
case SB_HW_ALS100:
|
||||
case SB_HW_CS5530:
|
||||
restore_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
|
||||
break;
|
||||
case SB_HW_ALS4000:
|
||||
restore_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
|
||||
break;
|
||||
case SB_HW_DT019X:
|
||||
restore_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
2
sound/isa/sb/sbawe.c
Normal file
2
sound/isa/sb/sbawe.c
Normal file
|
@ -0,0 +1,2 @@
|
|||
#define SNDRV_SBAWE
|
||||
#include "sb16.c"
|
Loading…
Add table
Add a link
Reference in a new issue