📄 ymfpci_main.c
字号:
/* * Copyright (c) by Jaroslav Kysela <perex@suse.cz> * Routines for control of YMF724/740/744/754 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 <sound/driver.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/pci.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/vmalloc.h>#include <sound/core.h>#include <sound/control.h>#include <sound/info.h>#include <sound/ymfpci.h>#include <sound/asoundef.h>#include <sound/mpu401.h>#include <asm/io.h>/* * constants *//* * common I/O routines */static void snd_ymfpci_irq_wait(ymfpci_t *chip);static inline u8 snd_ymfpci_readb(ymfpci_t *chip, u32 offset){ return readb(chip->reg_area_virt + offset);}static inline void snd_ymfpci_writeb(ymfpci_t *chip, u32 offset, u8 val){ writeb(val, chip->reg_area_virt + offset);}static inline u16 snd_ymfpci_readw(ymfpci_t *chip, u32 offset){ return readw(chip->reg_area_virt + offset);}static inline void snd_ymfpci_writew(ymfpci_t *chip, u32 offset, u16 val){ writew(val, chip->reg_area_virt + offset);}static inline u32 snd_ymfpci_readl(ymfpci_t *chip, u32 offset){ return readl(chip->reg_area_virt + offset);}static inline void snd_ymfpci_writel(ymfpci_t *chip, u32 offset, u32 val){ writel(val, chip->reg_area_virt + offset);}static int snd_ymfpci_codec_ready(ymfpci_t *chip, int secondary){ unsigned long end_time; u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR; end_time = jiffies + msecs_to_jiffies(750); do { if ((snd_ymfpci_readw(chip, reg) & 0x8000) == 0) return 0; set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout_uninterruptible(1); } while (time_before(jiffies, end_time)); snd_printk(KERN_ERR "codec_ready: codec %i is not ready [0x%x]\n", secondary, snd_ymfpci_readw(chip, reg)); return -EBUSY;}static void snd_ymfpci_codec_write(ac97_t *ac97, u16 reg, u16 val){ ymfpci_t *chip = ac97->private_data; u32 cmd; snd_ymfpci_codec_ready(chip, 0); cmd = ((YDSXG_AC97WRITECMD | reg) << 16) | val; snd_ymfpci_writel(chip, YDSXGR_AC97CMDDATA, cmd);}static u16 snd_ymfpci_codec_read(ac97_t *ac97, u16 reg){ ymfpci_t *chip = ac97->private_data; if (snd_ymfpci_codec_ready(chip, 0)) return ~0; snd_ymfpci_writew(chip, YDSXGR_AC97CMDADR, YDSXG_AC97READCMD | reg); if (snd_ymfpci_codec_ready(chip, 0)) return ~0; if (chip->device_id == PCI_DEVICE_ID_YAMAHA_744 && chip->rev < 2) { int i; for (i = 0; i < 600; i++) snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA); } return snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA);}/* * Misc routines */static u32 snd_ymfpci_calc_delta(u32 rate){ switch (rate) { case 8000: return 0x02aaab00; case 11025: return 0x03accd00; case 16000: return 0x05555500; case 22050: return 0x07599a00; case 32000: return 0x0aaaab00; case 44100: return 0x0eb33300; default: return ((rate << 16) / 375) << 5; }}static u32 def_rate[8] = { 100, 2000, 8000, 11025, 16000, 22050, 32000, 48000};static u32 snd_ymfpci_calc_lpfK(u32 rate){ u32 i; static u32 val[8] = { 0x00570000, 0x06AA0000, 0x18B20000, 0x20930000, 0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000 }; if (rate == 44100) return 0x40000000; /* FIXME: What's the right value? */ for (i = 0; i < 8; i++) if (rate <= def_rate[i]) return val[i]; return val[0];}static u32 snd_ymfpci_calc_lpfQ(u32 rate){ u32 i; static u32 val[8] = { 0x35280000, 0x34A70000, 0x32020000, 0x31770000, 0x31390000, 0x31C90000, 0x33D00000, 0x40000000 }; if (rate == 44100) return 0x370A0000; for (i = 0; i < 8; i++) if (rate <= def_rate[i]) return val[i]; return val[0];}/* * Hardware start management */static void snd_ymfpci_hw_start(ymfpci_t *chip){ unsigned long flags; spin_lock_irqsave(&chip->reg_lock, flags); if (chip->start_count++ > 0) goto __end; snd_ymfpci_writel(chip, YDSXGR_MODE, snd_ymfpci_readl(chip, YDSXGR_MODE) | 3); chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1; __end: spin_unlock_irqrestore(&chip->reg_lock, flags);}static void snd_ymfpci_hw_stop(ymfpci_t *chip){ unsigned long flags; long timeout = 1000; spin_lock_irqsave(&chip->reg_lock, flags); if (--chip->start_count > 0) goto __end; snd_ymfpci_writel(chip, YDSXGR_MODE, snd_ymfpci_readl(chip, YDSXGR_MODE) & ~3); while (timeout-- > 0) { if ((snd_ymfpci_readl(chip, YDSXGR_STATUS) & 2) == 0) break; } if (atomic_read(&chip->interrupt_sleep_count)) { atomic_set(&chip->interrupt_sleep_count, 0); wake_up(&chip->interrupt_sleep); } __end: spin_unlock_irqrestore(&chip->reg_lock, flags);}/* * Playback voice management */static int voice_alloc(ymfpci_t *chip, ymfpci_voice_type_t type, int pair, ymfpci_voice_t **rvoice){ ymfpci_voice_t *voice, *voice2; int idx; *rvoice = NULL; for (idx = 0; idx < YDSXG_PLAYBACK_VOICES; idx += pair ? 2 : 1) { voice = &chip->voices[idx]; voice2 = pair ? &chip->voices[idx+1] : NULL; if (voice->use || (voice2 && voice2->use)) continue; voice->use = 1; if (voice2) voice2->use = 1; switch (type) { case YMFPCI_PCM: voice->pcm = 1; if (voice2) voice2->pcm = 1; break; case YMFPCI_SYNTH: voice->synth = 1; break; case YMFPCI_MIDI: voice->midi = 1; break; } snd_ymfpci_hw_start(chip); if (voice2) snd_ymfpci_hw_start(chip); *rvoice = voice; return 0; } return -ENOMEM;}static int snd_ymfpci_voice_alloc(ymfpci_t *chip, ymfpci_voice_type_t type, int pair, ymfpci_voice_t **rvoice){ unsigned long flags; int result; snd_assert(rvoice != NULL, return -EINVAL); snd_assert(!pair || type == YMFPCI_PCM, return -EINVAL); spin_lock_irqsave(&chip->voice_lock, flags); for (;;) { result = voice_alloc(chip, type, pair, rvoice); if (result == 0 || type != YMFPCI_PCM) break; /* TODO: synth/midi voice deallocation */ break; } spin_unlock_irqrestore(&chip->voice_lock, flags); return result; }static int snd_ymfpci_voice_free(ymfpci_t *chip, ymfpci_voice_t *pvoice){ unsigned long flags; snd_assert(pvoice != NULL, return -EINVAL); snd_ymfpci_hw_stop(chip); spin_lock_irqsave(&chip->voice_lock, flags); pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0; pvoice->ypcm = NULL; pvoice->interrupt = NULL; spin_unlock_irqrestore(&chip->voice_lock, flags); return 0;}/* * PCM part */static void snd_ymfpci_pcm_interrupt(ymfpci_t *chip, ymfpci_voice_t *voice){ ymfpci_pcm_t *ypcm; u32 pos, delta; if ((ypcm = voice->ypcm) == NULL) return; if (ypcm->substream == NULL) return; spin_lock(&chip->reg_lock); if (ypcm->running) { pos = le32_to_cpu(voice->bank[chip->active_bank].start); if (pos < ypcm->last_pos) delta = pos + (ypcm->buffer_size - ypcm->last_pos); else delta = pos - ypcm->last_pos; ypcm->period_pos += delta; ypcm->last_pos = pos; if (ypcm->period_pos >= ypcm->period_size) { // printk("done - active_bank = 0x%x, start = 0x%x\n", chip->active_bank, voice->bank[chip->active_bank].start); ypcm->period_pos %= ypcm->period_size; spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(ypcm->substream); spin_lock(&chip->reg_lock); } if (unlikely(ypcm->update_pcm_vol)) { unsigned int subs = ypcm->substream->number; unsigned int next_bank = 1 - chip->active_bank; snd_ymfpci_playback_bank_t *bank; u32 volume; bank = &voice->bank[next_bank]; volume = cpu_to_le32(chip->pcm_mixer[subs].left << 15); bank->left_gain_end = volume; if (ypcm->output_rear) bank->eff2_gain_end = volume; if (ypcm->voices[1]) bank = &ypcm->voices[1]->bank[next_bank]; volume = cpu_to_le32(chip->pcm_mixer[subs].right << 15); bank->right_gain_end = volume; if (ypcm->output_rear) bank->eff3_gain_end = volume; ypcm->update_pcm_vol--; } } spin_unlock(&chip->reg_lock);}static void snd_ymfpci_pcm_capture_interrupt(snd_pcm_substream_t *substream){ snd_pcm_runtime_t *runtime = substream->runtime; ymfpci_pcm_t *ypcm = runtime->private_data; ymfpci_t *chip = ypcm->chip; u32 pos, delta; spin_lock(&chip->reg_lock); if (ypcm->running) { pos = le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift; if (pos < ypcm->last_pos) delta = pos + (ypcm->buffer_size - ypcm->last_pos); else delta = pos - ypcm->last_pos; ypcm->period_pos += delta; ypcm->last_pos = pos; if (ypcm->period_pos >= ypcm->period_size) { ypcm->period_pos %= ypcm->period_size; // printk("done - active_bank = 0x%x, start = 0x%x\n", chip->active_bank, voice->bank[chip->active_bank].start); spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(substream); spin_lock(&chip->reg_lock); } } spin_unlock(&chip->reg_lock);}static int snd_ymfpci_playback_trigger(snd_pcm_substream_t * substream, int cmd){ ymfpci_t *chip = snd_pcm_substream_chip(substream); ymfpci_pcm_t *ypcm = substream->runtime->private_data; int result = 0; spin_lock(&chip->reg_lock); if (ypcm->voices[0] == NULL) { result = -EINVAL; goto __unlock; } switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: chip->ctrl_playback[ypcm->voices[0]->number + 1] = cpu_to_le32(ypcm->voices[0]->bank_addr); if (ypcm->voices[1] != NULL) chip->ctrl_playback[ypcm->voices[1]->number + 1] = cpu_to_le32(ypcm->voices[1]->bank_addr); ypcm->running = 1; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: chip->ctrl_playback[ypcm->voices[0]->number + 1] = 0; if (ypcm->voices[1] != NULL) chip->ctrl_playback[ypcm->voices[1]->number + 1] = 0; ypcm->running = 0; break; default: result = -EINVAL; break; } __unlock: spin_unlock(&chip->reg_lock); return result;}static int snd_ymfpci_capture_trigger(snd_pcm_substream_t * substream, int cmd){ ymfpci_t *chip = snd_pcm_substream_chip(substream); ymfpci_pcm_t *ypcm = substream->runtime->private_data; int result = 0; u32 tmp; spin_lock(&chip->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) | (1 << ypcm->capture_bank_number); snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp); ypcm->running = 1; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) & ~(1 << ypcm->capture_bank_number); snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp); ypcm->running = 0; break; default: result = -EINVAL; break; } spin_unlock(&chip->reg_lock); return result;}static int snd_ymfpci_pcm_voice_alloc(ymfpci_pcm_t *ypcm, int voices){ int err; if (ypcm->voices[1] != NULL && voices < 2) { snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[1]); ypcm->voices[1] = NULL; } if (voices == 1 && ypcm->voices[0] != NULL) return 0; /* already allocated */ if (voices == 2 && ypcm->voices[0] != NULL && ypcm->voices[1] != NULL)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -