Commit 27ae958c authored by Jaroslav Kysela's avatar Jaroslav Kysela

[ALSA] emu10k1 driver - add multichannel device hw:x,3 [2-8/8]

EMU10K1/EMU10K2 driver
This series of patches adds a 16 channel non interleaved PCM playback
device, hw:x,3, to the emu10k1 driver.  It also adds support for the
newly discovered per channel half loop interrupt.
Signed-Off-By: default avatarLee Revell <rlrevell@joe-job.com>
Signed-off-by: default avatarJaroslav Kysela <perex@suse.cz>
parent 3acd4a9a
......@@ -149,6 +149,11 @@ static int __devinit snd_card_emu10k1_probe(struct pci_dev *pci,
return err;
}
if ((err = snd_emu10k1_pcm_multi(emu, 3, NULL)) < 0) {
snd_card_free(card);
return err;
}
if (emu->audigy) {
if ((err = snd_emu10k1_audigy_midi(emu)) < 0) {
snd_card_free(card);
......
......@@ -291,7 +291,7 @@ get_voice(snd_emux_t *emu, snd_emux_port_t *port)
if (vp->ch < 0) {
/* allocate a voice */
emu10k1_voice_t *hwvoice;
if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 0, &hwvoice) < 0 || hwvoice == NULL)
if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL)
continue;
vp->ch = hwvoice->number;
emu->num_voices++;
......
......@@ -224,6 +224,8 @@ static void update_emu10k1_send_volume(emu10k1_t *emu, int voice, unsigned char
}
}
/* PCM stream controls */
static int snd_emu10k1_send_routing_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
......@@ -430,6 +432,190 @@ static snd_kcontrol_new_t snd_emu10k1_attn_control =
.put = snd_emu10k1_attn_put
};
/* Mutichannel PCM stream controls */
static int snd_emu10k1_efx_send_routing_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = emu->audigy ? 8 : 4;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f;
return 0;
}
static int snd_emu10k1_efx_send_routing_get(snd_kcontrol_t * kcontrol,
snd_ctl_elem_value_t * ucontrol)
{
unsigned long flags;
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
int idx;
int num_efx = emu->audigy ? 8 : 4;
int mask = emu->audigy ? 0x3f : 0x0f;
spin_lock_irqsave(&emu->reg_lock, flags);
for (idx = 0; idx < num_efx; idx++)
ucontrol->value.integer.value[idx] =
mix->send_routing[0][idx] & mask;
spin_unlock_irqrestore(&emu->reg_lock, flags);
return 0;
}
static int snd_emu10k1_efx_send_routing_put(snd_kcontrol_t * kcontrol,
snd_ctl_elem_value_t * ucontrol)
{
unsigned long flags;
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[ch];
int change = 0, idx, val;
int num_efx = emu->audigy ? 8 : 4;
int mask = emu->audigy ? 0x3f : 0x0f;
spin_lock_irqsave(&emu->reg_lock, flags);
for (idx = 0; idx < num_efx; idx++) {
val = ucontrol->value.integer.value[idx] & mask;
if (mix->send_routing[0][idx] != val) {
mix->send_routing[0][idx] = val;
change = 1;
}
}
if (change && mix->epcm->voices[ch])
update_emu10k1_fxrt(emu, mix->epcm->voices[ch]->number,
&mix->send_routing[0][0]);
spin_unlock_irqrestore(&emu->reg_lock, flags);
return change;
}
static snd_kcontrol_new_t snd_emu10k1_efx_send_routing_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Multichannel PCM Send Routing",
.count = 16,
.info = snd_emu10k1_efx_send_routing_info,
.get = snd_emu10k1_efx_send_routing_get,
.put = snd_emu10k1_efx_send_routing_put
};
static int snd_emu10k1_efx_send_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = emu->audigy ? 8 : 4;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int snd_emu10k1_efx_send_volume_get(snd_kcontrol_t * kcontrol,
snd_ctl_elem_value_t * ucontrol)
{
unsigned long flags;
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
int idx;
int num_efx = emu->audigy ? 8 : 4;
spin_lock_irqsave(&emu->reg_lock, flags);
for (idx = 0; idx < num_efx; idx++)
ucontrol->value.integer.value[idx] = mix->send_volume[0][idx];
spin_unlock_irqrestore(&emu->reg_lock, flags);
return 0;
}
static int snd_emu10k1_efx_send_volume_put(snd_kcontrol_t * kcontrol,
snd_ctl_elem_value_t * ucontrol)
{
unsigned long flags;
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[ch];
int change = 0, idx, val;
int num_efx = emu->audigy ? 8 : 4;
spin_lock_irqsave(&emu->reg_lock, flags);
for (idx = 0; idx < num_efx; idx++) {
val = ucontrol->value.integer.value[idx] & 255;
if (mix->send_volume[0][idx] != val) {
mix->send_volume[0][idx] = val;
change = 1;
}
}
if (change && mix->epcm->voices[ch])
update_emu10k1_send_volume(emu, mix->epcm->voices[ch]->number,
&mix->send_volume[0][0]);
spin_unlock_irqrestore(&emu->reg_lock, flags);
return change;
}
static snd_kcontrol_new_t snd_emu10k1_efx_send_volume_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Multichannel PCM Send Volume",
.count = 16,
.info = snd_emu10k1_efx_send_volume_info,
.get = snd_emu10k1_efx_send_volume_get,
.put = snd_emu10k1_efx_send_volume_put
};
static int snd_emu10k1_efx_attn_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 0xffff;
return 0;
}
static int snd_emu10k1_efx_attn_get(snd_kcontrol_t * kcontrol,
snd_ctl_elem_value_t * ucontrol)
{
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
unsigned long flags;
spin_lock_irqsave(&emu->reg_lock, flags);
ucontrol->value.integer.value[0] = mix->attn[0];
spin_unlock_irqrestore(&emu->reg_lock, flags);
return 0;
}
static int snd_emu10k1_efx_attn_put(snd_kcontrol_t * kcontrol,
snd_ctl_elem_value_t * ucontrol)
{
unsigned long flags;
emu10k1_t *emu = snd_kcontrol_chip(kcontrol);
int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
emu10k1_pcm_mixer_t *mix = &emu->efx_pcm_mixer[ch];
int change = 0, val;
spin_lock_irqsave(&emu->reg_lock, flags);
val = ucontrol->value.integer.value[0] & 0xffff;
if (mix->attn[0] != val) {
mix->attn[0] = val;
change = 1;
}
if (change && mix->epcm->voices[ch])
snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[ch]->number, mix->attn[0]);
spin_unlock_irqrestore(&emu->reg_lock, flags);
return change;
}
static snd_kcontrol_new_t snd_emu10k1_efx_attn_control =
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "Multichannel PCM Volume",
.count = 16,
.info = snd_emu10k1_efx_attn_info,
.get = snd_emu10k1_efx_attn_get,
.put = snd_emu10k1_efx_attn_put
};
static int snd_emu10k1_shared_spdif_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
......@@ -663,7 +849,22 @@ int __devinit snd_emu10k1_mixer(emu10k1_t *emu)
if ((err = snd_ctl_add(card, kctl)))
return err;
/* intiailize the routing and volume table for each pcm playback stream */
if ((kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu)) == NULL)
return -ENOMEM;
if ((err = snd_ctl_add(card, kctl)))
return err;
if ((kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu)) == NULL)
return -ENOMEM;
if ((err = snd_ctl_add(card, kctl)))
return err;
if ((kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu)) == NULL)
return -ENOMEM;
if ((err = snd_ctl_add(card, kctl)))
return err;
/* initialize the routing and volume table for each pcm playback stream */
for (pcm = 0; pcm < 32; pcm++) {
emu10k1_pcm_mixer_t *mix;
int v;
......@@ -683,6 +884,28 @@ int __devinit snd_emu10k1_mixer(emu10k1_t *emu)
mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
}
/* initialize the routing and volume table for the multichannel playback stream */
for (pcm = 0; pcm < NUM_EFX_PLAYBACK; pcm++) {
emu10k1_pcm_mixer_t *mix;
int v;
mix = &emu->efx_pcm_mixer[pcm];
mix->epcm = NULL;
mix->send_routing[0][0] = pcm;
mix->send_routing[0][1] = (pcm == 0) ? 1 : 0;
for (v = 0; v < 2; v++)
mix->send_routing[0][2+v] = 13+v;
if (emu->audigy)
for (v = 0; v < 4; v++)
mix->send_routing[0][4+v] = 60+v;
memset(&mix->send_volume, 0, sizeof(mix->send_volume));
mix->send_volume[0][0] = 255;
mix->attn[0] = 0xffff;
}
if (! emu->APS) { /* FIXME: APS has these controls? */
/* sb live! and audigy */
if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu)) == NULL)
......
This diff is collapsed.
......@@ -170,6 +170,63 @@ void snd_emu10k1_voice_intr_ack(emu10k1_t *emu, unsigned int voicenum)
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
void snd_emu10k1_voice_half_loop_intr_enable(emu10k1_t *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(emu10k1_t *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(emu10k1_t *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(emu10k1_t *emu, unsigned int voicenum)
{
unsigned long flags;
......
......@@ -73,6 +73,21 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id, struct pt_regs *regs)
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;
......
/*
* Copyright (c) by Jaroslav Kysela <perex@suse.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:
* --
*
......@@ -30,25 +33,70 @@
#include <sound/core.h>
#include <sound/emu10k1.h>
static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
/* 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(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
{
emu10k1_voice_t *voice, *voice2;
int idx;
emu10k1_voice_t *voice;
int i, j, k, first_voice, last_voice, skip;
*rvoice = NULL;
for (idx = 0; idx < 64; idx += pair ? 2 : 1) {
voice = &emu->voices[idx];
voice2 = pair ? &emu->voices[idx+1] : NULL;
if (voice->use || (voice2 && voice2->use))
first_voice = last_voice = 0;
for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
// printk("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;
}
/* make sure the block of voices does not cross the 32 voice boundary */
//if (((i % 32) + number) > 32)
// continue;
skip = 0;
for (k = 0; k < number; k++) {
voice = &emu->voices[(i+k) % NUM_G];
if (voice->use) {
printk("voice %d: use=1!\n", i+k);
skip = 1;
}
}
if (!skip) {
// printk("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) {
printk("BUG (or not enough voices), number %d, next free %d!\n",
number,
emu->next_free_voice);
return -ENOMEM;
}
for (i=0; i < number; i++) {
voice = &emu->voices[(first_voice + i) % NUM_G];
// printk("voice alloc - %i, %i of %i\n", voice->number, idx-first_voice+1, number);
voice->use = 1;
if (voice2)
voice2->use = 1;
switch (type) {
case EMU10K1_PCM:
voice->pcm = 1;
if (voice2)
voice2->pcm = 1;
break;
case EMU10K1_SYNTH:
voice->synth = 1;
......@@ -56,26 +104,27 @@ static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu1
case EMU10K1_MIDI:
voice->midi = 1;
break;
case EMU10K1_EFX:
voice->efx = 1;
break;
}
// printk("voice alloc - %i, pair = %i\n", voice->number, pair);
*rvoice = voice;
return 0;
}
return -ENOMEM;
*rvoice = &emu->voices[first_voice];
return 0;
}
int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
{
unsigned long flags;
int result;
snd_assert(rvoice != NULL, return -EINVAL);
snd_assert(!pair || type == EMU10K1_PCM, return -EINVAL);
snd_assert(number, return -EINVAL);
spin_lock_irqsave(&emu->voice_lock, flags);
for (;;) {
result = voice_alloc(emu, type, pair, rvoice);
if (result == 0 || type != EMU10K1_PCM)
result = voice_alloc(emu, type, number, rvoice);
if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
break;
/* free a voice from synth */
......@@ -84,7 +133,7 @@ int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair,
if (result >= 0) {
emu10k1_voice_t *pvoice = &emu->voices[result];
pvoice->interrupt = NULL;
pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
pvoice->epcm = NULL;
}
}
......@@ -103,7 +152,7 @@ int snd_emu10k1_voice_free(emu10k1_t *emu, emu10k1_voice_t *pvoice)
snd_assert(pvoice != NULL, return -EINVAL);
spin_lock_irqsave(&emu->voice_lock, flags);
pvoice->interrupt = NULL;
pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment