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)
......
......@@ -84,9 +84,26 @@ static void snd_emu10k1_pcm_efx_interrupt(emu10k1_t *emu, unsigned int status)
snd_pcm_period_elapsed(emu->pcm_capture_efx_substream);
}
static snd_pcm_uframes_t snd_emu10k1_efx_playback_pointer(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
emu10k1_pcm_t *epcm = runtime->private_data;
unsigned int ptr;
if (!epcm->running)
return 0;
ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
ptr += runtime->buffer_size;
ptr -= epcm->ccca_start_addr;
ptr %= runtime->buffer_size;
return ptr;
}
static int snd_emu10k1_pcm_channel_alloc(emu10k1_pcm_t * epcm, int voices)
{
int err;
int err, i;
if (epcm->voices[1] != NULL && voices < 2) {
snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
......@@ -102,23 +119,31 @@ static int snd_emu10k1_pcm_channel_alloc(emu10k1_pcm_t * epcm, int voices)
epcm->voices[0] = NULL;
}
}
err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, voices > 1, &epcm->voices[0]);
err = snd_emu10k1_voice_alloc(epcm->emu,
epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
voices,
&epcm->voices[0]);
if (err < 0)
return err;
epcm->voices[0]->epcm = epcm;
if (voices > 1) {
epcm->voices[1] = &epcm->emu->voices[epcm->voices[0]->number + 1];
epcm->voices[1]->epcm = epcm;
for (i = 1; i < voices; i++) {
epcm->voices[i] = &epcm->emu->voices[epcm->voices[0]->number + i];
epcm->voices[i]->epcm = epcm;
}
}
if (epcm->extra == NULL) {
err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, 0, &epcm->extra);
err = snd_emu10k1_voice_alloc(epcm->emu,
epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
1,
&epcm->extra);
if (err < 0) {
// printk("pcm_channel_alloc: failed extra: voices=%d, frame=%d\n", voices, frame);
snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
epcm->voices[0] = NULL;
if (epcm->voices[1])
snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
epcm->voices[1] = NULL;
for (i = 0; i < voices; i++) {
snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
epcm->voices[i] = NULL;
}
return err;
}
epcm->extra->epcm = epcm;
......@@ -234,7 +259,7 @@ static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu,
{
snd_pcm_substream_t *substream = evoice->epcm->substream;
snd_pcm_runtime_t *runtime = substream->runtime;
emu10k1_pcm_mixer_t *mix = &emu->pcm_mixer[substream->number];
emu10k1_pcm_mixer_t *mix;
unsigned int silent_page, tmp;
int voice, stereo, w_16;
unsigned char attn, send_amount[8];
......@@ -243,6 +268,11 @@ static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu,
unsigned int pitch_target;
voice = evoice->number;
if (evoice->epcm->type == PLAYBACK_EFX)
mix = &emu->efx_pcm_mixer[voice - evoice->epcm->voices[0]->number];
else
mix = &emu->pcm_mixer[substream->number];
stereo = runtime->channels == 2;
w_16 = snd_pcm_format_width(runtime->format) == 16;
......@@ -273,10 +303,11 @@ static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu,
memcpy(send_amount, &mix->send_volume[tmp][0], 8);
}
if (master) {
unsigned int ccis = stereo ? 28 : 30;
if (w_16)
ccis *= 2;
if (master) {
evoice->epcm->ccca_start_addr = start_addr + ccis;
if (extra) {
start_addr += ccis;
......@@ -310,7 +341,12 @@ static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu,
snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24));
snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24));
pitch_target = emu10k1_calc_pitch_target(runtime->rate);
snd_emu10k1_ptr_write(emu, CCCA, voice, evoice->epcm->ccca_start_addr |
if (extra)
snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr |
emu10k1_select_interprom(pitch_target) |
(w_16 ? 0 : CCCA_8BITSELECT));
else
snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) |
emu10k1_select_interprom(pitch_target) |
(w_16 ? 0 : CCCA_8BITSELECT));
// Clear filter delay memory
......@@ -398,6 +434,35 @@ static int snd_emu10k1_playback_hw_free(snd_pcm_substream_t * substream)
return 0;
}
static int snd_emu10k1_efx_playback_hw_free(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
emu10k1_pcm_t *epcm;
int i;
if (runtime->private_data == NULL)
return 0;
epcm = runtime->private_data;
if (epcm->extra) {
snd_emu10k1_voice_free(epcm->emu, epcm->extra);
epcm->extra = NULL;
}
for (i=0; i < NUM_EFX_PLAYBACK; i++) {
if (epcm->voices[i]) {
snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
epcm->voices[i] = NULL;
}
}
if (epcm->memblk) {
snd_emu10k1_free_pages(emu, epcm->memblk);
epcm->memblk = NULL;
epcm->start_addr = 0;
}
snd_pcm_lib_free_pages(substream);
return 0;
}
static int snd_emu10k1_playback_prepare(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
......@@ -421,6 +486,59 @@ static int snd_emu10k1_playback_prepare(snd_pcm_substream_t * substream)
return 0;
}
static int snd_emu10k1_efx_playback_prepare(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
emu10k1_pcm_t *epcm = runtime->private_data;
unsigned int start_addr, end_addr;
start_addr = epcm->start_addr;
end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
/*
* the kX driver leaves some space between voices
*/
unsigned int channel_size;
int i;
channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK;
snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
start_addr, start_addr + (channel_size / 2));
/* only difference with the master voice is we use it for the pointer */
snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
start_addr, start_addr + channel_size);
start_addr += channel_size;
for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i],
start_addr, start_addr+channel_size);
start_addr += channel_size;
}
return 0;
}
static snd_pcm_hardware_t snd_emu10k1_efx_playback =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = NUM_EFX_PLAYBACK,
.channels_max = NUM_EFX_PLAYBACK,
.buffer_bytes_max = (64*1024),
.period_bytes_min = 64,
.period_bytes_max = (64*1024),
.periods_min = 2,
.periods_max = 2,
.fifo_size = 0,
};
static int snd_emu10k1_capture_hw_params(snd_pcm_substream_t * substream,
snd_pcm_hw_params_t * hw_params)
{
......@@ -439,6 +557,7 @@ static int snd_emu10k1_capture_prepare(snd_pcm_substream_t * substream)
emu10k1_pcm_t *epcm = runtime->private_data;
int idx;
/* zeroing the buffer size will stop capture */
snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
switch (epcm->type) {
case CAPTURE_AC97ADC:
......@@ -488,7 +607,7 @@ static void snd_emu10k1_playback_invalidate_cache(emu10k1_t *emu, emu10k1_voice_
runtime = evoice->epcm->substream->runtime;
voice = evoice->number;
sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080;
if (runtime->channels > 1) {
if (runtime->channels == 2) {
ccis = 28;
cs = 4;
} else {
......@@ -499,10 +618,11 @@ static void snd_emu10k1_playback_invalidate_cache(emu10k1_t *emu, emu10k1_voice_
ccis *= 2;
for (i = 0; i < cs; i++)
snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample);
// reset cache
snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0);
snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra);
if (runtime->channels > 1) {
if (runtime->channels == 2) {
snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0);
snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra);
}
......@@ -523,7 +643,10 @@ static void snd_emu10k1_playback_prepare_voice(emu10k1_t *emu, emu10k1_voice_t *
substream = evoice->epcm->substream;
runtime = substream->runtime;
voice = evoice->number;
mix = &emu->pcm_mixer[substream->number];
mix = evoice->epcm->type == PLAYBACK_EFX
? &emu->efx_pcm_mixer[voice - evoice->epcm->voices[0]->number]
: &emu->pcm_mixer[substream->number];
attn = extra ? 0 : 0x00ff;
tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0;
......@@ -549,7 +672,7 @@ static void snd_emu10k1_playback_trigger_voice(emu10k1_t *emu, emu10k1_voice_t *
pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8;
pitch_target = emu10k1_calc_pitch_target(runtime->rate);
snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target);
if (master)
if (master || evoice->epcm->type == PLAYBACK_EFX)
snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target);
snd_emu10k1_ptr_write(emu, IP, voice, pitch);
if (extra)
......@@ -579,7 +702,6 @@ static int snd_emu10k1_playback_trigger(snd_pcm_substream_t * substream,
snd_pcm_runtime_t *runtime = substream->runtime;
emu10k1_pcm_t *epcm = runtime->private_data;
int result = 0;
// printk("trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n", (int)emu, cmd, substream->ops->pointer(substream));
spin_lock(&emu->reg_lock);
switch (cmd) {
......@@ -619,10 +741,10 @@ static int snd_emu10k1_capture_trigger(snd_pcm_substream_t * substream,
emu10k1_pcm_t *epcm = runtime->private_data;
int result = 0;
// printk("trigger - emu10k1 = %p, cmd = %i, pointer = %i\n", emu, cmd, substream->ops->pointer(substream));
spin_lock(&emu->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
// hmm this should cause full and half full interrupt to be raised?
outl(epcm->capture_ipr, emu->port + IPR);
snd_emu10k1_intr_enable(emu, epcm->capture_inte);
// printk("adccr = 0x%x, adcbs = 0x%x\n", epcm->adccr, epcm->adcbs);
......@@ -698,6 +820,56 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(snd_pcm_substream_t * subs
return ptr;
}
static int snd_emu10k1_efx_playback_trigger(snd_pcm_substream_t * substream,
int cmd)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
emu10k1_pcm_t *epcm = runtime->private_data;
int i = 0;
int result = 0;
spin_lock(&emu->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
// prepare voices
for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i]);
}
snd_emu10k1_playback_invalidate_cache(emu, epcm->extra);
/* follow thru */
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, 0);
snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1);
for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, 0);
}
snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0);
snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0);
}
epcm->running = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
epcm->running = 0;
for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
}
snd_emu10k1_playback_stop_voice(emu, epcm->extra);
break;
default:
result = -EINVAL;
break;
}
spin_unlock(&emu->reg_lock);
return result;
}
static snd_pcm_uframes_t snd_emu10k1_capture_pointer(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
......@@ -786,6 +958,13 @@ static void snd_emu10k1_pcm_mixer_notify(emu10k1_t *emu, int idx, int activate)
snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_attn, idx, activate);
}
static void snd_emu10k1_pcm_efx_mixer_notify(emu10k1_t *emu, int idx, int activate)
{
snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_routing, idx, activate);
snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_volume, idx, activate);
snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate);
}
static void snd_emu10k1_pcm_free_substream(snd_pcm_runtime_t *runtime)
{
emu10k1_pcm_t *epcm = runtime->private_data;
......@@ -793,6 +972,53 @@ static void snd_emu10k1_pcm_free_substream(snd_pcm_runtime_t *runtime)
kfree(epcm);
}
static int snd_emu10k1_efx_playback_close(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
emu10k1_pcm_mixer_t *mix;
int i;
for (i=0; i < NUM_EFX_PLAYBACK; i++) {
mix = &emu->efx_pcm_mixer[i];
mix->epcm = NULL;
snd_emu10k1_pcm_efx_mixer_notify(emu, i, 0);
}
return 0;
}
static int snd_emu10k1_efx_playback_open(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
emu10k1_pcm_t *epcm;
emu10k1_pcm_mixer_t *mix;
snd_pcm_runtime_t *runtime = substream->runtime;
int i;
epcm = kcalloc(1, sizeof(*epcm), GFP_KERNEL);
if (epcm == NULL)
return -ENOMEM;
epcm->emu = emu;
epcm->type = PLAYBACK_EFX;
epcm->substream = substream;
emu->pcm_playback_efx_substream = substream;
runtime->private_data = epcm;
runtime->private_free = snd_emu10k1_pcm_free_substream;
runtime->hw = snd_emu10k1_efx_playback;
for (i=0; i < NUM_EFX_PLAYBACK; i++) {
mix = &emu->efx_pcm_mixer[i];
mix->send_routing[0][0] = i;
memset(&mix->send_volume, 0, sizeof(mix->send_volume));
mix->send_volume[0][0] = 255;
mix->attn[0] = 0xffff;
mix->epcm = epcm;
snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1);
}
return 0;
}
static int snd_emu10k1_playback_open(snd_pcm_substream_t * substream)
{
emu10k1_t *emu = snd_pcm_substream_chip(substream);
......@@ -987,6 +1213,19 @@ static snd_pcm_ops_t snd_emu10k1_capture_ops = {
.pointer = snd_emu10k1_capture_pointer,
};
/* EFX playback */
static snd_pcm_ops_t snd_emu10k1_efx_playback_ops = {
.open = snd_emu10k1_efx_playback_open,
.close = snd_emu10k1_efx_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_emu10k1_playback_hw_params,
.hw_free = snd_emu10k1_efx_playback_hw_free,
.prepare = snd_emu10k1_efx_playback_prepare,
.trigger = snd_emu10k1_efx_playback_trigger,
.pointer = snd_emu10k1_efx_playback_pointer,
.page = snd_pcm_sgbuf_ops_page,
};
static void snd_emu10k1_pcm_free(snd_pcm_t *pcm)
{
emu10k1_t *emu = pcm->private_data;
......@@ -1030,6 +1269,39 @@ int __devinit snd_emu10k1_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
return 0;
}
int __devinit snd_emu10k1_pcm_multi(emu10k1_t * emu, int device, snd_pcm_t ** rpcm)
{
snd_pcm_t *pcm;
snd_pcm_substream_t *substream;
int err;
if (rpcm)
*rpcm = NULL;
if ((err = snd_pcm_new(emu->card, "emu10k1", device, 1, 0, &pcm)) < 0)
return err;
pcm->private_data = emu;
pcm->private_free = snd_emu10k1_pcm_free;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_efx_playback_ops);
pcm->info_flags = 0;
pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strcpy(pcm->name, "EMU10K1 multichannel EFX");
emu->pcm = 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_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0)
return err;
if (rpcm)
*rpcm = pcm;
return 0;
}
static snd_pcm_ops_t snd_emu10k1_capture_mic_ops = {
.open = snd_emu10k1_capture_mic_open,
.close = snd_emu10k1_capture_mic_close,
......
......@@ -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