📄 emupcm.c
字号:
/* * Copyright (c) by Jaroslav Kysela <perex@suse.cz> * Creative Labs, Inc. * Routines for control of EMU10K1 chips / PCM routines * * 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 <sound/driver.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/init.h>#include <sound/core.h>#include <sound/emu10k1.h>static void snd_emu10k1_pcm_interrupt(emu10k1_t *emu, emu10k1_voice_t *voice){ emu10k1_pcm_t *epcm; if ((epcm = voice->epcm) == NULL) return; if (epcm->substream == NULL) return;#if 0 printk("IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n", epcm->substream->runtime->hw->pointer(emu, epcm->substream), snd_pcm_lib_period_bytes(epcm->substream), snd_pcm_lib_buffer_bytes(epcm->substream));#endif snd_pcm_period_elapsed(epcm->substream);}static void snd_emu10k1_pcm_ac97adc_interrupt(emu10k1_t *emu, unsigned int status){#if 0 if (status & IPR_ADCBUFHALFFULL) { if (emu->pcm_capture_substream->runtime->mode == SNDRV_PCM_MODE_FRAME) return; }#endif snd_pcm_period_elapsed(emu->pcm_capture_substream);}static void snd_emu10k1_pcm_ac97mic_interrupt(emu10k1_t *emu, unsigned int status){#if 0 if (status & IPR_MICBUFHALFFULL) { if (emu->pcm_capture_mic_substream->runtime->mode == SNDRV_PCM_MODE_FRAME) return; }#endif snd_pcm_period_elapsed(emu->pcm_capture_mic_substream);}static void snd_emu10k1_pcm_efx_interrupt(emu10k1_t *emu, unsigned int status){#if 0 if (status & IPR_EFXBUFHALFFULL) { if (emu->pcm_capture_efx_substream->runtime->mode == SNDRV_PCM_MODE_FRAME) return; }#endif snd_pcm_period_elapsed(emu->pcm_capture_efx_substream);}static int snd_emu10k1_pcm_channel_alloc(emu10k1_pcm_t * epcm, int voices){ int err; if (epcm->voices[1] != NULL && voices < 2) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]); epcm->voices[1] = NULL; } if (voices == 1 && epcm->voices[0] != NULL) return 0; /* already allocated */ if (voices == 2 && epcm->voices[0] != NULL && epcm->voices[1] != NULL) return 0; if (voices > 1) { if (epcm->voices[0] != NULL && epcm->voices[1] == NULL) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]); epcm->voices[0] = NULL; } } err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, voices > 1, &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; } if (epcm->extra == NULL) { err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, 0, &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; return err; } epcm->extra->epcm = epcm; epcm->extra->interrupt = snd_emu10k1_pcm_interrupt; } return 0;}static unsigned int capture_period_sizes[31] = { 384, 448, 512, 640, 384*2, 448*2, 512*2, 640*2, 384*4, 448*4, 512*4, 640*4, 384*8, 448*8, 512*8, 640*8, 384*16, 448*16, 512*16, 640*16, 384*32, 448*32, 512*32, 640*32, 384*64, 448*64, 512*64, 640*64, 384*128,448*128,512*128};static snd_pcm_hw_constraint_list_t hw_constraints_capture_period_sizes = { .count = 31, .list = capture_period_sizes, .mask = 0};static unsigned int capture_rates[8] = { 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000};static snd_pcm_hw_constraint_list_t hw_constraints_capture_rates = { .count = 8, .list = capture_rates, .mask = 0};static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate){ switch (rate) { case 8000: return ADCCR_SAMPLERATE_8; case 11025: return ADCCR_SAMPLERATE_11; case 16000: return ADCCR_SAMPLERATE_16; case 22050: return ADCCR_SAMPLERATE_22; case 24000: return ADCCR_SAMPLERATE_24; case 32000: return ADCCR_SAMPLERATE_32; case 44100: return ADCCR_SAMPLERATE_44; case 48000: return ADCCR_SAMPLERATE_48; default: snd_BUG(); return ADCCR_SAMPLERATE_8; }}static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate){ switch (rate) { case 8000: return A_ADCCR_SAMPLERATE_8; case 11025: return A_ADCCR_SAMPLERATE_11; case 12000: return A_ADCCR_SAMPLERATE_12; /* really supported? */ case 16000: return ADCCR_SAMPLERATE_16; case 22050: return ADCCR_SAMPLERATE_22; case 24000: return ADCCR_SAMPLERATE_24; case 32000: return ADCCR_SAMPLERATE_32; case 44100: return ADCCR_SAMPLERATE_44; case 48000: return ADCCR_SAMPLERATE_48; default: snd_BUG(); return A_ADCCR_SAMPLERATE_8; }}static unsigned int emu10k1_calc_pitch_target(unsigned int rate){ unsigned int pitch_target; pitch_target = (rate << 8) / 375; pitch_target = (pitch_target >> 1) + (pitch_target & 1); return pitch_target;}#define PITCH_48000 0x00004000#define PITCH_96000 0x00008000#define PITCH_85000 0x00007155#define PITCH_80726 0x00006ba2#define PITCH_67882 0x00005a82#define PITCH_57081 0x00004c1cstatic unsigned int emu10k1_select_interprom(unsigned int pitch_target){ if (pitch_target == PITCH_48000) return CCCA_INTERPROM_0; else if (pitch_target < PITCH_48000) return CCCA_INTERPROM_1; else if (pitch_target >= PITCH_96000) return CCCA_INTERPROM_0; else if (pitch_target >= PITCH_85000) return CCCA_INTERPROM_6; else if (pitch_target >= PITCH_80726) return CCCA_INTERPROM_5; else if (pitch_target >= PITCH_67882) return CCCA_INTERPROM_4; else if (pitch_target >= PITCH_57081) return CCCA_INTERPROM_3; else return CCCA_INTERPROM_2;}static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu, int master, int extra, emu10k1_voice_t *evoice, unsigned int start_addr, unsigned int end_addr){ 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]; unsigned int silent_page, tmp; int voice, stereo, w_16; unsigned char attn, send_amount[8]; unsigned char send_routing[8]; unsigned long flags; unsigned int pitch_target; voice = evoice->number; stereo = runtime->channels == 2; w_16 = snd_pcm_format_width(runtime->format) == 16; if (!extra && stereo) { start_addr >>= 1; end_addr >>= 1; } if (w_16) { start_addr >>= 1; end_addr >>= 1; } spin_lock_irqsave(&emu->reg_lock, flags); /* volume parameters */ if (extra) { attn = 0; memset(send_routing, 0, sizeof(send_routing)); send_routing[0] = 0; send_routing[1] = 1; send_routing[2] = 2; send_routing[3] = 3; memset(send_amount, 0, sizeof(send_amount)); } else { /* mono, left, right (master voice = left) */ tmp = stereo ? (master ? 1 : 2) : 0; memcpy(send_routing, &mix->send_routing[tmp][0], 8); memcpy(send_amount, &mix->send_volume[tmp][0], 8); } if (master) { unsigned int ccis = stereo ? 28 : 30; if (w_16) ccis *= 2; evoice->epcm->ccca_start_addr = start_addr + ccis; if (extra) { start_addr += ccis; end_addr += ccis; } if (stereo && !extra) { snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); snd_emu10k1_ptr_write(emu, CPF, (voice + 1), CPF_STEREO_MASK); } else { snd_emu10k1_ptr_write(emu, CPF, voice, 0); } } // setup routing if (emu->audigy) { snd_emu10k1_ptr_write(emu, A_FXRT1, voice, snd_emu10k1_compose_audigy_fxrt1(send_routing)); snd_emu10k1_ptr_write(emu, A_FXRT2, voice, snd_emu10k1_compose_audigy_fxrt2(send_routing)); snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, ((unsigned int)send_amount[4] << 24) | ((unsigned int)send_amount[5] << 16) | ((unsigned int)send_amount[6] << 8) | (unsigned int)send_amount[7]); } else snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); // Stop CA // Assumption that PT is already 0 so no harm overwriting snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); 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 | emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); // Clear filter delay memory snd_emu10k1_ptr_write(emu, Z1, voice, 0); snd_emu10k1_ptr_write(emu, Z2, voice, 0); // invalidate maps silent_page = ((unsigned int)emu->silent_page.addr << 1) | MAP_PTI_MASK; snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page); snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page); // modulation envelope snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff); snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff); snd_emu10k1_ptr_write(emu, ATKHLDM, voice, 0); snd_emu10k1_ptr_write(emu, DCYSUSM, voice, 0x007f); snd_emu10k1_ptr_write(emu, LFOVAL1, voice, 0x8000); snd_emu10k1_ptr_write(emu, LFOVAL2, voice, 0x8000); snd_emu10k1_ptr_write(emu, FMMOD, voice, 0); snd_emu10k1_ptr_write(emu, TREMFRQ, voice, 0); snd_emu10k1_ptr_write(emu, FM2FRQ2, voice, 0); snd_emu10k1_ptr_write(emu, ENVVAL, voice, 0x8000); // volume envelope snd_emu10k1_ptr_write(emu, ATKHLDV, voice, 0x7f7f); snd_emu10k1_ptr_write(emu, ENVVOL, voice, 0x0000); // filter envelope snd_emu10k1_ptr_write(emu, PEFE_FILTERAMOUNT, voice, 0x7f); // pitch envelope snd_emu10k1_ptr_write(emu, PEFE_PITCHAMOUNT, voice, 0); spin_unlock_irqrestore(&emu->reg_lock, flags);}static int snd_emu10k1_playback_hw_params(snd_pcm_substream_t * substream, snd_pcm_hw_params_t * hw_params){ emu10k1_t *emu = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; emu10k1_pcm_t *epcm = runtime->private_data; int err; if ((err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params))) < 0) return err; if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) return err; if (err > 0) { /* change */ snd_util_memblk_t *memblk; if (epcm->memblk != NULL) snd_emu10k1_free_pages(emu, epcm->memblk); memblk = snd_emu10k1_alloc_pages(emu, substream); if ((epcm->memblk = memblk) == NULL || ((emu10k1_memblk_t *)memblk)->mapped_page < 0) { epcm->start_addr = 0; return -ENOMEM; } epcm->start_addr = ((emu10k1_memblk_t *)memblk)->mapped_page << PAGE_SHIFT; } return 0;}static int snd_emu10k1_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; 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; } if (epcm->voices[1]) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]); epcm->voices[1] = NULL; } if (epcm->voices[0]) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]); epcm->voices[0] = 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); 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 = snd_pcm_lib_period_bytes(substream); if (runtime->channels == 2) end_addr >>= 1; end_addr += start_addr; snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, start_addr, end_addr); end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream); snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], start_addr, end_addr); if (epcm->voices[1]) snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], start_addr, end_addr); return 0;}static int snd_emu10k1_capture_hw_params(snd_pcm_substream_t * substream, snd_pcm_hw_params_t * hw_params){ return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));}static int snd_emu10k1_capture_hw_free(snd_pcm_substream_t * substream){ return snd_pcm_lib_free_pages(substream);}static int snd_emu10k1_capture_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; int idx; snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0); switch (epcm->type) { case CAPTURE_AC97ADC: snd_emu10k1_ptr_write(emu, ADCCR, 0, 0); break; case CAPTURE_EFX: if (emu->audigy) { snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0); snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0); } else snd_emu10k1_ptr_write(emu, FXWC, 0, 0); break; default: break; } snd_emu10k1_ptr_write(emu, epcm->capture_ba_reg, 0, runtime->dma_addr); epcm->capture_bufsize = snd_pcm_lib_buffer_bytes(substream); epcm->capture_bs_val = 0; for (idx = 0; idx < 31; idx++) { if (capture_period_sizes[idx] == epcm->capture_bufsize) { epcm->capture_bs_val = idx + 1; break; } } if (epcm->capture_bs_val == 0) { snd_BUG();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -