diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/pci/ymfpci/ymfpci_main.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'sound/pci/ymfpci/ymfpci_main.c')
-rw-r--r-- | sound/pci/ymfpci/ymfpci_main.c | 2273 |
1 files changed, 2273 insertions, 0 deletions
diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c new file mode 100644 index 00000000000..05f1629760b --- /dev/null +++ b/sound/pci/ymfpci/ymfpci_main.c @@ -0,0 +1,2273 @@ +/* + * 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) +{ + signed long end_time; + u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR; + + end_time = (jiffies + ((3 * HZ) / 4)) + 1; + do { + if ((snd_ymfpci_readw(chip, reg) & 0x8000) == 0) + return 0; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (end_time - (signed long)jiffies >= 0); + snd_printk("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); + } + } + 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) + return 0; /* already allocated */ + if (voices > 1) { + if (ypcm->voices[0] != NULL && ypcm->voices[1] == NULL) { + snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[0]); + ypcm->voices[0] = NULL; + } + } + err = snd_ymfpci_voice_alloc(ypcm->chip, YMFPCI_PCM, voices > 1, &ypcm->voices[0]); + if (err < 0) + return err; + ypcm->voices[0]->ypcm = ypcm; + ypcm->voices[0]->interrupt = snd_ymfpci_pcm_interrupt; + if (voices > 1) { + ypcm->voices[1] = &ypcm->chip->voices[ypcm->voices[0]->number + 1]; + ypcm->voices[1]->ypcm = ypcm; + } + return 0; +} + +static void snd_ymfpci_pcm_init_voice(ymfpci_voice_t *voice, int stereo, + int rate, int w_16, unsigned long addr, + unsigned int end, + int output_front, int output_rear) +{ + u32 format; + u32 delta = snd_ymfpci_calc_delta(rate); + u32 lpfQ = snd_ymfpci_calc_lpfQ(rate); + u32 lpfK = snd_ymfpci_calc_lpfK(rate); + snd_ymfpci_playback_bank_t *bank; + unsigned int nbank; + + snd_assert(voice != NULL, return); + format = (stereo ? 0x00010000 : 0) | (w_16 ? 0 : 0x80000000); + for (nbank = 0; nbank < 2; nbank++) { + bank = &voice->bank[nbank]; + bank->format = cpu_to_le32(format); + bank->loop_default = 0; + bank->base = cpu_to_le32(addr); + bank->loop_start = 0; + bank->loop_end = cpu_to_le32(end); + bank->loop_frac = 0; + bank->eg_gain_end = cpu_to_le32(0x40000000); + bank->lpfQ = cpu_to_le32(lpfQ); + bank->status = 0; + bank->num_of_frames = 0; + bank->loop_count = 0; + bank->start = 0; + bank->start_frac = 0; + bank->delta = + bank->delta_end = cpu_to_le32(delta); + bank->lpfK = + bank->lpfK_end = cpu_to_le32(lpfK); + bank->eg_gain = cpu_to_le32(0x40000000); + bank->lpfD1 = + bank->lpfD2 = 0; + + bank->left_gain = + bank->right_gain = + bank->left_gain_end = + bank->right_gain_end = + bank->eff1_gain = + bank->eff2_gain = + bank->eff3_gain = + bank->eff1_gain_end = + bank->eff2_gain_end = + bank->eff3_gain_end = 0; + + if (!stereo) { + if (output_front) { + bank->left_gain = + bank->right_gain = + bank->left_gain_end = + bank->right_gain_end = cpu_to_le32(0x40000000); + } + if (output_rear) { + bank->eff2_gain = + bank->eff2_gain_end = + bank->eff3_gain = + bank->eff3_gain_end = cpu_to_le32(0x40000000); + } + } else { + if (output_front) { + if ((voice->number & 1) == 0) { + bank->left_gain = + bank->left_gain_end = cpu_to_le32(0x40000000); + } else { + bank->format |= cpu_to_le32(1); + bank->right_gain = + bank->right_gain_end = cpu_to_le32(0x40000000); + } + } + if (output_rear) { + if ((voice->number & 1) == 0) { + bank->eff3_gain = + bank->eff3_gain_end = cpu_to_le32(0x40000000); + } else { + bank->format |= cpu_to_le32(1); + bank->eff2_gain = + bank->eff2_gain_end = cpu_to_le32(0x40000000); + } + } + } + } +} + +static int __devinit snd_ymfpci_ac3_init(ymfpci_t *chip) +{ + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), + 4096, &chip->ac3_tmp_base) < 0) + return -ENOMEM; + + chip->bank_effect[3][0]->base = + chip->bank_effect[3][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr); + chip->bank_effect[3][0]->loop_end = + chip->bank_effect[3][1]->loop_end = cpu_to_le32(1024); + chip->bank_effect[4][0]->base = + chip->bank_effect[4][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr + 2048); + chip->bank_effect[4][0]->loop_end = + chip->bank_effect[4][1]->loop_end = cpu_to_le32(1024); + + spin_lock_irq(&chip->reg_lock); + snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, + snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) | 3 << 3); + spin_unlock_irq(&chip->reg_lock); + return 0; +} + +static int snd_ymfpci_ac3_done(ymfpci_t *chip) +{ + spin_lock_irq(&chip->reg_lock); + snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, + snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) & ~(3 << 3)); + spin_unlock_irq(&chip->reg_lock); + // snd_ymfpci_irq_wait(chip); + if (chip->ac3_tmp_base.area) { + snd_dma_free_pages(&chip->ac3_tmp_base); + chip->ac3_tmp_base.area = NULL; + } + return 0; +} + +static int snd_ymfpci_playback_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm = runtime->private_data; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + if ((err = snd_ymfpci_pcm_voice_alloc(ypcm, params_channels(hw_params))) < 0) + return err; + return 0; +} + +static int snd_ymfpci_playback_hw_free(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm; + + if (runtime->private_data == NULL) + return 0; + ypcm = runtime->private_data; + + /* wait, until the PCI operations are not finished */ + snd_ymfpci_irq_wait(chip); + snd_pcm_lib_free_pages(substream); + if (ypcm->voices[1]) { + snd_ymfpci_voice_free(chip, ypcm->voices[1]); + ypcm->voices[1] = NULL; + } + if (ypcm->voices[0]) { + snd_ymfpci_voice_free(chip, ypcm->voices[0]); + ypcm->voices[0] = NULL; + } + return 0; +} + +static int snd_ymfpci_playback_prepare(snd_pcm_substream_t * substream) +{ + // ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm = runtime->private_data; + unsigned int nvoice; + + ypcm->period_size = runtime->period_size; + ypcm->buffer_size = runtime->buffer_size; + ypcm->period_pos = 0; + ypcm->last_pos = 0; + for (nvoice = 0; nvoice < runtime->channels; nvoice++) + snd_ymfpci_pcm_init_voice(ypcm->voices[nvoice], + runtime->channels == 2, + runtime->rate, + snd_pcm_format_width(runtime->format) == 16, + runtime->dma_addr, + ypcm->buffer_size, + ypcm->output_front, + ypcm->output_rear); + return 0; +} + +static int snd_ymfpci_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_ymfpci_capture_hw_free(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + + /* wait, until the PCI operations are not finished */ + snd_ymfpci_irq_wait(chip); + return snd_pcm_lib_free_pages(substream); +} + +static int snd_ymfpci_capture_prepare(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm = runtime->private_data; + snd_ymfpci_capture_bank_t * bank; + int nbank; + u32 rate, format; + + ypcm->period_size = runtime->period_size; + ypcm->buffer_size = runtime->buffer_size; + ypcm->period_pos = 0; + ypcm->last_pos = 0; + ypcm->shift = 0; + rate = ((48000 * 4096) / runtime->rate) - 1; + format = 0; + if (runtime->channels == 2) { + format |= 2; + ypcm->shift++; + } + if (snd_pcm_format_width(runtime->format) == 8) + format |= 1; + else + ypcm->shift++; + switch (ypcm->capture_bank_number) { + case 0: + snd_ymfpci_writel(chip, YDSXGR_RECFORMAT, format); + snd_ymfpci_writel(chip, YDSXGR_RECSLOTSR, rate); + break; + case 1: + snd_ymfpci_writel(chip, YDSXGR_ADCFORMAT, format); + snd_ymfpci_writel(chip, YDSXGR_ADCSLOTSR, rate); + break; + } + for (nbank = 0; nbank < 2; nbank++) { + bank = chip->bank_capture[ypcm->capture_bank_number][nbank]; + bank->base = cpu_to_le32(runtime->dma_addr); + bank->loop_end = cpu_to_le32(ypcm->buffer_size << ypcm->shift); + bank->start = 0; + bank->num_of_loops = 0; + } + return 0; +} + +static snd_pcm_uframes_t snd_ymfpci_playback_pointer(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm = runtime->private_data; + ymfpci_voice_t *voice = ypcm->voices[0]; + + if (!(ypcm->running && voice)) + return 0; + return le32_to_cpu(voice->bank[chip->active_bank].start); +} + +static snd_pcm_uframes_t snd_ymfpci_capture_pointer(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm = runtime->private_data; + + if (!ypcm->running) + return 0; + return le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift; +} + +static void snd_ymfpci_irq_wait(ymfpci_t *chip) +{ + wait_queue_t wait; + int loops = 4; + + while (loops-- > 0) { + if ((snd_ymfpci_readl(chip, YDSXGR_MODE) & 3) == 0) + continue; + init_waitqueue_entry(&wait, current); + add_wait_queue(&chip->interrupt_sleep, &wait); + atomic_inc(&chip->interrupt_sleep_count); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/20); + remove_wait_queue(&chip->interrupt_sleep, &wait); + } +} + +static irqreturn_t snd_ymfpci_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + ymfpci_t *chip = dev_id; + u32 status, nvoice, mode; + ymfpci_voice_t *voice; + + status = snd_ymfpci_readl(chip, YDSXGR_STATUS); + if (status & 0x80000000) { + chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1; + spin_lock(&chip->voice_lock); + for (nvoice = 0; nvoice < YDSXG_PLAYBACK_VOICES; nvoice++) { + voice = &chip->voices[nvoice]; + if (voice->interrupt) + voice->interrupt(chip, voice); + } + for (nvoice = 0; nvoice < YDSXG_CAPTURE_VOICES; nvoice++) { + if (chip->capture_substream[nvoice]) + snd_ymfpci_pcm_capture_interrupt(chip->capture_substream[nvoice]); + } +#if 0 + for (nvoice = 0; nvoice < YDSXG_EFFECT_VOICES; nvoice++) { + if (chip->effect_substream[nvoice]) + snd_ymfpci_pcm_effect_interrupt(chip->effect_substream[nvoice]); + } +#endif + spin_unlock(&chip->voice_lock); + spin_lock(&chip->reg_lock); + snd_ymfpci_writel(chip, YDSXGR_STATUS, 0x80000000); + mode = snd_ymfpci_readl(chip, YDSXGR_MODE) | 2; + snd_ymfpci_writel(chip, YDSXGR_MODE, mode); + spin_unlock(&chip->reg_lock); + + if (atomic_read(&chip->interrupt_sleep_count)) { + atomic_set(&chip->interrupt_sleep_count, 0); + wake_up(&chip->interrupt_sleep); + } + } + + status = snd_ymfpci_readw(chip, YDSXGR_INTFLAG); + if (status & 1) { + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + } + snd_ymfpci_writew(chip, YDSXGR_INTFLAG, status); + + if (chip->rawmidi) + snd_mpu401_uart_interrupt(irq, chip->rawmidi->private_data, regs); + return IRQ_HANDLED; +} + +static snd_pcm_hardware_t snd_ymfpci_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 256 * 1024, /* FIXME: enough? */ + .period_bytes_min = 64, + .period_bytes_max = 256 * 1024, /* FIXME: enough? */ + .periods_min = 3, + .periods_max = 1024, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t snd_ymfpci_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 256 * 1024, /* FIXME: enough? */ + .period_bytes_min = 64, + .period_bytes_max = 256 * 1024, /* FIXME: enough? */ + .periods_min = 3, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void snd_ymfpci_pcm_free_substream(snd_pcm_runtime_t *runtime) +{ + ymfpci_pcm_t *ypcm = runtime->private_data; + + kfree(ypcm); +} + +static int snd_ymfpci_playback_open_1(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm; + + ypcm = kcalloc(1, sizeof(*ypcm), GFP_KERNEL); + if (ypcm == NULL) + return -ENOMEM; + ypcm->chip = chip; + ypcm->type = PLAYBACK_VOICE; + ypcm->substream = substream; + runtime->hw = snd_ymfpci_playback; + runtime->private_data = ypcm; + runtime->private_free = snd_ymfpci_pcm_free_substream; + /* FIXME? True value is 256/48 = 5.33333 ms */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 5333, UINT_MAX); + return 0; +} + +/* call with spinlock held */ +static void ymfpci_open_extension(ymfpci_t *chip) +{ + if (! chip->rear_opened) { + if (! chip->spdif_opened) /* set AC3 */ + snd_ymfpci_writel(chip, YDSXGR_MODE, + snd_ymfpci_readl(chip, YDSXGR_MODE) | (1 << 30)); + /* enable second codec (4CHEN) */ + snd_ymfpci_writew(chip, YDSXGR_SECCONFIG, + (snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) | 0x0010); + } +} + +/* call with spinlock held */ +static void ymfpci_close_extension(ymfpci_t *chip) +{ + if (! chip->rear_opened) { + if (! chip->spdif_opened) + snd_ymfpci_writel(chip, YDSXGR_MODE, + snd_ymfpci_readl(chip, YDSXGR_MODE) & ~(1 << 30)); + snd_ymfpci_writew(chip, YDSXGR_SECCONFIG, + (snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) & ~0x0010); + } +} + +static int snd_ymfpci_playback_open(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm; + int err; + + if ((err = snd_ymfpci_playback_open_1(substream)) < 0) + return err; + ypcm = runtime->private_data; + ypcm->output_front = 1; + ypcm->output_rear = chip->mode_dup4ch ? 1 : 0; + spin_lock_irq(&chip->reg_lock); + if (ypcm->output_rear) { + ymfpci_open_extension(chip); + chip->rear_opened++; + } + spin_unlock_irq(&chip->reg_lock); + return 0; +} + +static int snd_ymfpci_playback_spdif_open(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm; + int err; + + if ((err = snd_ymfpci_playback_open_1(substream)) < 0) + return err; + ypcm = runtime->private_data; + ypcm->output_front = 0; + ypcm->output_rear = 1; + spin_lock_irq(&chip->reg_lock); + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, + snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) | 2); + ymfpci_open_extension(chip); + chip->spdif_pcm_bits = chip->spdif_bits; + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits); + chip->spdif_opened++; + spin_unlock_irq(&chip->reg_lock); + + chip->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id); + return 0; +} + +static int snd_ymfpci_playback_4ch_open(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm; + int err; + + if ((err = snd_ymfpci_playback_open_1(substream)) < 0) + return err; + ypcm = runtime->private_data; + ypcm->output_front = 0; + ypcm->output_rear = 1; + spin_lock_irq(&chip->reg_lock); + ymfpci_open_extension(chip); + chip->rear_opened++; + spin_unlock_irq(&chip->reg_lock); + return 0; +} + +static int snd_ymfpci_capture_open(snd_pcm_substream_t * substream, + u32 capture_bank_number) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm; + + ypcm = kcalloc(1, sizeof(*ypcm), GFP_KERNEL); + if (ypcm == NULL) + return -ENOMEM; + ypcm->chip = chip; + ypcm->type = capture_bank_number + CAPTURE_REC; + ypcm->substream = substream; + ypcm->capture_bank_number = capture_bank_number; + chip->capture_substream[capture_bank_number] = substream; + runtime->hw = snd_ymfpci_capture; + /* FIXME? True value is 256/48 = 5.33333 ms */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 5333, UINT_MAX); + runtime->private_data = ypcm; + runtime->private_free = snd_ymfpci_pcm_free_substream; + snd_ymfpci_hw_start(chip); + return 0; +} + +static int snd_ymfpci_capture_rec_open(snd_pcm_substream_t * substream) +{ + return snd_ymfpci_capture_open(substream, 0); +} + +static int snd_ymfpci_capture_ac97_open(snd_pcm_substream_t * substream) +{ + return snd_ymfpci_capture_open(substream, 1); +} + +static int snd_ymfpci_playback_close_1(snd_pcm_substream_t * substream) +{ + return 0; +} + +static int snd_ymfpci_playback_close(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + ymfpci_pcm_t *ypcm = substream->runtime->private_data; + + spin_lock_irq(&chip->reg_lock); + if (ypcm->output_rear && chip->rear_opened > 0) { + chip->rear_opened--; + ymfpci_close_extension(chip); + } + spin_unlock_irq(&chip->reg_lock); + return snd_ymfpci_playback_close_1(substream); +} + +static int snd_ymfpci_playback_spdif_close(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + + spin_lock_irq(&chip->reg_lock); + chip->spdif_opened = 0; + ymfpci_close_extension(chip); + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, + snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & ~2); + snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits); + spin_unlock_irq(&chip->reg_lock); + chip->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id); + return snd_ymfpci_playback_close_1(substream); +} + +static int snd_ymfpci_playback_4ch_close(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + + spin_lock_irq(&chip->reg_lock); + if (chip->rear_opened > 0) { + chip->rear_opened--; + ymfpci_close_extension(chip); + } + spin_unlock_irq(&chip->reg_lock); + return snd_ymfpci_playback_close_1(substream); +} + +static int snd_ymfpci_capture_close(snd_pcm_substream_t * substream) +{ + ymfpci_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + ymfpci_pcm_t *ypcm = runtime->private_data; + + if (ypcm != NULL) { + chip->capture_substream[ypcm->capture_bank_number] = NULL; + snd_ymfpci_hw_stop(chip); + } + return 0; +} + +static snd_pcm_ops_t snd_ymfpci_playback_ops = { + .open = snd_ymfpci_playback_open, + .close = snd_ymfpci_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ymfpci_playback_hw_params, + .hw_free = snd_ymfpci_playback_hw_free, + .prepare = snd_ymfpci_playback_prepare, + .trigger = snd_ymfpci_playback_trigger, + .pointer = snd_ymfpci_playback_pointer, +}; + +static snd_pcm_ops_t snd_ymfpci_capture_rec_ops = { + .open = snd_ymfpci_capture_rec_open, + .close = snd_ymfpci_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ymfpci_capture_hw_params, + .hw_free = snd_ymfpci_capture_hw_free, + .prepare = snd_ymfpci_capture_prepare, + .trigger = snd_ymfpci_capture_trigger, + .pointer = snd_ymfpci_capture_pointer, +}; + +static void snd_ymfpci_pcm_free(snd_pcm_t *pcm) +{ + ymfpci_t *chip = pcm->private_data; + chip->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int __devinit snd_ymfpci_pcm(ymfpci_t *chip, int device, snd_pcm_t ** rpcm) +{ + snd_pcm_t *pcm; + int err; + + if (rpcm) + *rpcm = NULL; + if ((err = snd_pcm_new(chip->card, "YMFPCI", device, 32, 1, &pcm)) < 0) + return err; + pcm->private_data = chip; + pcm->private_free = snd_ymfpci_pcm_free; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_rec_ops); + + /* global setup */ + pcm->info_flags = 0; + strcpy(pcm->name, "YMFPCI"); + chip->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 64*1024, 256*1024); + + if (rpcm) + *rpcm = pcm; + return 0; +} + +static snd_pcm_ops_t snd_ymfpci_capture_ac97_ops = { + .open = snd_ymfpci_capture_ac97_open, + .close = snd_ymfpci_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ymfpci_capture_hw_params, + .hw_free = snd_ymfpci_capture_hw_fre |