/*
* 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 <linux/sched/signal.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 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