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

View file

@ -0,0 +1,15 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-emu10k1-objs := emu10k1.o emu10k1_main.o \
irq.o memory.o voice.o emumpu401.o emupcm.o io.o \
emuproc.o emumixer.o emufx.o timer.o p16v.o
snd-emu10k1-synth-objs := emu10k1_synth.o emu10k1_callback.o emu10k1_patch.o
snd-emu10k1x-objs := emu10k1x.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_EMU10K1) += snd-emu10k1.o
obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-emu10k1-synth.o
obj-$(CONFIG_SND_EMU10K1X) += snd-emu10k1x.o

289
sound/pci/emu10k1/emu10k1.c Normal file
View file

@ -0,0 +1,289 @@
/*
* The driver for the EMU10K1 (SB Live!) based soundcards
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
* Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
* Added support for Audigy 2 Value.
*
*
* 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/pci.h>
#include <linux/time.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
#include <sound/initval.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("EMU10K1");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB Live!/PCI512/E-mu APS},"
"{Creative Labs,SB Audigy}}");
#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
#define ENABLE_SYNTH
#include <sound/emu10k1_synth.h>
#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_PNP; /* Enable this card */
static int extin[SNDRV_CARDS];
static int extout[SNDRV_CARDS];
static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
static int max_synth_voices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 64};
static int max_buffer_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 128};
static bool enable_ir[SNDRV_CARDS];
static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */
static uint delay_pcm_irq[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the EMU10K1 soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the EMU10K1 soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable the EMU10K1 soundcard.");
module_param_array(extin, int, NULL, 0444);
MODULE_PARM_DESC(extin, "Available external inputs for FX8010. Zero=default.");
module_param_array(extout, int, NULL, 0444);
MODULE_PARM_DESC(extout, "Available external outputs for FX8010. Zero=default.");
module_param_array(seq_ports, int, NULL, 0444);
MODULE_PARM_DESC(seq_ports, "Allocated sequencer ports for internal synthesizer.");
module_param_array(max_synth_voices, int, NULL, 0444);
MODULE_PARM_DESC(max_synth_voices, "Maximum number of voices for WaveTable.");
module_param_array(max_buffer_size, int, NULL, 0444);
MODULE_PARM_DESC(max_buffer_size, "Maximum sample buffer size in MB.");
module_param_array(enable_ir, bool, NULL, 0444);
MODULE_PARM_DESC(enable_ir, "Enable IR.");
module_param_array(subsystem, uint, NULL, 0444);
MODULE_PARM_DESC(subsystem, "Force card subsystem model.");
module_param_array(delay_pcm_irq, uint, NULL, 0444);
MODULE_PARM_DESC(delay_pcm_irq, "Delay PCM interrupt by specified number of samples (default 0).");
/*
* Class 0401: 1102:0008 (rev 00) Subsystem: 1102:1001 -> Audigy2 Value Model:SB0400
*/
static const struct pci_device_id snd_emu10k1_ids[] = {
{ PCI_VDEVICE(CREATIVE, 0x0002), 0 }, /* EMU10K1 */
{ PCI_VDEVICE(CREATIVE, 0x0004), 1 }, /* Audigy */
{ PCI_VDEVICE(CREATIVE, 0x0008), 1 }, /* Audigy 2 Value SB0400 */
{ 0, }
};
/*
* Audigy 2 Value notes:
* A_IOCFG Input (GPIO)
* 0x400 = Front analog jack plugged in. (Green socket)
* 0x1000 = Read analog jack plugged in. (Black socket)
* 0x2000 = Center/LFE analog jack plugged in. (Orange socket)
* A_IOCFG Output (GPIO)
* 0x60 = Sound out of front Left.
* Win sets it to 0xXX61
*/
MODULE_DEVICE_TABLE(pci, snd_emu10k1_ids);
static int snd_card_emu10k1_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct snd_emu10k1 *emu;
#ifdef ENABLE_SYNTH
struct snd_seq_device *wave = NULL;
#endif
int err;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
if (max_buffer_size[dev] < 32)
max_buffer_size[dev] = 32;
else if (max_buffer_size[dev] > 1024)
max_buffer_size[dev] = 1024;
if ((err = snd_emu10k1_create(card, pci, extin[dev], extout[dev],
(long)max_buffer_size[dev] * 1024 * 1024,
enable_ir[dev], subsystem[dev],
&emu)) < 0)
goto error;
card->private_data = emu;
emu->delay_pcm_irq = delay_pcm_irq[dev] & 0x1f;
if ((err = snd_emu10k1_pcm(emu, 0, NULL)) < 0)
goto error;
if ((err = snd_emu10k1_pcm_mic(emu, 1, NULL)) < 0)
goto error;
if ((err = snd_emu10k1_pcm_efx(emu, 2, NULL)) < 0)
goto error;
/* This stores the periods table. */
if (emu->card_capabilities->ca0151_chip) { /* P16V */
if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
1024, &emu->p16v_buffer)) < 0)
goto error;
}
if ((err = snd_emu10k1_mixer(emu, 0, 3)) < 0)
goto error;
if ((err = snd_emu10k1_timer(emu, 0)) < 0)
goto error;
if ((err = snd_emu10k1_pcm_multi(emu, 3, NULL)) < 0)
goto error;
if (emu->card_capabilities->ca0151_chip) { /* P16V */
if ((err = snd_p16v_pcm(emu, 4, NULL)) < 0)
goto error;
}
if (emu->audigy) {
if ((err = snd_emu10k1_audigy_midi(emu)) < 0)
goto error;
} else {
if ((err = snd_emu10k1_midi(emu)) < 0)
goto error;
}
if ((err = snd_emu10k1_fx8010_new(emu, 0, NULL)) < 0)
goto error;
#ifdef ENABLE_SYNTH
if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH,
sizeof(struct snd_emu10k1_synth_arg), &wave) < 0 ||
wave == NULL) {
dev_warn(emu->card->dev,
"can't initialize Emu10k1 wavetable synth\n");
} else {
struct snd_emu10k1_synth_arg *arg;
arg = SNDRV_SEQ_DEVICE_ARGPTR(wave);
strcpy(wave->name, "Emu-10k1 Synth");
arg->hwptr = emu;
arg->index = 1;
arg->seq_ports = seq_ports[dev];
arg->max_voices = max_synth_voices[dev];
}
#endif
strlcpy(card->driver, emu->card_capabilities->driver,
sizeof(card->driver));
strlcpy(card->shortname, emu->card_capabilities->name,
sizeof(card->shortname));
snprintf(card->longname, sizeof(card->longname),
"%s (rev.%d, serial:0x%x) at 0x%lx, irq %i",
card->shortname, emu->revision, emu->serial, emu->port, emu->irq);
if ((err = snd_card_register(card)) < 0)
goto error;
pci_set_drvdata(pci, card);
dev++;
return 0;
error:
snd_card_free(card);
return err;
}
static void snd_card_emu10k1_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
#ifdef CONFIG_PM_SLEEP
static int snd_emu10k1_suspend(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct snd_card *card = dev_get_drvdata(dev);
struct snd_emu10k1 *emu = card->private_data;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
emu->suspend = 1;
snd_pcm_suspend_all(emu->pcm);
snd_pcm_suspend_all(emu->pcm_mic);
snd_pcm_suspend_all(emu->pcm_efx);
snd_pcm_suspend_all(emu->pcm_multi);
snd_pcm_suspend_all(emu->pcm_p16v);
snd_ac97_suspend(emu->ac97);
snd_emu10k1_efx_suspend(emu);
snd_emu10k1_suspend_regs(emu);
if (emu->card_capabilities->ca0151_chip)
snd_p16v_suspend(emu);
snd_emu10k1_done(emu);
pci_disable_device(pci);
pci_save_state(pci);
pci_set_power_state(pci, PCI_D3hot);
return 0;
}
static int snd_emu10k1_resume(struct device *dev)
{
struct pci_dev *pci = to_pci_dev(dev);
struct snd_card *card = dev_get_drvdata(dev);
struct snd_emu10k1 *emu = card->private_data;
pci_set_power_state(pci, PCI_D0);
pci_restore_state(pci);
if (pci_enable_device(pci) < 0) {
dev_err(dev, "pci_enable_device failed, disabling device\n");
snd_card_disconnect(card);
return -EIO;
}
pci_set_master(pci);
snd_emu10k1_resume_init(emu);
snd_emu10k1_efx_resume(emu);
snd_ac97_resume(emu->ac97);
snd_emu10k1_resume_regs(emu);
if (emu->card_capabilities->ca0151_chip)
snd_p16v_resume(emu);
emu->suspend = 0;
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
static SIMPLE_DEV_PM_OPS(snd_emu10k1_pm, snd_emu10k1_suspend, snd_emu10k1_resume);
#define SND_EMU10K1_PM_OPS &snd_emu10k1_pm
#else
#define SND_EMU10K1_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP */
static struct pci_driver emu10k1_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_emu10k1_ids,
.probe = snd_card_emu10k1_probe,
.remove = snd_card_emu10k1_remove,
.driver = {
.pm = SND_EMU10K1_PM_OPS,
},
};
module_pci_driver(emu10k1_driver);

View file

@ -0,0 +1,550 @@
/*
* synth callback routines for Emu10k1
*
* Copyright (C) 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/export.h>
#include "emu10k1_synth_local.h"
#include <sound/asoundef.h>
/* voice status */
enum {
V_FREE=0, V_OFF, V_RELEASED, V_PLAYING, V_END
};
/* Keeps track of what we are finding */
struct best_voice {
unsigned int time;
int voice;
};
/*
* prototypes
*/
static void lookup_voices(struct snd_emux *emux, struct snd_emu10k1 *hw,
struct best_voice *best, int active_only);
static struct snd_emux_voice *get_voice(struct snd_emux *emux,
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 terminate_voice(struct snd_emux_voice *vp);
static void free_voice(struct snd_emux_voice *vp);
static void set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
static void set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
static void set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
/*
* 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 emu10k1_ops = {
.owner = THIS_MODULE,
.get_voice = get_voice,
.prepare = start_voice,
.trigger = trigger_voice,
.release = release_voice,
.update = update_voice,
.terminate = terminate_voice,
.free_voice = free_voice,
.sample_new = snd_emu10k1_sample_new,
.sample_free = snd_emu10k1_sample_free,
};
void
snd_emu10k1_ops_setup(struct snd_emux *emux)
{
emux->ops = emu10k1_ops;
}
/*
* get more voice for pcm
*
* terminate most inactive voice and give it as a pcm voice.
*
* voice_lock is already held.
*/
int
snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw)
{
struct snd_emux *emu;
struct snd_emux_voice *vp;
struct best_voice best[V_END];
int i;
emu = hw->synth;
lookup_voices(emu, hw, best, 1); /* no OFF voices */
for (i = 0; i < V_END; i++) {
if (best[i].voice >= 0) {
int ch;
vp = &emu->voices[best[i].voice];
if ((ch = vp->ch) < 0) {
/*
dev_warn(emu->card->dev,
"synth_get_voice: ch < 0 (%d) ??", i);
*/
continue;
}
vp->emu->num_voices--;
vp->ch = -1;
vp->state = SNDRV_EMUX_ST_OFF;
return ch;
}
}
/* not found */
return -ENOMEM;
}
/*
* turn off the voice (not terminated)
*/
static void
release_voice(struct snd_emux_voice *vp)
{
int dcysusv;
struct snd_emu10k1 *hw;
hw = vp->hw;
dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv);
dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease | DCYSUSV_CHANNELENABLE_MASK;
snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv);
}
/*
* terminate the voice
*/
static void
terminate_voice(struct snd_emux_voice *vp)
{
struct snd_emu10k1 *hw;
if (snd_BUG_ON(!vp))
return;
hw = vp->hw;
snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
if (vp->block) {
struct snd_emu10k1_memblk *emem;
emem = (struct snd_emu10k1_memblk *)vp->block;
if (emem->map_locked > 0)
emem->map_locked--;
}
}
/*
* release the voice to system
*/
static void
free_voice(struct snd_emux_voice *vp)
{
struct snd_emu10k1 *hw;
hw = vp->hw;
/* FIXME: emu10k1_synth is broken. */
/* This can get called with hw == 0 */
/* Problem apparent on plug, unplug then plug */
/* on the Audigy 2 ZS Notebook. */
if (hw && (vp->ch >= 0)) {
snd_emu10k1_ptr_write(hw, IFATN, vp->ch, 0xff00);
snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
// snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0);
snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff);
snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff);
snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]);
vp->emu->num_voices--;
vp->ch = -1;
}
}
/*
* update registers
*/
static void
update_voice(struct snd_emux_voice *vp, int update)
{
struct snd_emu10k1 *hw;
hw = vp->hw;
if (update & SNDRV_EMUX_UPDATE_VOLUME)
snd_emu10k1_ptr_write(hw, IFATN_ATTENUATION, vp->ch, vp->avol);
if (update & SNDRV_EMUX_UPDATE_PITCH)
snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
if (update & SNDRV_EMUX_UPDATE_PAN) {
snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_A, vp->ch, vp->apan);
snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux);
}
if (update & SNDRV_EMUX_UPDATE_FMMOD)
set_fmmod(hw, vp);
if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
set_fm2frq2(hw, vp);
if (update & SNDRV_EMUX_UPDATE_Q)
set_filterQ(hw, vp);
}
/*
* look up voice table - get the best voice in order of preference
*/
/* spinlock held! */
static void
lookup_voices(struct snd_emux *emu, struct snd_emu10k1 *hw,
struct best_voice *best, int active_only)
{
struct snd_emux_voice *vp;
struct best_voice *bp;
int i;
for (i = 0; i < V_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.
* NOTE: could also look at volume and pick the quietest one.
*/
for (i = 0; i < emu->max_voices; i++) {
int state, val;
vp = &emu->voices[i];
state = vp->state;
if (state == SNDRV_EMUX_ST_OFF) {
if (vp->ch < 0) {
if (active_only)
continue;
bp = best + V_FREE;
} else
bp = best + V_OFF;
}
else if (state == SNDRV_EMUX_ST_RELEASED ||
state == SNDRV_EMUX_ST_PENDING) {
bp = best + V_RELEASED;
#if 1
val = snd_emu10k1_ptr_read(hw, CVCF_CURRENTVOL, vp->ch);
if (! val)
bp = best + V_OFF;
#endif
}
else if (state == SNDRV_EMUX_ST_STANDBY)
continue;
else if (state & SNDRV_EMUX_ST_ON)
bp = best + V_PLAYING;
else
continue;
/* check if sample is finished playing (non-looping only) */
if (bp != best + V_OFF && bp != best + V_FREE &&
(vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch);
if (val >= vp->reg.loopstart)
bp = best + V_OFF;
}
if (vp->time < bp->time) {
bp->time = vp->time;
bp->voice = i;
}
}
}
/*
* get an empty voice
*
* emu->voice_lock is already held.
*/
static struct snd_emux_voice *
get_voice(struct snd_emux *emu, struct snd_emux_port *port)
{
struct snd_emu10k1 *hw;
struct snd_emux_voice *vp;
struct best_voice best[V_END];
int i;
hw = emu->hw;
lookup_voices(emu, hw, best, 0);
for (i = 0; i < V_END; i++) {
if (best[i].voice >= 0) {
vp = &emu->voices[best[i].voice];
if (vp->ch < 0) {
/* allocate a voice */
struct snd_emu10k1_voice *hwvoice;
if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL)
continue;
vp->ch = hwvoice->number;
emu->num_voices++;
}
return vp;
}
}
/* not found */
return NULL;
}
/*
* prepare envelopes and LFOs
*/
static int
start_voice(struct snd_emux_voice *vp)
{
unsigned int temp;
int ch;
unsigned int addr, mapped_offset;
struct snd_midi_channel *chan;
struct snd_emu10k1 *hw;
struct snd_emu10k1_memblk *emem;
hw = vp->hw;
ch = vp->ch;
if (snd_BUG_ON(ch < 0))
return -EINVAL;
chan = vp->chan;
emem = (struct snd_emu10k1_memblk *)vp->block;
if (emem == NULL)
return -EINVAL;
emem->map_locked++;
if (snd_emu10k1_memblk_map(hw, emem) < 0) {
/* dev_err(hw->card->devK, "emu: cannot map!\n"); */
return -ENOMEM;
}
mapped_offset = snd_emu10k1_memblk_offset(emem) >> 1;
vp->reg.start += mapped_offset;
vp->reg.end += mapped_offset;
vp->reg.loopstart += mapped_offset;
vp->reg.loopend += mapped_offset;
/* set channel routing */
/* A = left(0), B = right(1), C = reverb(c), D = chorus(d) */
if (hw->audigy) {
temp = FXBUS_MIDI_LEFT | (FXBUS_MIDI_RIGHT << 8) |
(FXBUS_MIDI_REVERB << 16) | (FXBUS_MIDI_CHORUS << 24);
snd_emu10k1_ptr_write(hw, A_FXRT1, ch, temp);
} else {
temp = (FXBUS_MIDI_LEFT << 16) | (FXBUS_MIDI_RIGHT << 20) |
(FXBUS_MIDI_REVERB << 24) | (FXBUS_MIDI_CHORUS << 28);
snd_emu10k1_ptr_write(hw, FXRT, ch, temp);
}
/* channel to be silent and idle */
snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0x0000);
snd_emu10k1_ptr_write(hw, VTFT, ch, 0x0000FFFF);
snd_emu10k1_ptr_write(hw, CVCF, ch, 0x0000FFFF);
snd_emu10k1_ptr_write(hw, PTRX, ch, 0);
snd_emu10k1_ptr_write(hw, CPF, ch, 0);
/* set pitch offset */
snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
/* set envelope parameters */
snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay);
snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld);
snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus);
snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay);
snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld);
/* decay/sustain parameter for volume envelope is used
for triggerg the voice */
/* cutoff and volume */
temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol;
snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp);
/* modulation envelope heights */
snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe);
/* lfo1/2 delay */
snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay);
snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay);
/* lfo1 pitch & cutoff shift */
set_fmmod(hw, vp);
/* lfo1 volume & freq */
snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
/* lfo2 pitch & freq */
set_fm2frq2(hw, vp);
/* reverb and loop start (reverb 8bit, MSB) */
temp = vp->reg.parm.reverb;
temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
LIMITMAX(temp, 255);
addr = vp->reg.loopstart;
snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr);
/* chorus & loop end (chorus 8bit, MSB) */
addr = vp->reg.loopend;
temp = vp->reg.parm.chorus;
temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
LIMITMAX(temp, 255);
temp = (temp <<24) | addr;
snd_emu10k1_ptr_write(hw, DSL, ch, temp);
/* clear filter delay memory */
snd_emu10k1_ptr_write(hw, Z1, ch, 0);
snd_emu10k1_ptr_write(hw, Z2, ch, 0);
/* invalidate maps */
temp = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0);
snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
#if 0
/* cache */
{
unsigned int val, sample;
val = 32;
if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
sample = 0x80808080;
else {
sample = 0;
val *= 2;
}
/* cache */
snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16);
snd_emu10k1_ptr_write(hw, CDE, ch, sample);
snd_emu10k1_ptr_write(hw, CDF, ch, sample);
/* invalidate maps */
temp = ((unsigned int)hw->silent_page.addr << hw_address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0);
snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
/* fill cache */
val -= 4;
val <<= 25;
val |= 0x1c << 16;
snd_emu10k1_ptr_write(hw, CCR, ch, val);
}
#endif
/* Q & current address (Q 4bit value, MSB) */
addr = vp->reg.start;
temp = vp->reg.parm.filterQ;
temp = (temp<<28) | addr;
if (vp->apitch < 0xe400)
temp |= CCCA_INTERPROM_0;
else {
unsigned int shift = (vp->apitch - 0xe000) >> 10;
temp |= shift << 25;
}
if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
temp |= CCCA_8BITSELECT;
snd_emu10k1_ptr_write(hw, CCCA, ch, temp);
/* reset volume */
temp = (unsigned int)vp->vtarget << 16;
snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget);
snd_emu10k1_ptr_write(hw, CVCF, ch, temp | 0xff00);
return 0;
}
/*
* Start envelope
*/
static void
trigger_voice(struct snd_emux_voice *vp)
{
unsigned int temp, ptarget;
struct snd_emu10k1 *hw;
struct snd_emu10k1_memblk *emem;
hw = vp->hw;
emem = (struct snd_emu10k1_memblk *)vp->block;
if (! emem || emem->mapped_page < 0)
return; /* not mapped */
#if 0
ptarget = (unsigned int)vp->ptarget << 16;
#else
ptarget = IP_TO_CP(vp->apitch);
#endif
/* set pitch target and pan (volume) */
temp = ptarget | (vp->apan << 8) | vp->aaux;
snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp);
/* pitch target */
snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget);
/* trigger voice */
snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK);
}
#define MOD_SENSE 18
/* set lfo1 modulation height and cutoff */
static void
set_fmmod(struct snd_emu10k1 *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;
snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod);
}
/* set lfo2 pitch & frequency */
static void
set_fm2frq2(struct snd_emu10k1 *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;
snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2);
}
/* set filterQ */
static void
set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
{
unsigned int val;
val = snd_emu10k1_ptr_read(hw, CCCA, vp->ch) & ~CCCA_RESONANCE;
val |= (vp->reg.parm.filterQ << 28);
snd_emu10k1_ptr_write(hw, CCCA, vp->ch, val);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,231 @@
/*
* Patch transfer callback for Emu10k1
*
* Copyright (C) 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
*/
/*
* All the code for loading in a patch. There is very little that is
* chip specific here. Just the actual writing to the board.
*/
#include "emu10k1_synth_local.h"
/*
*/
#define BLANK_LOOP_START 4
#define BLANK_LOOP_END 8
#define BLANK_LOOP_SIZE 12
#define BLANK_HEAD_SIZE 32
/*
* allocate a sample block and copy data from userspace
*/
int
snd_emu10k1_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
struct snd_util_memhdr *hdr,
const void __user *data, long count)
{
int offset;
int truesize, size, loopsize, blocksize;
int loopend, sampleend;
unsigned int start_addr;
struct snd_emu10k1 *emu;
emu = rec->hw;
if (snd_BUG_ON(!sp || !hdr))
return -EINVAL;
if (sp->v.size == 0) {
dev_dbg(emu->card->dev,
"emu: rom font for sample %d\n", sp->v.sample);
return 0;
}
/* 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;
/* some samples have invalid data. the addresses are corrected in voice info */
sampleend = sp->v.end;
if (sampleend > sp->v.size)
sampleend = sp->v.size;
loopend = sp->v.loopend;
if (loopend > sampleend)
loopend = sampleend;
/* 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 + BLANK_HEAD_SIZE;
loopsize = 0;
#if 0 /* not supported */
if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
loopsize = sp->v.loopend - sp->v.loopstart;
truesize += loopsize;
#endif
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
truesize += BLANK_LOOP_SIZE;
/* try to allocate a memory block */
blocksize = truesize;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
blocksize *= 2;
sp->block = snd_emu10k1_synth_alloc(emu, blocksize);
if (sp->block == NULL) {
dev_dbg(emu->card->dev,
"synth malloc failed (size=%d)\n", blocksize);
/* not ENOMEM (for compatibility with OSS) */
return -ENOSPC;
}
/* set the total size */
sp->v.truesize = blocksize;
/* write blank samples at head */
offset = 0;
size = BLANK_HEAD_SIZE;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
size *= 2;
if (offset + size > blocksize)
return -EINVAL;
snd_emu10k1_synth_bzero(emu, sp->block, offset, size);
offset += size;
/* copy start->loopend */
size = loopend;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
size *= 2;
if (offset + size > blocksize)
return -EINVAL;
if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
snd_emu10k1_synth_free(emu, sp->block);
sp->block = NULL;
return -EFAULT;
}
offset += size;
data += size;
#if 0 /* not supported yet */
/* handle reverse (or bidirectional) loop */
if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) {
/* copy loop in reverse */
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
int woffset;
unsigned short *wblock = (unsigned short*)block;
woffset = offset / 2;
if (offset + loopsize * 2 > blocksize)
return -EINVAL;
for (i = 0; i < loopsize; i++)
wblock[woffset + i] = wblock[woffset - i -1];
offset += loopsize * 2;
} else {
if (offset + loopsize > blocksize)
return -EINVAL;
for (i = 0; i < loopsize; i++)
block[offset + i] = block[offset - i -1];
offset += loopsize;
}
/* modify loop pointers */
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
sp->v.loopend += loopsize;
} else {
sp->v.loopstart += loopsize;
sp->v.loopend += loopsize;
}
/* add sample pointer */
sp->v.end += loopsize;
}
#endif
/* loopend -> sample end */
size = sp->v.size - loopend;
if (size < 0)
return -EINVAL;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
size *= 2;
if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
snd_emu10k1_synth_free(emu, sp->block);
sp->block = NULL;
return -EFAULT;
}
offset += size;
/* clear rest of samples (if any) */
if (offset < blocksize)
snd_emu10k1_synth_bzero(emu, sp->block, offset, blocksize - offset);
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
/* if no blank loop is attached in the sample, add it */
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;
}
}
#if 0 /* not supported yet */
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) {
/* unsigned -> signed */
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
unsigned short *wblock = (unsigned short*)block;
for (i = 0; i < truesize; i++)
wblock[i] ^= 0x8000;
} else {
for (i = 0; i < truesize; i++)
block[i] ^= 0x80;
}
}
#endif
/* recalculate offset */
start_addr = BLANK_HEAD_SIZE * 2;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
start_addr >>= 1;
sp->v.start += start_addr;
sp->v.end += start_addr;
sp->v.loopstart += start_addr;
sp->v.loopend += start_addr;
return 0;
}
/*
* free a sample block
*/
int
snd_emu10k1_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
struct snd_util_memhdr *hdr)
{
struct snd_emu10k1 *emu;
emu = rec->hw;
if (snd_BUG_ON(!sp || !hdr))
return -EINVAL;
if (sp->block) {
snd_emu10k1_synth_free(emu, sp->block);
sp->block = NULL;
}
return 0;
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
*
* Routines for control of EMU10K1 WaveTable synth
*
* 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 "emu10k1_synth_local.h"
#include <linux/init.h>
#include <linux/module.h>
MODULE_AUTHOR("Takashi Iwai");
MODULE_DESCRIPTION("Routines for control of EMU10K1 WaveTable synth");
MODULE_LICENSE("GPL");
/*
* create a new hardware dependent device for Emu10k1
*/
static int snd_emu10k1_synth_new_device(struct snd_seq_device *dev)
{
struct snd_emux *emux;
struct snd_emu10k1 *hw;
struct snd_emu10k1_synth_arg *arg;
unsigned long flags;
arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
if (arg == NULL)
return -EINVAL;
if (arg->seq_ports <= 0)
return 0; /* nothing */
if (arg->max_voices < 1)
arg->max_voices = 1;
else if (arg->max_voices > 64)
arg->max_voices = 64;
if (snd_emux_new(&emux) < 0)
return -ENOMEM;
snd_emu10k1_ops_setup(emux);
hw = arg->hwptr;
emux->hw = hw;
emux->max_voices = arg->max_voices;
emux->num_ports = arg->seq_ports;
emux->pitch_shift = -501;
emux->memhdr = hw->memhdr;
/* maximum two ports */
emux->midi_ports = arg->seq_ports < 2 ? arg->seq_ports : 2;
/* audigy has two external midis */
emux->midi_devidx = hw->audigy ? 2 : 1;
emux->linear_panning = 0;
emux->hwdep_idx = 2; /* FIXED */
if (snd_emux_register(emux, dev->card, arg->index, "Emu10k1") < 0) {
snd_emux_free(emux);
return -ENOMEM;
}
spin_lock_irqsave(&hw->voice_lock, flags);
hw->synth = emux;
hw->get_synth_voice = snd_emu10k1_synth_get_voice;
spin_unlock_irqrestore(&hw->voice_lock, flags);
dev->driver_data = emux;
return 0;
}
static int snd_emu10k1_synth_delete_device(struct snd_seq_device *dev)
{
struct snd_emux *emux;
struct snd_emu10k1 *hw;
unsigned long flags;
if (dev->driver_data == NULL)
return 0; /* not registered actually */
emux = dev->driver_data;
hw = emux->hw;
spin_lock_irqsave(&hw->voice_lock, flags);
hw->synth = NULL;
hw->get_synth_voice = NULL;
spin_unlock_irqrestore(&hw->voice_lock, flags);
snd_emux_free(emux);
return 0;
}
/*
* INIT part
*/
static int __init alsa_emu10k1_synth_init(void)
{
static struct snd_seq_dev_ops ops = {
snd_emu10k1_synth_new_device,
snd_emu10k1_synth_delete_device,
};
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH, &ops,
sizeof(struct snd_emu10k1_synth_arg));
}
static void __exit alsa_emu10k1_synth_exit(void)
{
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH);
}
module_init(alsa_emu10k1_synth_init)
module_exit(alsa_emu10k1_synth_exit)

View file

@ -0,0 +1,42 @@
#ifndef __EMU10K1_SYNTH_LOCAL_H
#define __EMU10K1_SYNTH_LOCAL_H
/*
* Local defininitons for Emu10k1 wavetable
*
* Copyright (C) 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/time.h>
#include <sound/core.h>
#include <sound/emu10k1_synth.h>
/* emu10k1_patch.c */
int snd_emu10k1_sample_new(struct snd_emux *private_data,
struct snd_sf_sample *sp,
struct snd_util_memhdr *hdr,
const void __user *_data, long count);
int snd_emu10k1_sample_free(struct snd_emux *private_data,
struct snd_sf_sample *sp,
struct snd_util_memhdr *hdr);
int snd_emu10k1_memhdr_init(struct snd_emux *emu);
/* emu10k1_callback.c */
void snd_emu10k1_ops_setup(struct snd_emux *emu);
int snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw);
#endif /* __EMU10K1_SYNTH_LOCAL_H */

1651
sound/pci/emu10k1/emu10k1x.c Normal file

File diff suppressed because it is too large Load diff

2775
sound/pci/emu10k1/emufx.c Normal file

File diff suppressed because it is too large Load diff

2145
sound/pci/emu10k1/emumixer.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,399 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Routines for control of EMU10K1 MPU-401 in UART mode
*
*
* 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/time.h>
#include <linux/init.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
#define EMU10K1_MIDI_MODE_INPUT (1<<0)
#define EMU10K1_MIDI_MODE_OUTPUT (1<<1)
static inline unsigned char mpu401_read(struct snd_emu10k1 *emu,
struct snd_emu10k1_midi *mpu, int idx)
{
if (emu->audigy)
return (unsigned char)snd_emu10k1_ptr_read(emu, mpu->port + idx, 0);
else
return inb(emu->port + mpu->port + idx);
}
static inline void mpu401_write(struct snd_emu10k1 *emu,
struct snd_emu10k1_midi *mpu, int data, int idx)
{
if (emu->audigy)
snd_emu10k1_ptr_write(emu, mpu->port + idx, 0, data);
else
outb(data, emu->port + mpu->port + idx);
}
#define mpu401_write_data(emu, mpu, data) mpu401_write(emu, mpu, data, 0)
#define mpu401_write_cmd(emu, mpu, data) mpu401_write(emu, mpu, data, 1)
#define mpu401_read_data(emu, mpu) mpu401_read(emu, mpu, 0)
#define mpu401_read_stat(emu, mpu) mpu401_read(emu, mpu, 1)
#define mpu401_input_avail(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x80))
#define mpu401_output_ready(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x40))
#define MPU401_RESET 0xff
#define MPU401_ENTER_UART 0x3f
#define MPU401_ACK 0xfe
static void mpu401_clear_rx(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *mpu)
{
int timeout = 100000;
for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--)
mpu401_read_data(emu, mpu);
#ifdef CONFIG_SND_DEBUG
if (timeout <= 0)
dev_err(emu->card->dev,
"cmd: clear rx timeout (status = 0x%x)\n",
mpu401_read_stat(emu, mpu));
#endif
}
/*
*/
static void do_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, unsigned int status)
{
unsigned char byte;
if (midi->rmidi == NULL) {
snd_emu10k1_intr_disable(emu, midi->tx_enable | midi->rx_enable);
return;
}
spin_lock(&midi->input_lock);
if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) {
if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
mpu401_clear_rx(emu, midi);
} else {
byte = mpu401_read_data(emu, midi);
if (midi->substream_input)
snd_rawmidi_receive(midi->substream_input, &byte, 1);
}
}
spin_unlock(&midi->input_lock);
spin_lock(&midi->output_lock);
if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) {
if (midi->substream_output &&
snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
mpu401_write_data(emu, midi, byte);
} else {
snd_emu10k1_intr_disable(emu, midi->tx_enable);
}
}
spin_unlock(&midi->output_lock);
}
static void snd_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, unsigned int status)
{
do_emu10k1_midi_interrupt(emu, &emu->midi, status);
}
static void snd_emu10k1_midi_interrupt2(struct snd_emu10k1 *emu, unsigned int status)
{
do_emu10k1_midi_interrupt(emu, &emu->midi2, status);
}
static int snd_emu10k1_midi_cmd(struct snd_emu10k1 * emu, struct snd_emu10k1_midi *midi, unsigned char cmd, int ack)
{
unsigned long flags;
int timeout, ok;
spin_lock_irqsave(&midi->input_lock, flags);
mpu401_write_data(emu, midi, 0x00);
/* mpu401_clear_rx(emu, midi); */
mpu401_write_cmd(emu, midi, cmd);
if (ack) {
ok = 0;
timeout = 10000;
while (!ok && timeout-- > 0) {
if (mpu401_input_avail(emu, midi)) {
if (mpu401_read_data(emu, midi) == MPU401_ACK)
ok = 1;
}
}
if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK)
ok = 1;
} else {
ok = 1;
}
spin_unlock_irqrestore(&midi->input_lock, flags);
if (!ok) {
dev_err(emu->card->dev,
"midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n",
cmd, emu->port,
mpu401_read_stat(emu, midi),
mpu401_read_data(emu, midi));
return 1;
}
return 0;
}
static int snd_emu10k1_midi_input_open(struct snd_rawmidi_substream *substream)
{
struct snd_emu10k1 *emu;
struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
unsigned long flags;
emu = midi->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
midi->midi_mode |= EMU10K1_MIDI_MODE_INPUT;
midi->substream_input = substream;
if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1))
goto error_out;
if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
goto error_out;
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return 0;
error_out:
return -EIO;
}
static int snd_emu10k1_midi_output_open(struct snd_rawmidi_substream *substream)
{
struct snd_emu10k1 *emu;
struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
unsigned long flags;
emu = midi->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
midi->midi_mode |= EMU10K1_MIDI_MODE_OUTPUT;
midi->substream_output = substream;
if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1))
goto error_out;
if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
goto error_out;
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return 0;
error_out:
return -EIO;
}
static int snd_emu10k1_midi_input_close(struct snd_rawmidi_substream *substream)
{
struct snd_emu10k1 *emu;
struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
unsigned long flags;
int err = 0;
emu = midi->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
snd_emu10k1_intr_disable(emu, midi->rx_enable);
midi->midi_mode &= ~EMU10K1_MIDI_MODE_INPUT;
midi->substream_input = NULL;
if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
err = snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return err;
}
static int snd_emu10k1_midi_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_emu10k1 *emu;
struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
unsigned long flags;
int err = 0;
emu = midi->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
spin_lock_irqsave(&midi->open_lock, flags);
snd_emu10k1_intr_disable(emu, midi->tx_enable);
midi->midi_mode &= ~EMU10K1_MIDI_MODE_OUTPUT;
midi->substream_output = NULL;
if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
spin_unlock_irqrestore(&midi->open_lock, flags);
err = snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
} else {
spin_unlock_irqrestore(&midi->open_lock, flags);
}
return err;
}
static void snd_emu10k1_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_emu10k1 *emu;
struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
emu = midi->emu;
if (snd_BUG_ON(!emu))
return;
if (up)
snd_emu10k1_intr_enable(emu, midi->rx_enable);
else
snd_emu10k1_intr_disable(emu, midi->rx_enable);
}
static void snd_emu10k1_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_emu10k1 *emu;
struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
unsigned long flags;
emu = midi->emu;
if (snd_BUG_ON(!emu))
return;
if (up) {
int max = 4;
unsigned char byte;
/* try to send some amount of bytes here before interrupts */
spin_lock_irqsave(&midi->output_lock, flags);
while (max > 0) {
if (mpu401_output_ready(emu, midi)) {
if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT) ||
snd_rawmidi_transmit(substream, &byte, 1) != 1) {
/* no more data */
spin_unlock_irqrestore(&midi->output_lock, flags);
return;
}
mpu401_write_data(emu, midi, byte);
max--;
} else {
break;
}
}
spin_unlock_irqrestore(&midi->output_lock, flags);
snd_emu10k1_intr_enable(emu, midi->tx_enable);
} else {
snd_emu10k1_intr_disable(emu, midi->tx_enable);
}
}
/*
*/
static struct snd_rawmidi_ops snd_emu10k1_midi_output =
{
.open = snd_emu10k1_midi_output_open,
.close = snd_emu10k1_midi_output_close,
.trigger = snd_emu10k1_midi_output_trigger,
};
static struct snd_rawmidi_ops snd_emu10k1_midi_input =
{
.open = snd_emu10k1_midi_input_open,
.close = snd_emu10k1_midi_input_close,
.trigger = snd_emu10k1_midi_input_trigger,
};
static void snd_emu10k1_midi_free(struct snd_rawmidi *rmidi)
{
struct snd_emu10k1_midi *midi = rmidi->private_data;
midi->interrupt = NULL;
midi->rmidi = NULL;
}
static int emu10k1_midi_init(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, int device, char *name)
{
struct snd_rawmidi *rmidi;
int err;
if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
return err;
midi->emu = emu;
spin_lock_init(&midi->open_lock);
spin_lock_init(&midi->input_lock);
spin_lock_init(&midi->output_lock);
strcpy(rmidi->name, name);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1_midi_output);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1_midi_input);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
rmidi->private_data = midi;
rmidi->private_free = snd_emu10k1_midi_free;
midi->rmidi = rmidi;
return 0;
}
int snd_emu10k1_midi(struct snd_emu10k1 *emu)
{
struct snd_emu10k1_midi *midi = &emu->midi;
int err;
if ((err = emu10k1_midi_init(emu, midi, 0, "EMU10K1 MPU-401 (UART)")) < 0)
return err;
midi->tx_enable = INTE_MIDITXENABLE;
midi->rx_enable = INTE_MIDIRXENABLE;
midi->port = MUDATA;
midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
midi->interrupt = snd_emu10k1_midi_interrupt;
return 0;
}
int snd_emu10k1_audigy_midi(struct snd_emu10k1 *emu)
{
struct snd_emu10k1_midi *midi;
int err;
midi = &emu->midi;
if ((err = emu10k1_midi_init(emu, midi, 0, "Audigy MPU-401 (UART)")) < 0)
return err;
midi->tx_enable = INTE_MIDITXENABLE;
midi->rx_enable = INTE_MIDIRXENABLE;
midi->port = A_MUDATA1;
midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
midi->interrupt = snd_emu10k1_midi_interrupt;
midi = &emu->midi2;
if ((err = emu10k1_midi_init(emu, midi, 1, "Audigy MPU-401 #2")) < 0)
return err;
midi->tx_enable = INTE_A_MIDITXENABLE2;
midi->rx_enable = INTE_A_MIDIRXENABLE2;
midi->port = A_MUDATA2;
midi->ipr_tx = IPR_A_MIDITRANSBUFEMPTY2;
midi->ipr_rx = IPR_A_MIDIRECVBUFEMPTY2;
midi->interrupt = snd_emu10k1_midi_interrupt2;
return 0;
}

