Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

28
sound/isa/sb/Makefile Normal file
View 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

File diff suppressed because it is too large Load diff

View 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;
}

View 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 */

View 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
View 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;
}

View 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
View 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
View 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

File diff suppressed because it is too large Load diff

925
sound/isa/sb/sb16_main.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
#define SNDRV_SBAWE
#include "sb16.c"