/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Routines for control of GF1 chip (PCM things)
*
* InterWave chips supports interleaved DMA, but this feature isn't used in
* this code.
*
* This code emulates autoinit DMA transfer for playback, recording by GF1
* chip doesn't support autoinit DMA.
*
*
* 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 <asm/dma.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/gus.h>
#include <sound/pcm_params.h>
#include "gus_tables.h"
/* maximum rate */
#define SNDRV_GF1_PCM_RATE 48000
#define SNDRV_GF1_PCM_PFLG_NONE 0
#define SNDRV_GF1_PCM_PFLG_ACTIVE (1<<0)
#define SNDRV_GF1_PCM_PFLG_NEUTRAL (2<<0)
struct gus_pcm_private {
struct snd_gus_card * gus;
struct snd_pcm_substream *substream;
spinlock_t lock;
unsigned int voices;
struct snd_gus_voice *pvoices[2];
unsigned int memory;
unsigned short flags;
unsigned char voice_ctrl, ramp_ctrl;
unsigned int bpos;
unsigned int blocks;
unsigned int block_size;
unsigned int dma_size;
wait_queue_head_t sleep;
atomic_t dma_count;
int final_volume;
};
static int snd_gf1_pcm_use_dma = 1;
static void snd_gf1_pcm_block_change_ack(struct snd_gus_card * gus, void *private_data)
{
struct gus_pcm_private *pcmp = private_data;
if (pcmp) {
atomic_dec(&pcmp->dma_count);
wake_up(&pcmp->sleep);
}
}
static int snd_gf1_pcm_block_change(struct snd_pcm_substream *substream,
unsigned int offset,
unsigned int addr,
unsigned int count)
{
struct snd_gf1_dma_block block;
struct snd_pcm_runtime *runtime = substream->runtime;
struct gus_pcm_private *pcmp = runtime->private_data;
count += offset & 31;
offset &= ~31;
/*
snd_printk(KERN_DEBUG "block change - offset = 0x%x, count = 0x%x\n",
offset, count);
*/
memset(&block, 0, sizeof(block));
block.cmd = SNDRV_GF1_DMA_IRQ;
if (snd_pcm_format_unsigned(runtime->format))
block.cmd |= SNDRV_GF1_DMA_UNSIGNED;
if (snd_pcm_format_width(runtime->format) == 16)
block.cmd |= SNDRV_GF1_DMA_16BIT;
block.addr = addr & ~31;
block.buffer = runtime->dma_area + offset;
block.buf_addr = runtime->dma_addr + offset;
block.count = count;
block.private_data = pcmp;
block.ack = snd_gf1_pcm_block_change_ack;
if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0))
atomic_inc(&pcmp->dma_count);
return 0;
}
static void snd_gf1_pcm_trigger_up(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct gus_pcm_private *pcmp = runtime->private_data;
struct snd_gus_card * gus = pcmp->gus;
unsigned long flags;
unsigned char voice_ctrl, ramp_ctrl;
unsigned short rate;
unsigned int curr, begin, end;
unsigned short vol;
unsigned char pan;
unsigned int voice;
spin_lock_irqsave(&pcmp->lock, flags);
if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
spin_unlock_irqrestore(&pcmp->lock, flags);
return;
}
pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE;
pcmp->final_volume = 0;
spin_unlock_irqrestore(&pcmp->lock, flags);
rate = snd_gf1_translate_freq(gus, runtime->rate << 4);
/* enable WAVE IRQ */
voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20;
/* enable RAMP IRQ + rollover */
ramp_ctrl = 0x24;
if (pcmp->blocks == 1) {
voice_ctrl |= 0x08; /* loop enable */
ramp_ctrl &= ~0x04; /* disable rollover */
}
for (voice = 0; voice < pcmp->voices; voice++) {
begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels);
curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels;
end = curr + (pcmp->block_size / runtime->channels);
end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1;
/*
snd_printk(KERN_DEBUG "init: curr=0x%x, begin=0x%x, end=0x%x, "
"ctrl=0x%x, ramp=0x%x, rate=0x%x\n",
curr, begin, end, voice_ctrl, ramp_ctrl, rate);
*/
pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8;
vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
spin_lock_irqsave(&gus->reg_lock, flags);
snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan);
snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate);
snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4);
snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4);
snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4);
snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f);
snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET);
snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8);
snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
if (!gus->gf1.enh_mode) {
snd_gf1_delay(gus);
snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
}
spin_unlock_irqrestore(&gus->reg_lock, flags);
}
spin_lock_irqsave(&gus->reg_lock, flags);
for (voice = 0; voice < pcmp->voices; voice++) {
snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
if (gus->gf1.enh_mode)
snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00); /* deactivate voice */
snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
voice_ctrl &= ~0x20;
}
voice_ctrl |= 0x20;
if (!gus->gf1.enh_mode) {
snd_gf1_delay(gus);
for (voice = 0; voice < pcmp->voices; voice++) {
snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
voice_ctrl &= ~0x20; /* disable IRQ for next voice */
}
}
spin_unlock_irqrestore(&gus->reg_lock, flags);
}
static void snd_gf1_pcm_interrupt_wave(struct snd_gus_card * gus,
struct snd_gus_voice *pvoice)
{
struct gus_pcm_private * pcmp;
struct snd_pcm_runtime *runtime;
unsigned char voice_ctrl, ramp_ctrl;
unsigned int idx;
unsigned int end, step;
if (!pvoice->private_data) {
snd_printd("snd_gf1_pcm: unknown wave irq?\n");
snd_gf1_smart_stop_voice(gus, pvoice->number);
return;
}
pcmp = pvoice->private_data;
if (pcmp == NULL) {
snd_printd("snd_gf1_pcm: unknown wave irq?\n");
snd_gf1_smart_stop_voice(gus, pvoice->number);
return;
}
gus = pcmp->gus;
runtime = pcmp->substream->runtime;
spin_lock(&gus->reg_lock);
snd_gf1_select_voice(gus, pvoice->number);
voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b;
ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03;
#if 0
snd_gf1_select_voice(gus, pvoice->number);
printk(KERN_DEBUG "position = 0x%x\n",
(snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
snd_gf1_select_voice(gus, pcmp->pvoices[1]->number);
printk(KERN_DEBUG "position = 0x%x\n",
(snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
snd_gf1_select_voice(gus, pvoice->number);
#endif
pcmp->bpos++;
pcmp->bpos %= pcmp->blocks;
if (pcmp->bpos + 1 >= pcmp->blocks) { /* last block? */
voice_ctrl |= 0x08; /* enable loop */
} else {
ramp_ctrl |= 0x04; /* enable rollover */
}
end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels);
end -= voice_ctrl & 4 ? 2 : 1;
step = pcmp->dma_size / runtime->channels;
voice_ctrl |= 0x20;
if (!pcmp->final_volume) {
ramp_ctrl |= 0x20;
ramp_ctrl &= ~0x03;
}
for (idx = 0; idx < p