1883
sound/pci/emu10k1/emupcm.c Normal file

File diff suppressed because it is too large Load diff

659
sound/pci/emu10k1/emuproc.c Normal file
View file

@ -0,0 +1,659 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Creative Labs, Inc.
* Routines for control of EMU10K1 chips / proc interface routines
*
* Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
* Added EMU 1010 support.
*
* BUGS:
* --
*
* TODO:
* --
*
* 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/slab.h>
#include <linux/init.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
#include "p16v.h"
#ifdef CONFIG_PROC_FS
static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu,
struct snd_info_buffer *buffer,
char *title,
int status_reg,
int rate_reg)
{
static char *clkaccy[4] = { "1000ppm", "50ppm", "variable", "unknown" };
static int samplerate[16] = { 44100, 1, 48000, 32000, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
static char *channel[16] = { "unspec", "left", "right", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" };
static char *emphasis[8] = { "none", "50/15 usec 2 channel", "2", "3", "4", "5", "6", "7" };
unsigned int status, rate = 0;
status = snd_emu10k1_ptr_read(emu, status_reg, 0);
snd_iprintf(buffer, "\n%s\n", title);
if (status != 0xffffffff) {
snd_iprintf(buffer, "Professional Mode : %s\n", (status & SPCS_PROFESSIONAL) ? "yes" : "no");
snd_iprintf(buffer, "Not Audio Data : %s\n", (status & SPCS_NOTAUDIODATA) ? "yes" : "no");
snd_iprintf(buffer, "Copyright : %s\n", (status & SPCS_COPYRIGHT) ? "yes" : "no");
snd_iprintf(buffer, "Emphasis : %s\n", emphasis[(status & SPCS_EMPHASISMASK) >> 3]);
snd_iprintf(buffer, "Mode : %i\n", (status & SPCS_MODEMASK) >> 6);
snd_iprintf(buffer, "Category Code : 0x%x\n", (status & SPCS_CATEGORYCODEMASK) >> 8);
snd_iprintf(buffer, "Generation Status : %s\n", status & SPCS_GENERATIONSTATUS ? "original" : "copy");
snd_iprintf(buffer, "Source Mask : %i\n", (status & SPCS_SOURCENUMMASK) >> 16);
snd_iprintf(buffer, "Channel Number : %s\n", channel[(status & SPCS_CHANNELNUMMASK) >> 20]);
snd_iprintf(buffer, "Sample Rate : %iHz\n", samplerate[(status & SPCS_SAMPLERATEMASK) >> 24]);
snd_iprintf(buffer, "Clock Accuracy : %s\n", clkaccy[(status & SPCS_CLKACCYMASK) >> 28]);
if (rate_reg > 0) {
rate = snd_emu10k1_ptr_read(emu, rate_reg, 0);
snd_iprintf(buffer, "S/PDIF Valid : %s\n", rate & SRCS_SPDIFVALID ? "on" : "off");
snd_iprintf(buffer, "S/PDIF Locked : %s\n", rate & SRCS_SPDIFLOCKED ? "on" : "off");
snd_iprintf(buffer, "Rate Locked : %s\n", rate & SRCS_RATELOCKED ? "on" : "off");
/* From ((Rate * 48000 ) / 262144); */
snd_iprintf(buffer, "Estimated Sample Rate : %d\n", ((rate & 0xFFFFF ) * 375) >> 11);
}
} else {
snd_iprintf(buffer, "No signal detected.\n");
}
}
static void snd_emu10k1_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
/* FIXME - output names are in emufx.c too */
static char *creative_outs[32] = {
/* 00 */ "AC97 Left",
/* 01 */ "AC97 Right",
/* 02 */ "Optical IEC958 Left",
/* 03 */ "Optical IEC958 Right",
/* 04 */ "Center",
/* 05 */ "LFE",
/* 06 */ "Headphone Left",
/* 07 */ "Headphone Right",
/* 08 */ "Surround Left",
/* 09 */ "Surround Right",
/* 10 */ "PCM Capture Left",
/* 11 */ "PCM Capture Right",
/* 12 */ "MIC Capture",
/* 13 */ "AC97 Surround Left",
/* 14 */ "AC97 Surround Right",
/* 15 */ "???",
/* 16 */ "???",
/* 17 */ "Analog Center",
/* 18 */ "Analog LFE",
/* 19 */ "???",
/* 20 */ "???",
/* 21 */ "???",
/* 22 */ "???",
/* 23 */ "???",
/* 24 */ "???",
/* 25 */ "???",
/* 26 */ "???",
/* 27 */ "???",
/* 28 */ "???",
/* 29 */ "???",
/* 30 */ "???",
/* 31 */ "???"
};
static char *audigy_outs[64] = {
/* 00 */ "Digital Front Left",
/* 01 */ "Digital Front Right",
/* 02 */ "Digital Center",
/* 03 */ "Digital LEF",
/* 04 */ "Headphone Left",
/* 05 */ "Headphone Right",
/* 06 */ "Digital Rear Left",
/* 07 */ "Digital Rear Right",
/* 08 */ "Front Left",
/* 09 */ "Front Right",
/* 10 */ "Center",
/* 11 */ "LFE",
/* 12 */ "???",
/* 13 */ "???",
/* 14 */ "Rear Left",
/* 15 */ "Rear Right",
/* 16 */ "AC97 Front Left",
/* 17 */ "AC97 Front Right",
/* 18 */ "ADC Caputre Left",
/* 19 */ "ADC Capture Right",
/* 20 */ "???",
/* 21 */ "???",
/* 22 */ "???",
/* 23 */ "???",
/* 24 */ "???",
/* 25 */ "???",
/* 26 */ "???",
/* 27 */ "???",
/* 28 */ "???",
/* 29 */ "???",
/* 30 */ "???",
/* 31 */ "???",
/* 32 */ "FXBUS2_0",
/* 33 */ "FXBUS2_1",
/* 34 */ "FXBUS2_2",
/* 35 */ "FXBUS2_3",
/* 36 */ "FXBUS2_4",
/* 37 */ "FXBUS2_5",
/* 38 */ "FXBUS2_6",
/* 39 */ "FXBUS2_7",
/* 40 */ "FXBUS2_8",
/* 41 */ "FXBUS2_9",
/* 42 */ "FXBUS2_10",
/* 43 */ "FXBUS2_11",
/* 44 */ "FXBUS2_12",
/* 45 */ "FXBUS2_13",
/* 46 */ "FXBUS2_14",
/* 47 */ "FXBUS2_15",
/* 48 */ "FXBUS2_16",
/* 49 */ "FXBUS2_17",
/* 50 */ "FXBUS2_18",
/* 51 */ "FXBUS2_19",
/* 52 */ "FXBUS2_20",
/* 53 */ "FXBUS2_21",
/* 54 */ "FXBUS2_22",
/* 55 */ "FXBUS2_23",
/* 56 */ "FXBUS2_24",
/* 57 */ "FXBUS2_25",
/* 58 */ "FXBUS2_26",
/* 59 */ "FXBUS2_27",
/* 60 */ "FXBUS2_28",
/* 61 */ "FXBUS2_29",
/* 62 */ "FXBUS2_30",
/* 63 */ "FXBUS2_31"
};
struct snd_emu10k1 *emu = entry->private_data;
unsigned int val, val1;
int nefx = emu->audigy ? 64 : 32;
char **outputs = emu->audigy ? audigy_outs : creative_outs;
int idx;
snd_iprintf(buffer, "EMU10K1\n\n");
snd_iprintf(buffer, "Card : %s\n",
emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative"));
snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size);
snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2);
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, "Effect Send Routing :\n");
for (idx = 0; idx < NUM_G; idx++) {
val = emu->audigy ?
snd_emu10k1_ptr_read(emu, A_FXRT1, idx) :
snd_emu10k1_ptr_read(emu, FXRT, idx);
val1 = emu->audigy ?
snd_emu10k1_ptr_read(emu, A_FXRT2, idx) :
0;
if (emu->audigy) {
snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i, ",
idx,
val & 0x3f,
(val >> 8) & 0x3f,
(val >> 16) & 0x3f,
(val >> 24) & 0x3f);
snd_iprintf(buffer, "E=%i, F=%i, G=%i, H=%i\n",
val1 & 0x3f,
(val1 >> 8) & 0x3f,
(val1 >> 16) & 0x3f,
(val1 >> 24) & 0x3f);
} else {
snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i\n",
idx,
(val >> 16) & 0x0f,
(val >> 20) & 0x0f,
(val >> 24) & 0x0f,
(val >> 28) & 0x0f);
}
}
snd_iprintf(buffer, "\nCaptured FX Outputs :\n");
for (idx = 0; idx < nefx; idx++) {
if (emu->efx_voices_mask[idx/32] & (1 << (idx%32)))
snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]);
}
snd_iprintf(buffer, "\nAll FX Outputs :\n");
for (idx = 0; idx < (emu->audigy ? 64 : 32); idx++)
snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]);
}
static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_emu10k1 *emu = entry->private_data;
u32 value;
u32 value2;
u32 rate;
if (emu->card_capabilities->emu_model) {
snd_emu1010_fpga_read(emu, 0x38, &value);
if ((value & 0x1) == 0) {
snd_emu1010_fpga_read(emu, 0x2a, &value);
snd_emu1010_fpga_read(emu, 0x2b, &value2);
rate = 0x1770000 / (((value << 5) | value2)+1);
snd_iprintf(buffer, "ADAT Locked : %u\n", rate);
} else {
snd_iprintf(buffer, "ADAT Unlocked\n");
}
snd_emu1010_fpga_read(emu, 0x20, &value);
if ((value & 0x4) == 0) {
snd_emu1010_fpga_read(emu, 0x28, &value);
snd_emu1010_fpga_read(emu, 0x29, &value2);
rate = 0x1770000 / (((value << 5) | value2)+1);
snd_iprintf(buffer, "SPDIF Locked : %d\n", rate);
} else {
snd_iprintf(buffer, "SPDIF Unlocked\n");
}
} else {
snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS);
snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS);
}
#if 0
val = snd_emu10k1_ptr_read(emu, ZVSRCS, 0);
snd_iprintf(buffer, "\nZoomed Video\n");
snd_iprintf(buffer, "Rate Locked : %s\n", val & SRCS_RATELOCKED ? "on" : "off");
snd_iprintf(buffer, "Estimated Sample Rate : 0x%x\n", val & SRCS_ESTSAMPLERATE);
#endif
}
static void snd_emu10k1_proc_rates_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
static int samplerate[8] = { 44100, 48000, 96000, 192000, 4, 5, 6, 7 };
struct snd_emu10k1 *emu = entry->private_data;
unsigned int val, tmp, n;
val = snd_emu10k1_ptr20_read(emu, CAPTURE_RATE_STATUS, 0);
tmp = (val >> 16) & 0x8;
for (n = 0; n < 4; n++) {
tmp = val >> (16 + (n*4));
if (tmp & 0x8) snd_iprintf(buffer, "Channel %d: Rate=%d\n", n, samplerate[tmp & 0x7]);
else snd_iprintf(buffer, "Channel %d: No input\n", n);
}
}
static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
u32 pc;
struct snd_emu10k1 *emu = entry->private_data;
snd_iprintf(buffer, "FX8010 Instruction List '%s'\n", emu->fx8010.name);
snd_iprintf(buffer, " Code dump :\n");
for (pc = 0; pc < (emu->audigy ? 1024 : 512); pc++) {
u32 low, high;
low = snd_emu10k1_efx_read(emu, pc * 2);
high = snd_emu10k1_efx_read(emu, pc * 2 + 1);
if (emu->audigy)
snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
(high >> 24) & 0x0f,
(high >> 12) & 0x7ff,
(high >> 0) & 0x7ff,
(low >> 12) & 0x7ff,
(low >> 0) & 0x7ff,
pc,
high, low);
else
snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
(high >> 20) & 0x0f,
(high >> 10) & 0x3ff,
(high >> 0) & 0x3ff,
(low >> 10) & 0x3ff,
(low >> 0) & 0x3ff,
pc,
high, low);
}
}
#define TOTAL_SIZE_GPR (0x100*4)
#define A_TOTAL_SIZE_GPR (0x200*4)
#define TOTAL_SIZE_TANKMEM_DATA (0xa0*4)
#define TOTAL_SIZE_TANKMEM_ADDR (0xa0*4)
#define A_TOTAL_SIZE_TANKMEM_DATA (0x100*4)
#define A_TOTAL_SIZE_TANKMEM_ADDR (0x100*4)
#define TOTAL_SIZE_CODE (0x200*8)
#define A_TOTAL_SIZE_CODE (0x400*8)
static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry,
void *file_private_data,
struct file *file, char __user *buf,
size_t count, loff_t pos)
{
struct snd_emu10k1 *emu = entry->private_data;
unsigned int offset;
int tram_addr = 0;
unsigned int *tmp;
long res;
unsigned int idx;
if (!strcmp(entry->name, "fx8010_tram_addr")) {
offset = TANKMEMADDRREGBASE;
tram_addr = 1;
} else if (!strcmp(entry->name, "fx8010_tram_data")) {
offset = TANKMEMDATAREGBASE;
} else if (!strcmp(entry->name, "fx8010_code")) {
offset = emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
} else {
offset = emu->audigy ? A_FXGPREGBASE : FXGPREGBASE;
}
tmp = kmalloc(count + 8, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
for (idx = 0; idx < ((pos & 3) + count + 3) >> 2; idx++) {
unsigned int val;
val = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0);
if (tram_addr && emu->audigy) {
val >>= 11;
val |= snd_emu10k1_ptr_read(emu, 0x100 + idx + (pos >> 2), 0) << 20;
}
tmp[idx] = val;
}
if (copy_to_user(buf, ((char *)tmp) + (pos & 3), count))
res = -EFAULT;
else
res = count;
kfree(tmp);
return res;
}
static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_emu10k1 *emu = entry->private_data;
struct snd_emu10k1_voice *voice;
int idx;
snd_iprintf(buffer, "ch\tuse\tpcm\tefx\tsynth\tmidi\n");
for (idx = 0; idx < NUM_G; idx++) {
voice = &emu->voices[idx];
snd_iprintf(buffer, "%i\t%i\t%i\t%i\t%i\t%i\n",
idx,
voice->use,
voice->pcm,
voice->efx,
voice->synth,
voice->midi);
}
}
#ifdef CONFIG_SND_DEBUG
static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_emu10k1 *emu = entry->private_data;
u32 value;
int i;
snd_iprintf(buffer, "EMU1010 Registers:\n\n");
for(i = 0; i < 0x40; i+=1) {
snd_emu1010_fpga_read(emu, i, &value);
snd_iprintf(buffer, "%02X: %08X, %02X\n", i, value, (value >> 8) & 0x7f);
}
}
static void snd_emu_proc_io_reg_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_emu10k1 *emu = entry->private_data;
unsigned long value;
unsigned long flags;
int i;
snd_iprintf(buffer, "IO Registers:\n\n");
for(i = 0; i < 0x40; i+=4) {
spin_lock_irqsave(&emu->emu_lock, flags);
value = inl(emu->port + i);
spin_unlock_irqrestore(&emu->emu_lock, flags);
snd_iprintf(buffer, "%02X: %08lX\n", i, value);
}
}
static void snd_emu_proc_io_reg_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_emu10k1 *emu = entry->private_data;
unsigned long flags;
char line[64];
u32 reg, val;
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x", &reg, &val) != 2)
continue;
if (reg < 0x40 && val <= 0xffffffff) {
spin_lock_irqsave(&emu->emu_lock, flags);
outl(val, emu->port + (reg & 0xfffffffc));
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
}
}
static unsigned int snd_ptr_read(struct snd_emu10k1 * emu,
unsigned int iobase,
unsigned int reg,
unsigned int chn)
{
unsigned long flags;
unsigned int regptr, val;
regptr = (reg << 16) | chn;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + iobase + PTR);
val = inl(emu->port + iobase + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return val;
}
static void snd_ptr_write(struct snd_emu10k1 *emu,
unsigned int iobase,
unsigned int reg,
unsigned int chn,
unsigned int data)
{
unsigned int regptr;
unsigned long flags;
regptr = (reg << 16) | chn;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + iobase + PTR);
outl(data, emu->port + iobase + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
static void snd_emu_proc_ptr_reg_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer, int iobase, int offset, int length, int voices)
{
struct snd_emu10k1 *emu = entry->private_data;
unsigned long value;
int i,j;
if (offset+length > 0xa0) {
snd_iprintf(buffer, "Input values out of range\n");
return;
}
snd_iprintf(buffer, "Registers 0x%x\n", iobase);
for(i = offset; i < offset+length; i++) {
snd_iprintf(buffer, "%02X: ",i);
for (j = 0; j < voices; j++) {
if(iobase == 0)
value = snd_ptr_read(emu, 0, i, j);
else
value = snd_ptr_read(emu, 0x20, i, j);
snd_iprintf(buffer, "%08lX ", value);
}
snd_iprintf(buffer, "\n");
}
}
static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer, int iobase)
{
struct snd_emu10k1 *emu = entry->private_data;
char line[64];
unsigned int reg, channel_id , val;
while (!snd_info_get_line(buffer, line, sizeof(line))) {
if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
continue;
if (reg < 0xa0 && val <= 0xffffffff && channel_id <= 3)
snd_ptr_write(emu, iobase, reg, channel_id, val);
}
}
static void snd_emu_proc_ptr_reg_write00(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_emu_proc_ptr_reg_write(entry, buffer, 0);
}
static void snd_emu_proc_ptr_reg_write20(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_emu_proc_ptr_reg_write(entry, buffer, 0x20);
}
static void snd_emu_proc_ptr_reg_read00a(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0, 0x40, 64);
}
static void snd_emu_proc_ptr_reg_read00b(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0x40, 0x40, 64);
}
static void snd_emu_proc_ptr_reg_read20a(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0, 0x40, 4);
}
static void snd_emu_proc_ptr_reg_read20b(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x40, 0x40, 4);
}
static void snd_emu_proc_ptr_reg_read20c(struct snd_info_entry *entry,
struct snd_info_buffer * buffer)
{
snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x80, 0x20, 4);
}
#endif
static struct snd_info_entry_ops snd_emu10k1_proc_ops_fx8010 = {
.read = snd_emu10k1_fx8010_read,
};
int snd_emu10k1_proc_init(struct snd_emu10k1 *emu)
{
struct snd_info_entry *entry;
#ifdef CONFIG_SND_DEBUG
if (emu->card_capabilities->emu_model) {
if (! snd_card_proc_new(emu->card, "emu1010_regs", &entry))
snd_info_set_text_ops(entry, emu, snd_emu_proc_emu1010_reg_read);
}
if (! snd_card_proc_new(emu->card, "io_regs", &entry)) {
snd_info_set_text_ops(entry, emu, snd_emu_proc_io_reg_read);
entry->c.text.write = snd_emu_proc_io_reg_write;
entry->mode |= S_IWUSR;
}
if (! snd_card_proc_new(emu->card, "ptr_regs00a", &entry)) {
snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00a);
entry->c.text.write = snd_emu_proc_ptr_reg_write00;
entry->mode |= S_IWUSR;
}
if (! snd_card_proc_new(emu->card, "ptr_regs00b", &entry)) {
snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00b);
entry->c.text.write = snd_emu_proc_ptr_reg_write00;
entry->mode |= S_IWUSR;
}
if (! snd_card_proc_new(emu->card, "ptr_regs20a", &entry)) {
snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20a);
entry->c.text.write = snd_emu_proc_ptr_reg_write20;
entry->mode |= S_IWUSR;
}
if (! snd_card_proc_new(emu->card, "ptr_regs20b", &entry)) {
snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20b);
entry->c.text.write = snd_emu_proc_ptr_reg_write20;
entry->mode |= S_IWUSR;
}
if (! snd_card_proc_new(emu->card, "ptr_regs20c", &entry)) {
snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20c);
entry->c.text.write = snd_emu_proc_ptr_reg_write20;
entry->mode |= S_IWUSR;
}
#endif
if (! snd_card_proc_new(emu->card, "emu10k1", &entry))
snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_read);
if (emu->card_capabilities->emu10k2_chip) {
if (! snd_card_proc_new(emu->card, "spdif-in", &entry))
snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_spdif_read);
}
if (emu->card_capabilities->ca0151_chip) {
if (! snd_card_proc_new(emu->card, "capture-rates", &entry))
snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_rates_read);
}
if (! snd_card_proc_new(emu->card, "voices", &entry))
snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_voices_read);
if (! snd_card_proc_new(emu->card, "fx8010_gpr", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = emu->audigy ? A_TOTAL_SIZE_GPR : TOTAL_SIZE_GPR;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
}
if (! snd_card_proc_new(emu->card, "fx8010_tram_data", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_DATA : TOTAL_SIZE_TANKMEM_DATA ;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
}
if (! snd_card_proc_new(emu->card, "fx8010_tram_addr", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_ADDR : TOTAL_SIZE_TANKMEM_ADDR ;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
}
if (! snd_card_proc_new(emu->card, "fx8010_code", &entry)) {
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->private_data = emu;
entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->size = emu->audigy ? A_TOTAL_SIZE_CODE : TOTAL_SIZE_CODE;
entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
}
if (! snd_card_proc_new(emu->card, "fx8010_acode", &entry)) {
entry->content = SNDRV_INFO_CONTENT_TEXT;
entry->private_data = emu;
entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
entry->c.text.read = snd_emu10k1_proc_acode_read;
}
return 0;
}
#endif /* CONFIG_PROC_FS */

580
sound/pci/emu10k1/io.c Normal file
View file

@ -0,0 +1,580 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Creative Labs, Inc.
* Routines for control of EMU10K1 chips
*
* BUGS:
* --
*
* TODO:
* --
*
* 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/time.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
#include <linux/delay.h>
#include <linux/export.h>
#include "p17v.h"
unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn)
{
unsigned long flags;
unsigned int regptr, val;
unsigned int mask;
mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
if (reg & 0xff000000) {
unsigned char size, offset;
size = (reg >> 24) & 0x3f;
offset = (reg >> 16) & 0x1f;
mask = ((1 << size) - 1) << offset;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
val = inl(emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return (val & mask) >> offset;
} else {
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
val = inl(emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return val;
}
}
EXPORT_SYMBOL(snd_emu10k1_ptr_read);
void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data)
{
unsigned int regptr;
unsigned long flags;
unsigned int mask;
if (snd_BUG_ON(!emu))
return;
mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
if (reg & 0xff000000) {
unsigned char size, offset;
size = (reg >> 24) & 0x3f;
offset = (reg >> 16) & 0x1f;
mask = ((1 << size) - 1) << offset;
data = (data << offset) & mask;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
data |= inl(emu->port + DATA) & ~mask;
outl(data, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
} else {
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + PTR);
outl(data, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
}
EXPORT_SYMBOL(snd_emu10k1_ptr_write);
unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu,
unsigned int reg,
unsigned int chn)
{
unsigned long flags;
unsigned int regptr, val;
regptr = (reg << 16) | chn;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + 0x20 + PTR);
val = inl(emu->port + 0x20 + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return val;
}
void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu,
unsigned int reg,
unsigned int chn,
unsigned int data)
{
unsigned int regptr;
unsigned long flags;
regptr = (reg << 16) | chn;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(regptr, emu->port + 0x20 + PTR);
outl(data, emu->port + 0x20 + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
int snd_emu10k1_spi_write(struct snd_emu10k1 * emu,
unsigned int data)
{
unsigned int reset, set;
unsigned int reg, tmp;
int n, result;
int err = 0;
/* This function is not re-entrant, so protect against it. */
spin_lock(&emu->spi_lock);
if (emu->card_capabilities->ca0108_chip)
reg = 0x3c; /* PTR20, reg 0x3c */
else {
/* For other chip types the SPI register
* is currently unknown. */
err = 1;
goto spi_write_exit;
}
if (data > 0xffff) {
/* Only 16bit values allowed */
err = 1;
goto spi_write_exit;
}
tmp = snd_emu10k1_ptr20_read(emu, reg, 0);
reset = (tmp & ~0x3ffff) | 0x20000; /* Set xxx20000 */
set = reset | 0x10000; /* Set xxx1xxxx */
snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* write post */
snd_emu10k1_ptr20_write(emu, reg, 0, set | data);
result = 1;
/* Wait for status bit to return to 0 */
for (n = 0; n < 100; n++) {
udelay(10);
tmp = snd_emu10k1_ptr20_read(emu, reg, 0);
if (!(tmp & 0x10000)) {
result = 0;
break;
}
}
if (result) {
/* Timed out */
err = 1;
goto spi_write_exit;
}
snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* Write post */
err = 0;
spi_write_exit:
spin_unlock(&emu->spi_lock);
return err;
}
/* The ADC does not support i2c read, so only write is implemented */
int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu,
u32 reg,
u32 value)
{
u32 tmp;
int timeout = 0;
int status;
int retry;
int err = 0;
if ((reg > 0x7f) || (value > 0x1ff)) {
dev_err(emu->card->dev, "i2c_write: invalid values.\n");
return -EINVAL;
}
/* This function is not re-entrant, so protect against it. */
spin_lock(&emu->i2c_lock);
tmp = reg << 25 | value << 16;
/* This controls the I2C connected to the WM8775 ADC Codec */
snd_emu10k1_ptr20_write(emu, P17V_I2C_1, 0, tmp);
tmp = snd_emu10k1_ptr20_read(emu, P17V_I2C_1, 0); /* write post */
for (retry = 0; retry < 10; retry++) {
/* Send the data to i2c */
tmp = 0;
tmp = tmp | (I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD);
snd_emu10k1_ptr20_write(emu, P17V_I2C_ADDR, 0, tmp);
/* Wait till the transaction ends */
while (1) {
mdelay(1);
status = snd_emu10k1_ptr20_read(emu, P17V_I2C_ADDR, 0);
timeout++;
if ((status & I2C_A_ADC_START) == 0)
break;
if (timeout > 1000) {
dev_warn(emu->card->dev,
"emu10k1:I2C:timeout status=0x%x\n",
status);
break;
}
}
//Read back and see if the transaction is successful
if ((status & I2C_A_ADC_ABORT) == 0)
break;
}
if (retry == 10) {
dev_err(emu->card->dev, "Writing to ADC failed!\n");
dev_err(emu->card->dev, "status=0x%x, reg=%d, value=%d\n",
status, reg, value);
/* dump_stack(); */
err = -EINVAL;
}
spin_unlock(&emu->i2c_lock);
return err;
}
int snd_emu1010_fpga_write(struct snd_emu10k1 * emu, u32 reg, u32 value)
{
unsigned long flags;
if (reg > 0x3f)
return 1;
reg += 0x40; /* 0x40 upwards are registers. */
if (value > 0x3f) /* 0 to 0x3f are values */
return 1;
spin_lock_irqsave(&emu->emu_lock, flags);
outl(reg, emu->port + A_IOCFG);
udelay(10);
outl(reg | 0x80, emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
udelay(10);
outl(value, emu->port + A_IOCFG);
udelay(10);
outl(value | 0x80 , emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
spin_unlock_irqrestore(&emu->emu_lock, flags);
return 0;
}
int snd_emu1010_fpga_read(struct snd_emu10k1 * emu, u32 reg, u32 *value)
{
unsigned long flags;
if (reg > 0x3f)
return 1;
reg += 0x40; /* 0x40 upwards are registers. */
spin_lock_irqsave(&emu->emu_lock, flags);
outl(reg, emu->port + A_IOCFG);
udelay(10);
outl(reg | 0x80, emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
udelay(10);
*value = ((inl(emu->port + A_IOCFG) >> 8) & 0x7f);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return 0;
}
/* Each Destination has one and only one Source,
* but one Source can feed any number of Destinations simultaneously.
*/
int snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 * emu, u32 dst, u32 src)
{
snd_emu1010_fpga_write(emu, 0x00, ((dst >> 8) & 0x3f) );
snd_emu1010_fpga_write(emu, 0x01, (dst & 0x3f) );
snd_emu1010_fpga_write(emu, 0x02, ((src >> 8) & 0x3f) );
snd_emu1010_fpga_write(emu, 0x03, (src & 0x3f) );
return 0;
}
void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
unsigned long flags;
unsigned int enable;
spin_lock_irqsave(&emu->emu_lock, flags);
enable = inl(emu->port + INTE) | intrenb;
outl(enable, emu->port + INTE);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
unsigned long flags;
unsigned int enable;
spin_lock_irqsave(&emu->emu_lock, flags);
enable = inl(emu->port + INTE) & ~intrenb;
outl(enable, emu->port + INTE);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(CLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << (voicenum - 32);
} else {
outl(CLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << voicenum;
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(CLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << (voicenum - 32));
} else {
outl(CLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << voicenum);
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(CLIPH << 16, emu->port + PTR);
voicenum = 1 << (voicenum - 32);
} else {
outl(CLIPL << 16, emu->port + PTR);
voicenum = 1 << voicenum;
}
outl(voicenum, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(HLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << (voicenum - 32);
} else {
outl(HLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val |= 1 << voicenum;
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(HLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << (voicenum - 32));
} else {
outl(HLIEL << 16, emu->port + PTR);
val = inl(emu->port + DATA);
val &= ~(1 << voicenum);
}
outl(val, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(HLIPH << 16, emu->port + PTR);
voicenum = 1 << (voicenum - 32);
} else {
outl(HLIPL << 16, emu->port + PTR);
voicenum = 1 << voicenum;
}
outl(voicenum, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int sol;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(SOLEH << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol |= 1 << (voicenum - 32);
} else {
outl(SOLEL << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol |= 1 << voicenum;
}
outl(sol, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
{
unsigned long flags;
unsigned int sol;
spin_lock_irqsave(&emu->emu_lock, flags);
/* voice interrupt */
if (voicenum >= 32) {
outl(SOLEH << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol &= ~(1 << (voicenum - 32));
} else {
outl(SOLEL << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
sol &= ~(1 << voicenum);
}
outl(sol, emu->port + DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait)
{
volatile unsigned count;
unsigned int newtime = 0, curtime;
curtime = inl(emu->port + WC) >> 6;
while (wait-- > 0) {
count = 0;
while (count++ < 16384) {
newtime = inl(emu->port + WC) >> 6;
if (newtime != curtime)
break;
}
if (count > 16384)
break;
curtime = newtime;
}
}
unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
struct snd_emu10k1 *emu = ac97->private_data;
unsigned long flags;
unsigned short val;
spin_lock_irqsave(&emu->emu_lock, flags);
outb(reg, emu->port + AC97ADDRESS);
val = inw(emu->port + AC97DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return val;
}
void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data)
{
struct snd_emu10k1 *emu = ac97->private_data;
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
outb(reg, emu->port + AC97ADDRESS);
outw(data, emu->port + AC97DATA);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
/*
* convert rate to pitch
*/
unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate)
{
static u32 logMagTable[128] = {
0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
};
static char logSlopeTable[128] = {
0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
};
int i;
if (rate == 0)
return 0; /* Bail out if no leading "1" */
rate *= 11185; /* Scale 48000 to 0x20002380 */
for (i = 31; i > 0; i--) {
if (rate & 0x80000000) { /* Detect leading "1" */
return (((unsigned int) (i - 15) << 20) +
logMagTable[0x7f & (rate >> 24)] +
(0x7f & (rate >> 17)) *
logSlopeTable[0x7f & (rate >> 24)]);
}
rate <<= 1;
}
return 0; /* Should never reach this point */
}

213
sound/pci/emu10k1/irq.c Normal file
View file

@ -0,0 +1,213 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Creative Labs, Inc.
* Routines for IRQ control of EMU10K1 chips
*
* BUGS:
* --
*
* TODO:
* --
*
* 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/time.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id)
{
struct snd_emu10k1 *emu = dev_id;
unsigned int status, status2, orig_status, orig_status2;
int handled = 0;
int timeout = 0;
while (((status = inl(emu->port + IPR)) != 0) && (timeout < 1000)) {
timeout++;
orig_status = status;
handled = 1;
if ((status & 0xffffffff) == 0xffffffff) {
dev_info(emu->card->dev,
"Suspected sound card removal\n");
break;
}
if (status & IPR_PCIERROR) {
dev_err(emu->card->dev, "interrupt: PCI error\n");
snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE);
status &= ~IPR_PCIERROR;
}
if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) {
if (emu->hwvol_interrupt)
emu->hwvol_interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE);
status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE);
}
if (status & IPR_CHANNELLOOP) {
int voice;
int voice_max = status & IPR_CHANNELNUMBERMASK;
u32 val;
struct snd_emu10k1_voice *pvoice = emu->voices;
val = snd_emu10k1_ptr_read(emu, CLIPL, 0);
for (voice = 0; voice <= voice_max; voice++) {
if (voice == 0x20)
val = snd_emu10k1_ptr_read(emu, CLIPH, 0);
if (val & 1) {
if (pvoice->use && pvoice->interrupt != NULL) {
pvoice->interrupt(emu, pvoice);
snd_emu10k1_voice_intr_ack(emu, voice);
} else {
snd_emu10k1_voice_intr_disable(emu, voice);
}
}
val >>= 1;
pvoice++;
}
val = snd_emu10k1_ptr_read(emu, HLIPL, 0);
for (voice = 0; voice <= voice_max; voice++) {
if (voice == 0x20)
val = snd_emu10k1_ptr_read(emu, HLIPH, 0);
if (val & 1) {
if (pvoice->use && pvoice->interrupt != NULL) {
pvoice->interrupt(emu, pvoice);
snd_emu10k1_voice_half_loop_intr_ack(emu, voice);
} else {
snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
}
}
val >>= 1;
pvoice++;
}
status &= ~IPR_CHANNELLOOP;
}
status &= ~IPR_CHANNELNUMBERMASK;
if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) {
if (emu->capture_interrupt)
emu->capture_interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE);
status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL);
}
if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) {
if (emu->capture_mic_interrupt)
emu->capture_mic_interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE);
status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL);
}
if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) {
if (emu->capture_efx_interrupt)
emu->capture_efx_interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE);
status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL);
}
if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
if (emu->midi.interrupt)
emu->midi.interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY);
}
if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) {
if (emu->midi2.interrupt)
emu->midi2.interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2);
status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2);
}
if (status & IPR_INTERVALTIMER) {
if (emu->timer)
snd_timer_interrupt(emu->timer, emu->timer->sticks);
else
snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
status &= ~IPR_INTERVALTIMER;
}
if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) {
if (emu->spdif_interrupt)
emu->spdif_interrupt(emu, status);
else
snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE);
status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE);
}
if (status & IPR_FXDSP) {
if (emu->dsp_interrupt)
emu->dsp_interrupt(emu);
else
snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
status &= ~IPR_FXDSP;
}
if (status & IPR_P16V) {
while ((status2 = inl(emu->port + IPR2)) != 0) {
u32 mask = INTE2_PLAYBACK_CH_0_LOOP; /* Full Loop */
struct snd_emu10k1_voice *pvoice = &(emu->p16v_voices[0]);
struct snd_emu10k1_voice *cvoice = &(emu->p16v_capture_voice);
/* dev_dbg(emu->card->dev, "status2=0x%x\n", status2); */
orig_status2 = status2;
if(status2 & mask) {
if(pvoice->use) {
snd_pcm_period_elapsed(pvoice->epcm->substream);
} else {
dev_err(emu->card->dev,
"p16v: status: 0x%08x, mask=0x%08x, pvoice=%p, use=%d\n",
status2, mask, pvoice,
pvoice->use);
}
}
if(status2 & 0x110000) {
/* dev_info(emu->card->dev, "capture int found\n"); */
if(cvoice->use) {
/* dev_info(emu->card->dev, "capture period_elapsed\n"); */
snd_pcm_period_elapsed(cvoice->epcm->substream);
}
}
outl(orig_status2, emu->port + IPR2); /* ack all */
}
status &= ~IPR_P16V;
}
if (status) {
unsigned int bits;
dev_err(emu->card->dev,
"unhandled interrupt: 0x%08x\n", status);
//make sure any interrupts we don't handle are disabled:
bits = INTE_FXDSPENABLE |
INTE_PCIERRORENABLE |
INTE_VOLINCRENABLE |
INTE_VOLDECRENABLE |
INTE_MUTEENABLE |
INTE_MICBUFENABLE |
INTE_ADCBUFENABLE |
INTE_EFXBUFENABLE |
INTE_GPSPDIFENABLE |
INTE_CDSPDIFENABLE |
INTE_INTERVALTIMERENB |
INTE_MIDITXENABLE |
INTE_MIDIRXENABLE;
if (emu->audigy)
bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2;
snd_emu10k1_intr_disable(emu, bits);
}
outl(orig_status, emu->port + IPR); /* ack all */
}
if (timeout == 1000)
dev_info(emu->card->dev, "emu10k1 irq routine failure\n");
return IRQ_RETVAL(handled);
}

581
sound/pci/emu10k1/memory.c Normal file
View file

@ -0,0 +1,581 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* EMU10K1 memory page allocation (PTB area)
*
*
* 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/pci.h>
#include <linux/gfp.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
/* page arguments of these two macros are Emu page (4096 bytes), not like
* aligned pages in others
*/
#define __set_ptb_entry(emu,page,addr) \
(((u32 *)(emu)->ptb_pages.area)[page] = cpu_to_le32(((addr) << (emu->address_mode)) | (page)))
#define UNIT_PAGES (PAGE_SIZE / EMUPAGESIZE)
#define MAX_ALIGN_PAGES0 (MAXPAGES0 / UNIT_PAGES)
#define MAX_ALIGN_PAGES1 (MAXPAGES1 / UNIT_PAGES)
/* get aligned page from offset address */
#define get_aligned_page(offset) ((offset) >> PAGE_SHIFT)
/* get offset address from aligned page */
#define aligned_page_offset(page) ((page) << PAGE_SHIFT)
#if PAGE_SIZE == 4096
/* page size == EMUPAGESIZE */
/* fill PTB entrie(s) corresponding to page with addr */
#define set_ptb_entry(emu,page,addr) __set_ptb_entry(emu,page,addr)
/* fill PTB entrie(s) corresponding to page with silence pointer */
#define set_silent_ptb(emu,page) __set_ptb_entry(emu,page,emu->silent_page.addr)
#else
/* fill PTB entries -- we need to fill UNIT_PAGES entries */
static inline void set_ptb_entry(struct snd_emu10k1 *emu, int page, dma_addr_t addr)
{
int i;
page *= UNIT_PAGES;
for (i = 0; i < UNIT_PAGES; i++, page++) {
__set_ptb_entry(emu, page, addr);
addr += EMUPAGESIZE;
}
}
static inline void set_silent_ptb(struct snd_emu10k1 *emu, int page)
{
int i;
page *= UNIT_PAGES;
for (i = 0; i < UNIT_PAGES; i++, page++)
/* do not increment ptr */
__set_ptb_entry(emu, page, emu->silent_page.addr);
}
#endif /* PAGE_SIZE */
/*
*/
static int synth_alloc_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk);
static int synth_free_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk);
#define get_emu10k1_memblk(l,member) list_entry(l, struct snd_emu10k1_memblk, member)
/* initialize emu10k1 part */
static void emu10k1_memblk_init(struct snd_emu10k1_memblk *blk)
{
blk->mapped_page = -1;
INIT_LIST_HEAD(&blk->mapped_link);
INIT_LIST_HEAD(&blk->mapped_order_link);
blk->map_locked = 0;
blk->first_page = get_aligned_page(blk->mem.offset);
blk->last_page = get_aligned_page(blk->mem.offset + blk->mem.size - 1);
blk->pages = blk->last_page - blk->first_page + 1;
}
/*
* search empty region on PTB with the given size
*
* if an empty region is found, return the page and store the next mapped block
* in nextp
* if not found, return a negative error code.
*/
static int search_empty_map_area(struct snd_emu10k1 *emu, int npages, struct list_head **nextp)
{
int page = 0, found_page = -ENOMEM;
int max_size = npages;
int size;
struct list_head *candidate = &emu->mapped_link_head;
struct list_head *pos;
list_for_each (pos, &emu->mapped_link_head) {
struct snd_emu10k1_memblk *blk = get_emu10k1_memblk(pos, mapped_link);
if (blk->mapped_page < 0)
continue;
size = blk->mapped_page - page;
if (size == npages) {
*nextp = pos;
return page;
}
else if (size > max_size) {
/* we look for the maximum empty hole */
max_size = size;
candidate = pos;
found_page = page;
}
page = blk->mapped_page + blk->pages;
}
size = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0) - page;
if (size >= max_size) {
*nextp = pos;
return page;
}
*nextp = candidate;
return found_page;
}
/*
* map a memory block onto emu10k1's PTB
*
* call with memblk_lock held
*/
static int map_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
{
int page, pg;
struct list_head *next;
page = search_empty_map_area(emu, blk->pages, &next);
if (page < 0) /* not found */
return page;
/* insert this block in the proper position of mapped list */
list_add_tail(&blk->mapped_link, next);
/* append this as a newest block in order list */
list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head);
blk->mapped_page = page;
/* fill PTB */
for (pg = blk->first_page; pg <= blk->last_page; pg++) {
set_ptb_entry(emu, page, emu->page_addr_table[pg]);
page++;
}
return 0;
}
/*
* unmap the block
* return the size of resultant empty pages
*
* call with memblk_lock held
*/
static int unmap_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
{
int start_page, end_page, mpage, pg;
struct list_head *p;
struct snd_emu10k1_memblk *q;
/* calculate the expected size of empty region */
if ((p = blk->mapped_link.prev) != &emu->mapped_link_head) {
q = get_emu10k1_memblk(p, mapped_link);
start_page = q->mapped_page + q->pages;
} else
start_page = 0;
if ((p = blk->mapped_link.next) != &emu->mapped_link_head) {
q = get_emu10k1_memblk(p, mapped_link);
end_page = q->mapped_page;
} else
end_page = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0);
/* remove links */
list_del(&blk->mapped_link);
list_del(&blk->mapped_order_link);
/* clear PTB */
mpage = blk->mapped_page;
for (pg = blk->first_page; pg <= blk->last_page; pg++) {
set_silent_ptb(emu, mpage);
mpage++;
}
blk->mapped_page = -1;
return end_page - start_page; /* return the new empty size */
}
/*
* search empty pages with the given size, and create a memory block
*
* unlike synth_alloc the memory block is aligned to the page start
*/
static struct snd_emu10k1_memblk *
search_empty(struct snd_emu10k1 *emu, int size)
{
struct list_head *p;
struct snd_emu10k1_memblk *blk;
int page, psize;
psize = get_aligned_page(size + PAGE_SIZE -1);
page = 0;
list_for_each(p, &emu->memhdr->block) {
blk = get_emu10k1_memblk(p, mem.list);
if (page + psize <= blk->first_page)
goto __found_pages;
page = blk->last_page + 1;
}
if (page + psize > emu->max_cache_pages)
return NULL;
__found_pages:
/* create a new memory block */
blk = (struct snd_emu10k1_memblk *)__snd_util_memblk_new(emu->memhdr, psize << PAGE_SHIFT, p->prev);
if (blk == NULL)
return NULL;
blk->mem.offset = aligned_page_offset(page); /* set aligned offset */
emu10k1_memblk_init(blk);
return blk;
}
/*
* check if the given pointer is valid for pages
*/
static int is_valid_page(struct snd_emu10k1 *emu, dma_addr_t addr)
{
if (addr & ~emu->dma_mask) {
dev_err(emu->card->dev,
"max memory size is 0x%lx (addr = 0x%lx)!!\n",
emu->dma_mask, (unsigned long)addr);
return 0;
}
if (addr & (EMUPAGESIZE-1)) {
dev_err(emu->card->dev, "page is not aligned\n");
return 0;
}
return 1;
}
/*
* map the given memory block on PTB.
* if the block is already mapped, update the link order.
* if no empty pages are found, tries to release unused memory blocks
* and retry the mapping.
*/
int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
{
int err;
int size;
struct list_head *p, *nextp;
struct snd_emu10k1_memblk *deleted;
unsigned long flags;
spin_lock_irqsave(&emu->memblk_lock, flags);
if (blk->mapped_page >= 0) {
/* update order link */
list_move_tail(&blk->mapped_order_link,
&emu->mapped_order_link_head);
spin_unlock_irqrestore(&emu->memblk_lock, flags);
return 0;
}
if ((err = map_memblk(emu, blk)) < 0) {
/* no enough page - try to unmap some blocks */
/* starting from the oldest block */
p = emu->mapped_order_link_head.next;
for (; p != &emu->mapped_order_link_head; p = nextp) {
nextp = p->next;
deleted = get_emu10k1_memblk(p, mapped_order_link);
if (deleted->map_locked)
continue;
size = unmap_memblk(emu, deleted);
if (size >= blk->pages) {
/* ok the empty region is enough large */
err = map_memblk(emu, blk);
break;
}
}
}
spin_unlock_irqrestore(&emu->memblk_lock, flags);
return err;
}
EXPORT_SYMBOL(snd_emu10k1_memblk_map);
/*
* page allocation for DMA
*/
struct snd_util_memblk *
snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_util_memhdr *hdr;
struct snd_emu10k1_memblk *blk;
int page, err, idx;
if (snd_BUG_ON(!emu))
return NULL;
if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
runtime->dma_bytes >= (emu->address_mode ? MAXPAGES1 : MAXPAGES0) * EMUPAGESIZE))
return NULL;
hdr = emu->memhdr;
if (snd_BUG_ON(!hdr))
return NULL;
idx = runtime->period_size >= runtime->buffer_size ?
(emu->delay_pcm_irq * 2) : 0;
mutex_lock(&hdr->block_mutex);
blk = search_empty(emu, runtime->dma_bytes + idx);
if (blk == NULL) {
mutex_unlock(&hdr->block_mutex);
return NULL;
}
/* fill buffer addresses but pointers are not stored so that
* snd_free_pci_page() is not called in in synth_free()
*/
idx = 0;
for (page = blk->first_page; page <= blk->last_page; page++, idx++) {
unsigned long ofs = idx << PAGE_SHIFT;
dma_addr_t addr;
if (ofs >= runtime->dma_bytes)
addr = emu->silent_page.addr;
else
addr = snd_pcm_sgbuf_get_addr(substream, ofs);
if (! is_valid_page(emu, addr)) {
dev_err(emu->card->dev,
"emu: failure page = %d\n", idx);
mutex_unlock(&hdr->block_mutex);
return NULL;
}
emu->page_addr_table[page] = addr;
emu->page_ptr_table[page] = NULL;
}
/* set PTB entries */
blk->map_locked = 1; /* do not unmap this block! */
err = snd_emu10k1_memblk_map(emu, blk);
if (err < 0) {
__snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
mutex_unlock(&hdr->block_mutex);
return NULL;
}
mutex_unlock(&hdr->block_mutex);
return (struct snd_util_memblk *)blk;
}
/*
* release DMA buffer from page table
*/
int snd_emu10k1_free_pages(struct snd_emu10k1 *emu, struct snd_util_memblk *blk)
{
if (snd_BUG_ON(!emu || !blk))
return -EINVAL;
return snd_emu10k1_synth_free(emu, blk);
}
/*
* memory allocation using multiple pages (for synth)
* Unlike the DMA allocation above, non-contiguous pages are assined.
*/
/*
* allocate a synth sample area
*/
struct snd_util_memblk *
snd_emu10k1_synth_alloc(struct snd_emu10k1 *hw, unsigned int size)
{
struct snd_emu10k1_memblk *blk;
struct snd_util_memhdr *hdr = hw->memhdr;
mutex_lock(&hdr->block_mutex);
blk = (struct snd_emu10k1_memblk *)__snd_util_mem_alloc(hdr, size);
if (blk == NULL) {
mutex_unlock(&hdr->block_mutex);
return NULL;
}
if (synth_alloc_pages(hw, blk)) {
__snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
mutex_unlock(&hdr->block_mutex);
return NULL;
}
snd_emu10k1_memblk_map(hw, blk);
mutex_unlock(&hdr->block_mutex);
return (struct snd_util_memblk *)blk;
}
EXPORT_SYMBOL(snd_emu10k1_synth_alloc);
/*
* free a synth sample area
*/
int
snd_emu10k1_synth_free(struct snd_emu10k1 *emu, struct snd_util_memblk *memblk)
{
struct snd_util_memhdr *hdr = emu->memhdr;
struct snd_emu10k1_memblk *blk = (struct snd_emu10k1_memblk *)memblk;
unsigned long flags;
mutex_lock(&hdr->block_mutex);
spin_lock_irqsave(&emu->memblk_lock, flags);
if (blk->mapped_page >= 0)
unmap_memblk(emu, blk);
spin_unlock_irqrestore(&emu->memblk_lock, flags);
synth_free_pages(emu, blk);
__snd_util_mem_free(hdr, memblk);
mutex_unlock(&hdr->block_mutex);
return 0;
}
EXPORT_SYMBOL(snd_emu10k1_synth_free);
/* check new allocation range */
static void get_single_page_range(struct snd_util_memhdr *hdr,
struct snd_emu10k1_memblk *blk,
int *first_page_ret, int *last_page_ret)
{
struct list_head *p;
struct snd_emu10k1_memblk *q;
int first_page, last_page;
first_page = blk->first_page;
if ((p = blk->mem.list.prev) != &hdr->block) {
q = get_emu10k1_memblk(p, mem.list);
if (q->last_page == first_page)
first_page++; /* first page was already allocated */
}
last_page = blk->last_page;
if ((p = blk->mem.list.next) != &hdr->block) {
q = get_emu10k1_memblk(p, mem.list);
if (q->first_page == last_page)
last_page--; /* last page was already allocated */
}
*first_page_ret = first_page;
*last_page_ret = last_page;
}
/* release allocated pages */
static void __synth_free_pages(struct snd_emu10k1 *emu, int first_page,
int last_page)
{
int page;
for (page = first_page; page <= last_page; page++) {
free_page((unsigned long)emu->page_ptr_table[page]);
emu->page_addr_table[page] = 0;
emu->page_ptr_table[page] = NULL;
}
}
/*
* allocate kernel pages
*/
static int synth_alloc_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
{
int page, first_page, last_page;
emu10k1_memblk_init(blk);
get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
/* allocate kernel pages */
for (page = first_page; page <= last_page; page++) {
/* first try to allocate from <4GB zone */
struct page *p = alloc_page(GFP_KERNEL | GFP_DMA32 |
__GFP_NOWARN);
if (!p || (page_to_pfn(p) & ~(emu->dma_mask >> PAGE_SHIFT))) {
if (p)
__free_page(p);
/* try to allocate from <16MB zone */
p = alloc_page(GFP_ATOMIC | GFP_DMA |
__GFP_NORETRY | /* no OOM-killer */
__GFP_NOWARN);
}
if (!p) {
__synth_free_pages(emu, first_page, page - 1);
return -ENOMEM;
}
emu->page_addr_table[page] = page_to_phys(p);
emu->page_ptr_table[page] = page_address(p);
}
return 0;
}
/*
* free pages
*/
static int synth_free_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
{
int first_page, last_page;
get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
__synth_free_pages(emu, first_page, last_page);
return 0;
}
/* calculate buffer pointer from offset address */
static inline void *offset_ptr(struct snd_emu10k1 *emu, int page, int offset)
{
char *ptr;
if (snd_BUG_ON(page < 0 || page >= emu->max_cache_pages))
return NULL;
ptr = emu->page_ptr_table[page];
if (! ptr) {
dev_err(emu->card->dev,
"access to NULL ptr: page = %d\n", page);
return NULL;
}
ptr += offset & (PAGE_SIZE - 1);
return (void*)ptr;
}
/*
* bzero(blk + offset, size)
*/
int snd_emu10k1_synth_bzero(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
int offset, int size)
{
int page, nextofs, end_offset, temp, temp1;
void *ptr;
struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
offset += blk->offset & (PAGE_SIZE - 1);
end_offset = offset + size;
page = get_aligned_page(offset);
do {
nextofs = aligned_page_offset(page + 1);
temp = nextofs - offset;
temp1 = end_offset - offset;
if (temp1 < temp)
temp = temp1;
ptr = offset_ptr(emu, page + p->first_page, offset);
if (ptr)
memset(ptr, 0, temp);
offset = nextofs;
page++;
} while (offset < end_offset);
return 0;
}
EXPORT_SYMBOL(snd_emu10k1_synth_bzero);
/*
* copy_from_user(blk + offset, data, size)
*/
int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
int offset, const char __user *data, int size)
{
int page, nextofs, end_offset, temp, temp1;
void *ptr;
struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
offset += blk->offset & (PAGE_SIZE - 1);
end_offset = offset + size;
page = get_aligned_page(offset);
do {
nextofs = aligned_page_offset(page + 1);
temp = nextofs - offset;
temp1 = end_offset - offset;
if (temp1 < temp)
temp = temp1;
ptr = offset_ptr(emu, page + p->first_page, offset);
if (ptr && copy_from_user(ptr, data, temp))
return -EFAULT;
offset = nextofs;
data += temp;
page++;
} while (offset < end_offset);
return 0;
}
EXPORT_SYMBOL(snd_emu10k1_synth_copy_from_user);

937
sound/pci/emu10k1/p16v.c Normal file
View file

@ -0,0 +1,937 @@
/*
* Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver p16v chips
* Version: 0.25
*
* FEATURES currently supported:
* Output fixed at S32_LE, 2 channel to hw:0,0
* Rates: 44.1, 48, 96, 192.
*
* Changelog:
* 0.8
* Use separate card based buffer for periods table.
* 0.9
* Use 2 channel output streams instead of 8 channel.
* (8 channel output streams might be good for ASIO type output)
* Corrected speaker output, so Front -> Front etc.
* 0.10
* Fixed missed interrupts.
* 0.11
* Add Sound card model number and names.
* Add Analog volume controls.
* 0.12
* Corrected playback interrupts. Now interrupt per period, instead of half period.
* 0.13
* Use single trigger for multichannel.
* 0.14
* Mic capture now works at fixed: S32_LE, 96000Hz, Stereo.
* 0.15
* Force buffer_size / period_size == INTEGER.
* 0.16
* Update p16v.c to work with changed alsa api.
* 0.17
* Update p16v.c to work with changed alsa api. Removed boot_devs.
* 0.18
* Merging with snd-emu10k1 driver.
* 0.19
* One stereo channel at 24bit now works.
* 0.20
* Added better register defines.
* 0.21
* Integrated with snd-emu10k1 driver.
* 0.22
* Removed #if 0 ... #endif
* 0.23
* Implement different capture rates.
* 0.24
* Implement different capture source channels.
* e.g. When HD Capture source is set to SPDIF,
* setting HD Capture channel to 0 captures from CDROM digital input.
* setting HD Capture channel to 1 captures from SPDIF in.
* 0.25
* Include capture buffer sizes.
*
* BUGS:
* Some stability problems when unloading the snd-p16v kernel module.
* --
*
* TODO:
* SPDIF out.
* Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz.
* Currently capture fixed at 48000Hz.
*
* --
* GENERAL INFO:
* Model: SB0240
* P16V Chip: CA0151-DBS
* Audigy 2 Chip: CA0102-IAT
* AC97 Codec: STAC 9721
* ADC: Philips 1361T (Stereo 24bit)
* DAC: CS4382-K (8-channel, 24bit, 192Khz)
*
* This code was initially based on code from ALSA's emu10k1x.c which is:
* Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
*
* 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/pci.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/info.h>
#include <sound/tlv.h>
#include <sound/emu10k1.h>
#include "p16v.h"
#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */
#define PCM_FRONT_CHANNEL 0
#define PCM_REAR_CHANNEL 1
#define PCM_CENTER_LFE_CHANNEL 2
#define PCM_SIDE_CHANNEL 3
#define CONTROL_FRONT_CHANNEL 0
#define CONTROL_REAR_CHANNEL 3
#define CONTROL_CENTER_LFE_CHANNEL 1
#define CONTROL_SIDE_CHANNEL 2
/* Card IDs:
* Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2002 -> Audigy2 ZS 7.1 Model:SB0350
* Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1007 -> Audigy2 6.1 Model:SB0240
* Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1002 -> Audigy2 Platinum Model:SB msb0240230009266
* Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2007 -> Audigy4 Pro Model:SB0380 M1SB0380472001901E
*
*/
/* hardware definition */
static struct snd_pcm_hardware snd_p16v_playback_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_SYNC_START,
.formats = SNDRV_PCM_FMTBIT_S32_LE, /* Only supports 24-bit samples padded to 32 bits. */
.rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
.rate_min = 44100,
.rate_max = 192000,
.channels_min = 8,
.channels_max = 8,
.buffer_bytes_max = ((65536 - 64) * 8),
.period_bytes_min = 64,
.period_bytes_max = (65536 - 64),
.periods_min = 2,
.periods_max = 8,
.fifo_size = 0,
};
static struct snd_pcm_hardware snd_p16v_capture_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
.rate_min = 44100,
.rate_max = 192000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = (65536 - 64),
.period_bytes_min = 64,
.period_bytes_max = (65536 - 128) >> 1, /* size has to be N*64 bytes */
.periods_min = 2,
.periods_max = 2,
.fifo_size = 0,
};
static void snd_p16v_pcm_free_substream(struct snd_pcm_runtime *runtime)
{
struct snd_emu10k1_pcm *epcm = runtime->private_data;
if (epcm) {
/* dev_dbg(emu->card->dev, "epcm free: %p\n", epcm); */
kfree(epcm);
}
}
/* open_playback callback */
static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substream, int channel_id)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_emu10k1_voice *channel = &(emu->p16v_voices[channel_id]);
struct snd_emu10k1_pcm *epcm;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
/* dev_dbg(emu->card->dev, "epcm kcalloc: %p\n", epcm); */
if (epcm == NULL)
return -ENOMEM;
epcm->emu = emu;
epcm->substream = substream;
/*
dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n",
substream->pcm->device, channel_id);
*/
runtime->private_data = epcm;
runtime->private_free = snd_p16v_pcm_free_substream;
runtime->hw = snd_p16v_playback_hw;
channel->emu = emu;
channel->number = channel_id;
channel->use=1;
#if 0 /* debug */
dev_dbg(emu->card->dev,
"p16v: open channel_id=%d, channel=%p, use=0x%x\n",
channel_id, channel, channel->use);
dev_dbg(emu->card->dev, "open:channel_id=%d, chip=%p, channel=%p\n",
channel_id, chip, channel);
#endif /* debug */
/* channel->interrupt = snd_p16v_pcm_channel_interrupt; */
channel->epcm = epcm;
if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
return err;
runtime->sync.id32[0] = substream->pcm->card->number;
runtime->sync.id32[1] = 'P';
runtime->sync.id32[2] = 16;
runtime->sync.id32[3] = 'V';
return 0;
}
/* open_capture callback */
static int snd_p16v_pcm_open_capture_channel(struct snd_pcm_substream *substream, int channel_id)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_emu10k1_voice *channel = &(emu->p16v_capture_voice);
struct snd_emu10k1_pcm *epcm;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
/* dev_dbg(emu->card->dev, "epcm kcalloc: %p\n", epcm); */
if (epcm == NULL)
return -ENOMEM;
epcm->emu = emu;
epcm->substream = substream;
/*
dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n",
substream->pcm->device, channel_id);
*/
runtime->private_data = epcm;
runtime->private_free = snd_p16v_pcm_free_substream;
runtime->hw = snd_p16v_capture_hw;
channel->emu = emu;
channel->number = channel_id;
channel->use=1;
#if 0 /* debug */
dev_dbg(emu->card->dev,
"p16v: open channel_id=%d, channel=%p, use=0x%x\n",
channel_id, channel, channel->use);
dev_dbg(emu->card->dev, "open:channel_id=%d, chip=%p, channel=%p\n",
channel_id, chip, channel);
#endif /* debug */
/* channel->interrupt = snd_p16v_pcm_channel_interrupt; */
channel->epcm = epcm;
if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
return err;
return 0;
}
/* close callback */
static int snd_p16v_pcm_close_playback(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
//struct snd_pcm_runtime *runtime = substream->runtime;
//struct snd_emu10k1_pcm *epcm = runtime->private_data;
emu->p16v_voices[substream->pcm->device - emu->p16v_device_offset].use = 0;
/* FIXME: maybe zero others */
return 0;
}
/* close callback */
static int snd_p16v_pcm_close_capture(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
//struct snd_pcm_runtime *runtime = substream->runtime;
//struct snd_emu10k1_pcm *epcm = runtime->private_data;
emu->p16v_capture_voice.use = 0;
/* FIXME: maybe zero others */
return 0;
}
static int snd_p16v_pcm_open_playback_front(struct snd_pcm_substream *substream)
{
return snd_p16v_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL);
}
static int snd_p16v_pcm_open_capture(struct snd_pcm_substream *substream)
{
// Only using channel 0 for now, but the card has 2 channels.
return snd_p16v_pcm_open_capture_channel(substream, 0);
}
/* hw_params callback */
static int snd_p16v_pcm_hw_params_playback(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
int result;
result = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
return result;
}
/* hw_params callback */
static int snd_p16v_pcm_hw_params_capture(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
int result;
result = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
return result;
}
/* hw_free callback */
static int snd_p16v_pcm_hw_free_playback(struct snd_pcm_substream *substream)
{
int result;
result = snd_pcm_lib_free_pages(substream);
return result;
}
/* hw_free callback */
static int snd_p16v_pcm_hw_free_capture(struct snd_pcm_substream *substream)
{
int result;
result = snd_pcm_lib_free_pages(substream);
return result;
}
/* prepare playback callback */
static int snd_p16v_pcm_prepare_playback(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int channel = substream->pcm->device - emu->p16v_device_offset;
u32 *table_base = (u32 *)(emu->p16v_buffer.area+(8*16*channel));
u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size);
int i;
u32 tmp;
#if 0 /* debug */
dev_dbg(emu->card->dev,
"prepare:channel_number=%d, rate=%d, "
"format=0x%x, channels=%d, buffer_size=%ld, "
"period_size=%ld, periods=%u, frames_to_bytes=%d\n",
channel, runtime->rate, runtime->format, runtime->channels,
runtime->buffer_size, runtime->period_size,
runtime->periods, frames_to_bytes(runtime, 1));
dev_dbg(emu->card->dev,
"dma_addr=%x, dma_area=%p, table_base=%p\n",
runtime->dma_addr, runtime->dma_area, table_base);
dev_dbg(emu->card->dev,
"dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n",
emu->p16v_buffer.addr, emu->p16v_buffer.area,
emu->p16v_buffer.bytes);
#endif /* debug */
tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel);
switch (runtime->rate) {
case 44100:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x8080);
break;
case 96000:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x4040);
break;
case 192000:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x2020);
break;
case 48000:
default:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x0000);
break;
}
/* FIXME: Check emu->buffer.size before actually writing to it. */
for(i = 0; i < runtime->periods; i++) {
table_base[i*2]=runtime->dma_addr+(i*period_size_bytes);
table_base[(i*2)+1]=period_size_bytes<<16;
}
snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_ADDR, channel, emu->p16v_buffer.addr+(8*16*channel));
snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19);
snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_PTR, channel, 0);
snd_emu10k1_ptr20_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr);
//snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes
snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, 0); // buffer size in bytes
snd_emu10k1_ptr20_write(emu, PLAYBACK_POINTER, channel, 0);
snd_emu10k1_ptr20_write(emu, 0x07, channel, 0x0);
snd_emu10k1_ptr20_write(emu, 0x08, channel, 0);
return 0;
}
/* prepare capture callback */
static int snd_p16v_pcm_prepare_capture(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int channel = substream->pcm->device - emu->p16v_device_offset;
u32 tmp;
/*
dev_dbg(emu->card->dev, "prepare capture:channel_number=%d, rate=%d, "
"format=0x%x, channels=%d, buffer_size=%ld, period_size=%ld, "
"frames_to_bytes=%d\n",
channel, runtime->rate, runtime->format, runtime->channels,
runtime->buffer_size, runtime->period_size,
frames_to_bytes(runtime, 1));
*/
tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel);
switch (runtime->rate) {
case 44100:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0800);
break;
case 96000:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0400);
break;
case 192000:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0200);
break;
case 48000:
default:
snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0000);
break;
}
/* FIXME: Check emu->buffer.size before actually writing to it. */
snd_emu10k1_ptr20_write(emu, 0x13, channel, 0);
snd_emu10k1_ptr20_write(emu, CAPTURE_DMA_ADDR, channel, runtime->dma_addr);
snd_emu10k1_ptr20_write(emu, CAPTURE_BUFFER_SIZE, channel, frames_to_bytes(runtime, runtime->buffer_size) << 16); // buffer size in bytes
snd_emu10k1_ptr20_write(emu, CAPTURE_POINTER, channel, 0);
//snd_emu10k1_ptr20_write(emu, CAPTURE_SOURCE, 0x0, 0x333300e4); /* Select MIC or Line in */
//snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) | (0x110000<<channel));
return 0;
}
static void snd_p16v_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
unsigned long flags;
unsigned int enable;
spin_lock_irqsave(&emu->emu_lock, flags);
enable = inl(emu->port + INTE2) | intrenb;
outl(enable, emu->port + INTE2);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
static void snd_p16v_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
{
unsigned long flags;
unsigned int disable;
spin_lock_irqsave(&emu->emu_lock, flags);
disable = inl(emu->port + INTE2) & (~intrenb);
outl(disable, emu->port + INTE2);
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
/* trigger_playback callback */
static int snd_p16v_pcm_trigger_playback(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime;
struct snd_emu10k1_pcm *epcm;
int channel;
int result = 0;
struct snd_pcm_substream *s;
u32 basic = 0;
u32 inte = 0;
int running = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
running=1;
break;
case SNDRV_PCM_TRIGGER_STOP:
default:
running = 0;
break;
}
snd_pcm_group_for_each_entry(s, substream) {
if (snd_pcm_substream_chip(s) != emu ||
s->stream != SNDRV_PCM_STREAM_PLAYBACK)
continue;
runtime = s->runtime;
epcm = runtime->private_data;
channel = substream->pcm->device-emu->p16v_device_offset;
/* dev_dbg(emu->card->dev, "p16v channel=%d\n", channel); */
epcm->running = running;
basic |= (0x1<<channel);
inte |= (INTE2_PLAYBACK_CH_0_LOOP<<channel);
snd_pcm_trigger_done(s, substream);
}
/* dev_dbg(emu->card->dev, "basic=0x%x, inte=0x%x\n", basic, inte); */
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_p16v_intr_enable(emu, inte);
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)| (basic));
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(basic));
snd_p16v_intr_disable(emu, inte);
break;
default:
result = -EINVAL;
break;
}
return result;
}
/* trigger_capture callback */
static int snd_p16v_pcm_trigger_capture(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_emu10k1_pcm *epcm = runtime->private_data;
int channel = 0;
int result = 0;
u32 inte = INTE2_CAPTURE_CH_0_LOOP | INTE2_CAPTURE_CH_0_HALF_LOOP;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
snd_p16v_intr_enable(emu, inte);
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)|(0x100<<channel));
epcm->running = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<<channel));
snd_p16v_intr_disable(emu, inte);
//snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel));
epcm->running = 0;
break;
default:
result = -EINVAL;
break;
}
return result;
}
/* pointer_playback callback */
static snd_pcm_uframes_t
snd_p16v_pcm_pointer_playback(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_emu10k1_pcm *epcm = runtime->private_data;
snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0;
int channel = substream->pcm->device - emu->p16v_device_offset;
if (!epcm->running)
return 0;
ptr3 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel);
ptr4 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
if (ptr3 != ptr4) ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel);
ptr2 = bytes_to_frames(runtime, ptr1);
ptr2+= (ptr4 >> 3) * runtime->period_size;
ptr=ptr2;
if (ptr >= runtime->buffer_size)
ptr -= runtime->buffer_size;
return ptr;
}
/* pointer_capture callback */
static snd_pcm_uframes_t
snd_p16v_pcm_pointer_capture(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_emu10k1_pcm *epcm = runtime->private_data;
snd_pcm_uframes_t ptr, ptr1, ptr2 = 0;
int channel = 0;
if (!epcm->running)
return 0;
ptr1 = snd_emu10k1_ptr20_read(emu, CAPTURE_POINTER, channel);
ptr2 = bytes_to_frames(runtime, ptr1);
ptr=ptr2;
if (ptr >= runtime->buffer_size) {
ptr -= runtime->buffer_size;
dev_warn(emu->card->dev, "buffer capture limited!\n");
}
/*
dev_dbg(emu->card->dev, "ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, "
"buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n",
ptr1, ptr2, ptr, (int)runtime->buffer_size,
(int)runtime->period_size, (int)runtime->frame_bits,
(int)runtime->rate);
*/
return ptr;
}
/* operators */
static struct snd_pcm_ops snd_p16v_playback_front_ops = {
.open = snd_p16v_pcm_open_playback_front,
.close = snd_p16v_pcm_close_playback,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_p16v_pcm_hw_params_playback,
.hw_free = snd_p16v_pcm_hw_free_playback,
.prepare = snd_p16v_pcm_prepare_playback,
.trigger = snd_p16v_pcm_trigger_playback,
.pointer = snd_p16v_pcm_pointer_playback,
};
static struct snd_pcm_ops snd_p16v_capture_ops = {
.open = snd_p16v_pcm_open_capture,
.close = snd_p16v_pcm_close_capture,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_p16v_pcm_hw_params_capture,
.hw_free = snd_p16v_pcm_hw_free_capture,
.prepare = snd_p16v_pcm_prepare_capture,
.trigger = snd_p16v_pcm_trigger_capture,
.pointer = snd_p16v_pcm_pointer_capture,
};
int snd_p16v_free(struct snd_emu10k1 *chip)
{
// release the data
if (chip->p16v_buffer.area) {
snd_dma_free_pages(&chip->p16v_buffer);
/*
dev_dbg(chip->card->dev, "period lables free: %p\n",
&chip->p16v_buffer);
*/
}
return 0;
}
int snd_p16v_pcm(struct snd_emu10k1 *emu, int device, struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
struct snd_pcm_substream *substream;
int err;
int capture=1;
/* dev_dbg(emu->card->dev, "snd_p16v_pcm called. device=%d\n", device); */
emu->p16v_device_offset = device;
if (rpcm)
*rpcm = NULL;
if ((err = snd_pcm_new(emu->card, "p16v", device, 1, capture, &pcm)) < 0)
return err;
pcm->private_data = emu;
// Single playback 8 channel device.
// Single capture 2 channel device.
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_p16v_playback_front_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_p16v_capture_ops);
pcm->info_flags = 0;
pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strcpy(pcm->name, "p16v");
emu->pcm_p16v = pcm;
for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
substream;
substream = substream->next) {
if ((err = snd_pcm_lib_preallocate_pages(substream,
SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(emu->pci),
((65536 - 64) * 8), ((65536 - 64) * 8))) < 0)
return err;
/*
dev_dbg(emu->card->dev,
"preallocate playback substream: err=%d\n", err);
*/
}
for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
substream;
substream = substream->next) {
if ((err = snd_pcm_lib_preallocate_pages(substream,
SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(emu->pci),
65536 - 64, 65536 - 64)) < 0)
return err;
/*
dev_dbg(emu->card->dev,
"preallocate capture substream: err=%d\n", err);
*/
}
if (rpcm)
*rpcm = pcm;
return 0;
}
static int snd_p16v_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int snd_p16v_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
int high_low = (kcontrol->private_value >> 8) & 0xff;
int reg = kcontrol->private_value & 0xff;
u32 value;
value = snd_emu10k1_ptr20_read(emu, reg, high_low);
if (high_low) {
ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */
ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */
} else {
ucontrol->value.integer.value[0] = 0xff - ((value >> 8) & 0xff); /* Left */
ucontrol->value.integer.value[1] = 0xff - ((value >> 0) & 0xff); /* Right */
}
return 0;
}
static int snd_p16v_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
int high_low = (kcontrol->private_value >> 8) & 0xff;
int reg = kcontrol->private_value & 0xff;
u32 value, oval;
oval = value = snd_emu10k1_ptr20_read(emu, reg, 0);
if (high_low == 1) {
value &= 0xffff;
value |= ((0xff - ucontrol->value.integer.value[0]) << 24) |
((0xff - ucontrol->value.integer.value[1]) << 16);
} else {
value &= 0xffff0000;
value |= ((0xff - ucontrol->value.integer.value[0]) << 8) |
((0xff - ucontrol->value.integer.value[1]) );
}
if (value != oval) {
snd_emu10k1_ptr20_write(emu, reg, 0, value);
return 1;
}
return 0;
}
static int snd_p16v_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[8] = {
"SPDIF", "I2S", "SRC48", "SRCMulti_SPDIF", "SRCMulti_I2S",
"CDIF", "FX", "AC97"
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 8;
if (uinfo->value.enumerated.item > 7)
uinfo->value.enumerated.item = 7;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_p16v_capture_source_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = emu->p16v_capture_source;
return 0;
}
static int snd_p16v_capture_source_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change = 0;
u32 mask;
u32 source;
val = ucontrol->value.enumerated.item[0] ;
if (val > 7)
return -EINVAL;
change = (emu->p16v_capture_source != val);
if (change) {
emu->p16v_capture_source = val;
source = (val << 28) | (val << 24) | (val << 20) | (val << 16);
mask = snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & 0xffff;
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, source | mask);
}
return change;
}
static int snd_p16v_capture_channel_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[4] = { "0", "1", "2", "3", };
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 4;
if (uinfo->value.enumerated.item > 3)
uinfo->value.enumerated.item = 3;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_p16v_capture_channel_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = emu->p16v_capture_channel;
return 0;
}
static int snd_p16v_capture_channel_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
unsigned int val;
int change = 0;
u32 tmp;
val = ucontrol->value.enumerated.item[0] ;
if (val > 3)
return -EINVAL;
change = (emu->p16v_capture_channel != val);
if (change) {
emu->p16v_capture_channel = val;
tmp = snd_emu10k1_ptr20_read(emu, CAPTURE_P16V_SOURCE, 0) & 0xfffc;
snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, tmp | val);
}
return change;
}
static const DECLARE_TLV_DB_SCALE(snd_p16v_db_scale1, -5175, 25, 1);
#define P16V_VOL(xname,xreg,xhl) { \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.info = snd_p16v_volume_info, \
.get = snd_p16v_volume_get, \
.put = snd_p16v_volume_put, \
.tlv = { .p = snd_p16v_db_scale1 }, \
.private_value = ((xreg) | ((xhl) << 8)) \
}
static struct snd_kcontrol_new p16v_mixer_controls[] = {
P16V_VOL("HD Analog Front Playback Volume", PLAYBACK_VOLUME_MIXER9, 0),
P16V_VOL("HD Analog Rear Playback Volume", PLAYBACK_VOLUME_MIXER10, 1),
P16V_VOL("HD Analog Center/LFE Playback Volume", PLAYBACK_VOLUME_MIXER9, 1),
P16V_VOL("HD Analog Side Playback Volume", PLAYBACK_VOLUME_MIXER10, 0),
P16V_VOL("HD SPDIF Front Playback Volume", PLAYBACK_VOLUME_MIXER7, 0),
P16V_VOL("HD SPDIF Rear Playback Volume", PLAYBACK_VOLUME_MIXER8, 1),
P16V_VOL("HD SPDIF Center/LFE Playback Volume", PLAYBACK_VOLUME_MIXER7, 1),
P16V_VOL("HD SPDIF Side Playback Volume", PLAYBACK_VOLUME_MIXER8, 0),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HD source Capture",
.info = snd_p16v_capture_source_info,
.get = snd_p16v_capture_source_get,
.put = snd_p16v_capture_source_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HD channel Capture",
.info = snd_p16v_capture_channel_info,
.get = snd_p16v_capture_channel_get,
.put = snd_p16v_capture_channel_put
},
};
int snd_p16v_mixer(struct snd_emu10k1 *emu)
{
int i, err;
struct snd_card *card = emu->card;
for (i = 0; i < ARRAY_SIZE(p16v_mixer_controls); i++) {
if ((err = snd_ctl_add(card, snd_ctl_new1(&p16v_mixer_controls[i],
emu))) < 0)
return err;
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
#define NUM_CHS 1 /* up to 4, but only first channel is used */
int snd_p16v_alloc_pm_buffer(struct snd_emu10k1 *emu)
{
emu->p16v_saved = vmalloc(NUM_CHS * 4 * 0x80);
if (! emu->p16v_saved)
return -ENOMEM;
return 0;
}
void snd_p16v_free_pm_buffer(struct snd_emu10k1 *emu)
{
vfree(emu->p16v_saved);
}
void snd_p16v_suspend(struct snd_emu10k1 *emu)
{
int i, ch;
unsigned int *val;
val = emu->p16v_saved;
for (ch = 0; ch < NUM_CHS; ch++)
for (i = 0; i < 0x80; i++, val++)
*val = snd_emu10k1_ptr20_read(emu, i, ch);
}
void snd_p16v_resume(struct snd_emu10k1 *emu)
{
int i, ch;
unsigned int *val;
val = emu->p16v_saved;
for (ch = 0; ch < NUM_CHS; ch++)
for (i = 0; i < 0x80; i++, val++)
snd_emu10k1_ptr20_write(emu, i, ch, *val);
}
#endif

299
sound/pci/emu10k1/p16v.h Normal file
View file

@ -0,0 +1,299 @@
/*
* Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver p16v chips
* Version: 0.21
*
* FEATURES currently supported:
* Output fixed at S32_LE, 2 channel to hw:0,0
* Rates: 44.1, 48, 96, 192.
*
* Changelog:
* 0.8
* Use separate card based buffer for periods table.
* 0.9
* Use 2 channel output streams instead of 8 channel.
* (8 channel output streams might be good for ASIO type output)
* Corrected speaker output, so Front -> Front etc.
* 0.10
* Fixed missed interrupts.
* 0.11
* Add Sound card model number and names.
* Add Analog volume controls.
* 0.12
* Corrected playback interrupts. Now interrupt per period, instead of half period.
* 0.13
* Use single trigger for multichannel.
* 0.14
* Mic capture now works at fixed: S32_LE, 96000Hz, Stereo.
* 0.15
* Force buffer_size / period_size == INTEGER.
* 0.16
* Update p16v.c to work with changed alsa api.
* 0.17
* Update p16v.c to work with changed alsa api. Removed boot_devs.
* 0.18
* Merging with snd-emu10k1 driver.
* 0.19
* One stereo channel at 24bit now works.
* 0.20
* Added better register defines.
* 0.21
* Split from p16v.c
*
*
* BUGS:
* Some stability problems when unloading the snd-p16v kernel module.
* --
*
* TODO:
* SPDIF out.
* Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz.
* Currently capture fixed at 48000Hz.
*
* --
* GENERAL INFO:
* Model: SB0240
* P16V Chip: CA0151-DBS
* Audigy 2 Chip: CA0102-IAT
* AC97 Codec: STAC 9721
* ADC: Philips 1361T (Stereo 24bit)
* DAC: CS4382-K (8-channel, 24bit, 192Khz)
*
* This code was initially based on code from ALSA's emu10k1x.c which is:
* Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
*
* 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
*
*/
/********************************************************************************************************/
/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */
/********************************************************************************************************/
/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE.
* The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters.
*/
/* Initially all registers from 0x00 to 0x3f have zero contents. */
#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */
/* One list entry: 4 bytes for DMA address,
* 4 bytes for period_size << 16.
* One list entry is 8 bytes long.
* One list entry for each period in the buffer.
*/
#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */
#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */
#define PLAYBACK_UNKNOWN3 0x03 /* Not used */
#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA address */
#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */
#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */
#define PLAYBACK_FIFO_END_ADDRESS 0x07 /* Playback FIFO end address */
#define PLAYBACK_FIFO_POINTER 0x08 /* Playback FIFO pointer and number of valid sound samples in cache */
#define PLAYBACK_UNKNOWN9 0x09 /* Not used */
#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */
#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */
#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */
#define CAPTURE_FIFO_POINTER 0x13 /* Capture FIFO pointer and number of valid sound samples in cache */
#define CAPTURE_P16V_VOLUME1 0x14 /* Low: Capture volume 0xXXXX3030 */
#define CAPTURE_P16V_VOLUME2 0x15 /* High:Has no effect on capture volume */
#define CAPTURE_P16V_SOURCE 0x16 /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */
/* [0:1] Capture input 0 channel select. 0 = Capture output 0.
* 1 = Capture output 1.
* 2 = Capture output 2.
* 3 = Capture output 3.
* [3:2] Capture input 1 channel select. 0 = Capture output 0.
* 1 = Capture output 1.
* 2 = Capture output 2.
* 3 = Capture output 3.
* [5:4] Capture input 2 channel select. 0 = Capture output 0.
* 1 = Capture output 1.
* 2 = Capture output 2.
* 3 = Capture output 3.
* [7:6] Capture input 3 channel select. 0 = Capture output 0.
* 1 = Capture output 1.
* 2 = Capture output 2.
* 3 = Capture output 3.
* [9:8] Playback input 0 channel select. 0 = Play output 0.
* 1 = Play output 1.
* 2 = Play output 2.
* 3 = Play output 3.
* [11:10] Playback input 1 channel select. 0 = Play output 0.
* 1 = Play output 1.
* 2 = Play output 2.
* 3 = Play output 3.
* [13:12] Playback input 2 channel select. 0 = Play output 0.
* 1 = Play output 1.
* 2 = Play output 2.
* 3 = Play output 3.
* [15:14] Playback input 3 channel select. 0 = Play output 0.
* 1 = Play output 1.
* 2 = Play output 2.
* 3 = Play output 3.
* [19:16] Playback mixer output enable. 1 bit per channel.
* [23:20] Capture mixer output enable. 1 bit per channel.
* [26:24] FX engine channel capture 0 = 0x60-0x67.
* 1 = 0x68-0x6f.
* 2 = 0x70-0x77.
* 3 = 0x78-0x7f.
* 4 = 0x80-0x87.
* 5 = 0x88-0x8f.
* 6 = 0x90-0x97.
* 7 = 0x98-0x9f.
* [31:27] Not used.
*/
/* 0x1 = capture on.
* 0x100 = capture off.
* 0x200 = capture off.
* 0x1000 = capture off.
*/
#define CAPTURE_RATE_STATUS 0x17 /* Capture sample rate. Read only */
/* [15:0] Not used.
* [18:16] Channel 0 Detected sample rate. 0 - 44.1khz
* 1 - 48 khz
* 2 - 96 khz
* 3 - 192 khz
* 7 - undefined rate.
* [19] Channel 0. 1 - Valid, 0 - Not Valid.
* [22:20] Channel 1 Detected sample rate.
* [23] Channel 1. 1 - Valid, 0 - Not Valid.
* [26:24] Channel 2 Detected sample rate.
* [27] Channel 2. 1 - Valid, 0 - Not Valid.
* [30:28] Channel 3 Detected sample rate.
* [31] Channel 3. 1 - Valid, 0 - Not Valid.
*/
/* 0x18 - 0x1f unused */
#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played. Read only */
/* 0x21 - 0x3f unused */
#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */
/* Playback (0x1<<channel_id) Don't touch high 16bits. */
/* Capture (0x100<<channel_id). not tested */
/* Start Playback [3:0] (one bit per channel)
* Start Capture [11:8] (one bit per channel)
* Record source select for channel 0 [18:16]
* Record source select for channel 1 [22:20]
* Record source select for channel 2 [26:24]
* Record source select for channel 3 [30:28]
* 0 - SPDIF channel.
* 1 - I2S channel.
* 2 - SRC48 channel.
* 3 - SRCMulti_SPDIF channel.
* 4 - SRCMulti_I2S channel.
* 5 - SPDIF channel.
* 6 - fxengine capture.
* 7 - AC97 capture.
*/
/* Default 41110000.
* Writing 0xffffffff hangs the PC.
* Writing 0xffff0000 -> 77770000 so it must be some sort of route.
* bit 0x1 starts DMA playback on channel_id 0
*/
/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */
/* 0x43,0x48 do not remember settings */
/* 0x41-45 unused */
#define WATERMARK 0x46 /* Test bit to indicate cache level usage */
/* Values it can have while playing on channel 0.
* 0000f000, 0000f004, 0000f008, 0000f00c.
* Readonly.
*/
/* 0x47-0x4f unused */
/* 0x50-0x5f Capture cache data */
#define SRCSel 0x60 /* SRCSel. Default 0x4. Bypass P16V 0x14 */
/* [0] 0 = 10K2 audio, 1 = SRC48 mixer output.
* [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output.
* [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output.
*/
/* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */
/* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */
/* SRC48 and SRCMULTI sample rate select and output select. */
/* 0xffffffff -> 0xC0000015
* 0xXXXXXXX4 = Enable Front Left/Right
* Enable PCMs
*/
/* 0x61 -> 0x6c are Volume controls */
#define PLAYBACK_VOLUME_MIXER1 0x61 /* SRC48 Low to mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER2 0x62 /* SRC48 High to mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER3 0x63 /* SRCMULTI SPDIF Low to mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER4 0x64 /* SRCMULTI SPDIF High to mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER5 0x65 /* SRCMULTI I2S Low to mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER6 0x66 /* SRCMULTI I2S High to mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER7 0x67 /* P16V Low to SRCMULTI SPDIF mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER8 0x68 /* P16V High to SRCMULTI SPDIF mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER9 0x69 /* P16V Low to SRCMULTI I2S mixer input volume control. */
/* 0xXXXX3030 = PCM0 Volume (Front).
* 0x3030XXXX = PCM1 Volume (Center)
*/
#define PLAYBACK_VOLUME_MIXER10 0x6a /* P16V High to SRCMULTI I2S mixer input volume control. */
/* 0x3030XXXX = PCM3 Volume (Rear). */
#define PLAYBACK_VOLUME_MIXER11 0x6b /* E10K2 Low to SRC48 mixer input volume control. */
#define PLAYBACK_VOLUME_MIXER12 0x6c /* E10K2 High to SRC48 mixer input volume control. */
#define SRC48_ENABLE 0x6d /* SRC48 input audio enable */
/* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */
/* [23:16] The corresponding P16V channel to SRC48 enabled if == 1.
* [31:24] The corresponding E10K2 channel to SRC48 enabled.
*/
#define SRCMULTI_ENABLE 0x6e /* SRCMulti input audio enable. Default 0xffffffff */
/* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */
/* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1.
* [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled.
* [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled.
* [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled.
*/
/* Bypass P16V 0xff00ff00
* Bitmap. 0 = Off, 1 = On.
* P16V playback outputs:
* 0xXXXXXXX1 = PCM0 Left. (Front)
* 0xXXXXXXX2 = PCM0 Right.
* 0xXXXXXXX4 = PCM1 Left. (Center/LFE)
* 0xXXXXXXX8 = PCM1 Right.
* 0xXXXXXX1X = PCM2 Left. (Unknown)
* 0xXXXXXX2X = PCM2 Right.
* 0xXXXXXX4X = PCM3 Left. (Rear)
* 0xXXXXXX8X = PCM3 Right.
*/
#define AUDIO_OUT_ENABLE 0x6f /* Default: 000100FF */
/* [3:0] Does something, but not documented. Probably capture enable.
* [7:4] Playback channels enable. not documented.
* [16] AC97 output enable if == 1
* [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f.
* 1 = SRCMulti_I2S input from SRC48 output.
* [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67.
* 1 = SRCMulti_SPDIF input from SRC48 output.
*/
/* 0xffffffff -> C00100FF */
/* 0 -> Not playback sound, irq still running */
/* 0xXXXXXX10 = PCM0 Left/Right On. (Front)
* 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE)
* 0xXXXXXX40 = PCM2 Left/Right On. (Unknown)
* 0xXXXXXX80 = PCM3 Left/Right On. (Rear)
*/
#define PLAYBACK_SPDIF_SELECT 0x70 /* Default: 12030F00 */
/* 0xffffffff -> 3FF30FFF */
/* 0x00000001 pauses stream/irq fail. */
/* All other bits do not effect playback */
#define PLAYBACK_SPDIF_SRC_SELECT 0x71 /* Default: 0000E4E4 */
/* 0xffffffff -> F33FFFFF */
/* All bits do not effect playback */
#define PLAYBACK_SPDIF_USER_DATA0 0x72 /* SPDIF out user data 0 */
#define PLAYBACK_SPDIF_USER_DATA1 0x73 /* SPDIF out user data 1 */
/* 0x74-0x75 unknown */
#define CAPTURE_SPDIF_CONTROL 0x76 /* SPDIF in control setting */
#define CAPTURE_SPDIF_STATUS 0x77 /* SPDIF in status */
#define CAPURE_SPDIF_USER_DATA0 0x78 /* SPDIF in user data 0 */
#define CAPURE_SPDIF_USER_DATA1 0x79 /* SPDIF in user data 1 */
#define CAPURE_SPDIF_USER_DATA2 0x7a /* SPDIF in user data 2 */

158
sound/pci/emu10k1/p17v.h Normal file
View file

@ -0,0 +1,158 @@
/*
* Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver p17v chips
* Version: 0.01
*
* 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
*
*/
/******************************************************************************/
/* Audigy2Value Tina (P17V) pointer-offset register set,
* accessed through the PTR20 and DATA24 registers */
/******************************************************************************/
/* 00 - 07: Not used */
#define P17V_PLAYBACK_FIFO_PTR 0x08 /* Current playback fifo pointer
* and number of sound samples in cache.
*/
/* 09 - 12: Not used */
#define P17V_CAPTURE_FIFO_PTR 0x13 /* Current capture fifo pointer
* and number of sound samples in cache.
*/
/* 14 - 17: Not used */
#define P17V_PB_CHN_SEL 0x18 /* P17v playback channel select */
#define P17V_SE_SLOT_SEL_L 0x19 /* Sound Engine slot select low */
#define P17V_SE_SLOT_SEL_H 0x1a /* Sound Engine slot select high */
/* 1b - 1f: Not used */
/* 20 - 2f: Not used */
/* 30 - 3b: Not used */
#define P17V_SPI 0x3c /* SPI interface register */
#define P17V_I2C_ADDR 0x3d /* I2C Address */
#define P17V_I2C_0 0x3e /* I2C Data */
#define P17V_I2C_1 0x3f /* I2C Data */
/* I2C values */
#define I2C_A_ADC_ADD_MASK 0x000000fe /*The address is a 7 bit address */
#define I2C_A_ADC_RW_MASK 0x00000001 /*bit mask for R/W */
#define I2C_A_ADC_TRANS_MASK 0x00000010 /*Bit mask for I2c address DAC value */
#define I2C_A_ADC_ABORT_MASK 0x00000020 /*Bit mask for I2C transaction abort flag */
#define I2C_A_ADC_LAST_MASK 0x00000040 /*Bit mask for Last word transaction */
#define I2C_A_ADC_BYTE_MASK 0x00000080 /*Bit mask for Byte Mode */
#define I2C_A_ADC_ADD 0x00000034 /*This is the Device address for ADC */
#define I2C_A_ADC_READ 0x00000001 /*To perform a read operation */
#define I2C_A_ADC_START 0x00000100 /*Start I2C transaction */
#define I2C_A_ADC_ABORT 0x00000200 /*I2C transaction abort */
#define I2C_A_ADC_LAST 0x00000400 /*I2C last transaction */
#define I2C_A_ADC_BYTE 0x00000800 /*I2C one byte mode */
#define I2C_D_ADC_REG_MASK 0xfe000000 /*ADC address register */
#define I2C_D_ADC_DAT_MASK 0x01ff0000 /*ADC data register */
#define ADC_TIMEOUT 0x00000007 /*ADC Timeout Clock Disable */
#define ADC_IFC_CTRL 0x0000000b /*ADC Interface Control */
#define ADC_MASTER 0x0000000c /*ADC Master Mode Control */
#define ADC_POWER 0x0000000d /*ADC PowerDown Control */
#define ADC_ATTEN_ADCL 0x0000000e /*ADC Attenuation ADCL */
#define ADC_ATTEN_ADCR 0x0000000f /*ADC Attenuation ADCR */
#define ADC_ALC_CTRL1 0x00000010 /*ADC ALC Control 1 */
#define ADC_ALC_CTRL2 0x00000011 /*ADC ALC Control 2 */
#define ADC_ALC_CTRL3 0x00000012 /*ADC ALC Control 3 */
#define ADC_NOISE_CTRL 0x00000013 /*ADC Noise Gate Control */
#define ADC_LIMIT_CTRL 0x00000014 /*ADC Limiter Control */
#define ADC_MUX 0x00000015 /*ADC Mux offset */
#if 0
/* FIXME: Not tested yet. */
#define ADC_GAIN_MASK 0x000000ff //Mask for ADC Gain
#define ADC_ZERODB 0x000000cf //Value to set ADC to 0dB
#define ADC_MUTE_MASK 0x000000c0 //Mask for ADC mute
#define ADC_MUTE 0x000000c0 //Value to mute ADC
#define ADC_OSR 0x00000008 //Mask for ADC oversample rate select
#define ADC_TIMEOUT_DISABLE 0x00000008 //Value and mask to disable Timeout clock
#define ADC_HPF_DISABLE 0x00000100 //Value and mask to disable High pass filter
#define ADC_TRANWIN_MASK 0x00000070 //Mask for Length of Transient Window
#endif
#define ADC_MUX_MASK 0x0000000f //Mask for ADC Mux
#define ADC_MUX_0 0x00000001 //Value to select Unknown at ADC Mux (Not used)
#define ADC_MUX_1 0x00000002 //Value to select Unknown at ADC Mux (Not used)
#define ADC_MUX_2 0x00000004 //Value to select Mic at ADC Mux
#define ADC_MUX_3 0x00000008 //Value to select Line-In at ADC Mux
#define P17V_START_AUDIO 0x40 /* Start Audio bit */
/* 41 - 47: Reserved */
#define P17V_START_CAPTURE 0x48 /* Start Capture bit */
#define P17V_CAPTURE_FIFO_BASE 0x49 /* Record FIFO base address */
#define P17V_CAPTURE_FIFO_SIZE 0x4a /* Record FIFO buffer size */
#define P17V_CAPTURE_FIFO_INDEX 0x4b /* Record FIFO capture index */
#define P17V_CAPTURE_VOL_H 0x4c /* P17v capture volume control */
#define P17V_CAPTURE_VOL_L 0x4d /* P17v capture volume control */
/* 4e - 4f: Not used */
/* 50 - 5f: Not used */
#define P17V_SRCSel 0x60 /* SRC48 and SRCMulti sample rate select
* and output select
*/
#define P17V_MIXER_AC97_10K1_VOL_L 0x61 /* 10K to Mixer_AC97 input volume control */
#define P17V_MIXER_AC97_10K1_VOL_H 0x62 /* 10K to Mixer_AC97 input volume control */
#define P17V_MIXER_AC97_P17V_VOL_L 0x63 /* P17V to Mixer_AC97 input volume control */
#define P17V_MIXER_AC97_P17V_VOL_H 0x64 /* P17V to Mixer_AC97 input volume control */
#define P17V_MIXER_AC97_SRP_REC_VOL_L 0x65 /* SRP Record to Mixer_AC97 input volume control */
#define P17V_MIXER_AC97_SRP_REC_VOL_H 0x66 /* SRP Record to Mixer_AC97 input volume control */
/* 67 - 68: Reserved */
#define P17V_MIXER_Spdif_10K1_VOL_L 0x69 /* 10K to Mixer_Spdif input volume control */
#define P17V_MIXER_Spdif_10K1_VOL_H 0x6A /* 10K to Mixer_Spdif input volume control */
#define P17V_MIXER_Spdif_P17V_VOL_L 0x6B /* P17V to Mixer_Spdif input volume control */
#define P17V_MIXER_Spdif_P17V_VOL_H 0x6C /* P17V to Mixer_Spdif input volume control */
#define P17V_MIXER_Spdif_SRP_REC_VOL_L 0x6D /* SRP Record to Mixer_Spdif input volume control */
#define P17V_MIXER_Spdif_SRP_REC_VOL_H 0x6E /* SRP Record to Mixer_Spdif input volume control */
/* 6f - 70: Reserved */
#define P17V_MIXER_I2S_10K1_VOL_L 0x71 /* 10K to Mixer_I2S input volume control */
#define P17V_MIXER_I2S_10K1_VOL_H 0x72 /* 10K to Mixer_I2S input volume control */
#define P17V_MIXER_I2S_P17V_VOL_L 0x73 /* P17V to Mixer_I2S input volume control */
#define P17V_MIXER_I2S_P17V_VOL_H 0x74 /* P17V to Mixer_I2S input volume control */
#define P17V_MIXER_I2S_SRP_REC_VOL_L 0x75 /* SRP Record to Mixer_I2S input volume control */
#define P17V_MIXER_I2S_SRP_REC_VOL_H 0x76 /* SRP Record to Mixer_I2S input volume control */
/* 77 - 78: Reserved */
#define P17V_MIXER_AC97_ENABLE 0x79 /* Mixer AC97 input audio enable */
#define P17V_MIXER_SPDIF_ENABLE 0x7A /* Mixer SPDIF input audio enable */
#define P17V_MIXER_I2S_ENABLE 0x7B /* Mixer I2S input audio enable */
#define P17V_AUDIO_OUT_ENABLE 0x7C /* Audio out enable */
#define P17V_MIXER_ATT 0x7D /* SRP Mixer Attenuation Select */
#define P17V_SRP_RECORD_SRR 0x7E /* SRP Record channel source Select */
#define P17V_SOFT_RESET_SRP_MIXER 0x7F /* SRP and mixer soft reset */
#define P17V_AC97_OUT_MASTER_VOL_L 0x80 /* AC97 Output master volume control */
#define P17V_AC97_OUT_MASTER_VOL_H 0x81 /* AC97 Output master volume control */
#define P17V_SPDIF_OUT_MASTER_VOL_L 0x82 /* SPDIF Output master volume control */
#define P17V_SPDIF_OUT_MASTER_VOL_H 0x83 /* SPDIF Output master volume control */
#define P17V_I2S_OUT_MASTER_VOL_L 0x84 /* I2S Output master volume control */
#define P17V_I2S_OUT_MASTER_VOL_H 0x85 /* I2S Output master volume control */
/* 86 - 87: Not used */
#define P17V_I2S_CHANNEL_SWAP_PHASE_INVERSE 0x88 /* I2S out mono channel swap
* and phase inverse */
#define P17V_SPDIF_CHANNEL_SWAP_PHASE_INVERSE 0x89 /* SPDIF out mono channel swap
* and phase inverse */
/* 8A: Not used */
#define P17V_SRP_P17V_ESR 0x8B /* SRP_P17V estimated sample rate and rate lock */
#define P17V_SRP_REC_ESR 0x8C /* SRP_REC estimated sample rate and rate lock */
#define P17V_SRP_BYPASS 0x8D /* srps channel bypass and srps bypass */
/* 8E - 92: Not used */
#define P17V_I2S_SRC_SEL 0x93 /* I2SIN mode sel */

96
sound/pci/emu10k1/timer.c Normal file
View file

@ -0,0 +1,96 @@
/*
* Copyright (c) by Lee Revell <rlrevell@joe-job.com>
* Clemens Ladisch <clemens@ladisch.de>
* Routines for control of EMU10K1 chips
*
* BUGS:
* --
*
* TODO:
* --
*
* 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/time.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
static int snd_emu10k1_timer_start(struct snd_timer *timer)
{
struct snd_emu10k1 *emu;
unsigned long flags;
unsigned int delay;
emu = snd_timer_chip(timer);
delay = timer->sticks - 1;
if (delay < 5 ) /* minimum time is 5 ticks */
delay = 5;
spin_lock_irqsave(&emu->reg_lock, flags);
snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB);
outw(delay & TIMER_RATE_MASK, emu->port + TIMER);
spin_unlock_irqrestore(&emu->reg_lock, flags);
return 0;
}
static int snd_emu10k1_timer_stop(struct snd_timer *timer)
{
struct snd_emu10k1 *emu;
unsigned long flags;
emu = snd_timer_chip(timer);
spin_lock_irqsave(&emu->reg_lock, flags);
snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
spin_unlock_irqrestore(&emu->reg_lock, flags);
return 0;
}
static int snd_emu10k1_timer_precise_resolution(struct snd_timer *timer,
unsigned long *num, unsigned long *den)
{
*num = 1;
*den = 48000;
return 0;
}
static struct snd_timer_hardware snd_emu10k1_timer_hw = {
.flags = SNDRV_TIMER_HW_AUTO,
.resolution = 20833, /* 1 sample @ 48KHZ = 20.833...us */
.ticks = 1024,
.start = snd_emu10k1_timer_start,
.stop = snd_emu10k1_timer_stop,
.precise_resolution = snd_emu10k1_timer_precise_resolution,
};
int snd_emu10k1_timer(struct snd_emu10k1 *emu, int device)
{
struct snd_timer *timer = NULL;
struct snd_timer_id tid;
int err;
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.card = emu->card->number;
tid.device = device;
tid.subdevice = 0;
if ((err = snd_timer_new(emu->card, "EMU10K1", &tid, &timer)) >= 0) {
strcpy(timer->name, "EMU10K1 timer");
timer->private_data = emu;
timer->hw = snd_emu10k1_timer_hw;
}
emu->timer = timer;
return err;
}

32
sound/pci/emu10k1/tina2.h Normal file
View file

@ -0,0 +1,32 @@
/*
* Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
* Driver tina2 chips
* Version: 0.1
*
* 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
*
*/
/********************************************************************************************************/
/* Audigy2 Tina2 (notebook) pointer-offset register set, accessed through the PTR2 and DATA2 registers */
/********************************************************************************************************/
#define TINA2_VOLUME 0x71 /* Attenuate playback volume to prevent distortion. */
/* The windows driver does not use this register,
* so it must use some other attenuation method.
* Without this, the output is 12dB too loud,
* resulting in distortion.
*/

168
sound/pci/emu10k1/voice.c Normal file
View file

@ -0,0 +1,168 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Creative Labs, Inc.
* Lee Revell <rlrevell@joe-job.com>
* Routines for control of EMU10K1 chips - voice manager
*
* Rewrote voice allocator for multichannel support - rlrevell 12/2004
*
* BUGS:
* --
*
* TODO:
* --
*
* 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/time.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
/* Previously the voice allocator started at 0 every time. The new voice
* allocator uses a round robin scheme. The next free voice is tracked in
* the card record and each allocation begins where the last left off. The
* hardware requires stereo interleaved voices be aligned to an even/odd
* boundary. For multichannel voice allocation we ensure than the block of
* voices does not cross the 32 voice boundary. This simplifies the
* multichannel support and ensures we can use a single write to the
* (set|clear)_loop_stop registers. Otherwise (for example) the voices would
* get out of sync when pausing/resuming a stream.
* --rlrevell
*/
static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
struct snd_emu10k1_voice **rvoice)
{
struct snd_emu10k1_voice *voice;
int i, j, k, first_voice, last_voice, skip;
*rvoice = NULL;
first_voice = last_voice = 0;
for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
/*
dev_dbg(emu->card->dev, "i %d j %d next free %d!\n",
i, j, emu->next_free_voice);
*/
i %= NUM_G;
/* stereo voices must be even/odd */
if ((number == 2) && (i % 2)) {
i++;
continue;
}
skip = 0;
for (k = 0; k < number; k++) {
voice = &emu->voices[(i+k) % NUM_G];
if (voice->use) {
skip = 1;
break;
}
}
if (!skip) {
/* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */
first_voice = i;
last_voice = (i + number) % NUM_G;
emu->next_free_voice = last_voice;
break;
}
}
if (first_voice == last_voice)
return -ENOMEM;
for (i = 0; i < number; i++) {
voice = &emu->voices[(first_voice + i) % NUM_G];
/*
dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n",
voice->number, idx-first_voice+1, number);
*/
voice->use = 1;
switch (type) {
case EMU10K1_PCM:
voice->pcm = 1;
break;
case EMU10K1_SYNTH:
voice->synth = 1;
break;
case EMU10K1_MIDI:
voice->midi = 1;
break;
case EMU10K1_EFX:
voice->efx = 1;
break;
}
}
*rvoice = &emu->voices[first_voice];
return 0;
}
int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number,
struct snd_emu10k1_voice **rvoice)
{
unsigned long flags;
int result;
if (snd_BUG_ON(!rvoice))
return -EINVAL;
if (snd_BUG_ON(!number))
return -EINVAL;
spin_lock_irqsave(&emu->voice_lock, flags);
for (;;) {
result = voice_alloc(emu, type, number, rvoice);
if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
break;
/* free a voice from synth */
if (emu->get_synth_voice) {
result = emu->get_synth_voice(emu);
if (result >= 0) {
struct snd_emu10k1_voice *pvoice = &emu->voices[result];
pvoice->interrupt = NULL;
pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
pvoice->epcm = NULL;
}
}
if (result < 0)
break;
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
return result;
}
EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
struct snd_emu10k1_voice *pvoice)
{
unsigned long flags;
if (snd_BUG_ON(!pvoice))
return -EINVAL;
spin_lock_irqsave(&emu->voice_lock, flags);
pvoice->interrupt = NULL;
pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
pvoice->epcm = NULL;
snd_emu10k1_voice_init(emu, pvoice->number);
spin_unlock_irqrestore(&emu->voice_lock, flags);
return 0;
}
EXPORT_SYMBOL(snd_emu10k1_voice_free);