2005-04-17 02:20:36 +04:00
/*
* Routines for GF1 DMA control
2007-10-15 11:50:19 +04:00
* Copyright ( c ) by Jaroslav Kysela < perex @ perex . cz >
2005-04-17 02:20:36 +04:00
*
*
* 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/gus.h>
2005-11-17 16:36:44 +03:00
static void snd_gf1_dma_ack ( struct snd_gus_card * gus )
2005-04-17 02:20:36 +04:00
{
unsigned long flags ;
spin_lock_irqsave ( & gus - > reg_lock , flags ) ;
snd_gf1_write8 ( gus , SNDRV_GF1_GB_DRAM_DMA_CONTROL , 0x00 ) ;
snd_gf1_look8 ( gus , SNDRV_GF1_GB_DRAM_DMA_CONTROL ) ;
spin_unlock_irqrestore ( & gus - > reg_lock , flags ) ;
}
2005-11-17 16:36:44 +03:00
static void snd_gf1_dma_program ( struct snd_gus_card * gus ,
2005-04-17 02:20:36 +04:00
unsigned int addr ,
unsigned long buf_addr ,
unsigned int count ,
unsigned int cmd )
{
unsigned long flags ;
unsigned int address ;
unsigned char dma_cmd ;
unsigned int address_high ;
2009-01-29 13:46:45 +03:00
snd_printdd ( " dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x \n " ,
addr , buf_addr , count ) ;
2005-04-17 02:20:36 +04:00
if ( gus - > gf1 . dma1 > 3 ) {
if ( gus - > gf1 . enh_mode ) {
address = addr > > 1 ;
} else {
if ( addr & 0x1f ) {
snd_printd ( " snd_gf1_dma_transfer: unaligned address (0x%x)? \n " , addr ) ;
return ;
}
address = ( addr & 0x000c0000 ) | ( ( addr & 0x0003ffff ) > > 1 ) ;
}
} else {
address = addr ;
}
dma_cmd = SNDRV_GF1_DMA_ENABLE | ( unsigned short ) cmd ;
#if 0
dma_cmd | = 0x08 ;
# endif
if ( dma_cmd & SNDRV_GF1_DMA_16BIT ) {
count + + ;
count & = ~ 1 ; /* align */
}
if ( gus - > gf1 . dma1 > 3 ) {
dma_cmd | = SNDRV_GF1_DMA_WIDTH16 ;
count + + ;
count & = ~ 1 ; /* align */
}
snd_gf1_dma_ack ( gus ) ;
snd_dma_program ( gus - > gf1 . dma1 , buf_addr , count , dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE ) ;
#if 0
2009-02-05 17:46:48 +03:00
snd_printk ( KERN_DEBUG " address = 0x%x, count = 0x%x, dma_cmd = 0x%x \n " ,
address < < 1 , count , dma_cmd ) ;
2005-04-17 02:20:36 +04:00
# endif
spin_lock_irqsave ( & gus - > reg_lock , flags ) ;
if ( gus - > gf1 . enh_mode ) {
address_high = ( ( address > > 16 ) & 0x000000f0 ) | ( address & 0x0000000f ) ;
snd_gf1_write16 ( gus , SNDRV_GF1_GW_DRAM_DMA_LOW , ( unsigned short ) ( address > > 4 ) ) ;
snd_gf1_write8 ( gus , SNDRV_GF1_GB_DRAM_DMA_HIGH , ( unsigned char ) address_high ) ;
} else
snd_gf1_write16 ( gus , SNDRV_GF1_GW_DRAM_DMA_LOW , ( unsigned short ) ( address > > 4 ) ) ;
snd_gf1_write8 ( gus , SNDRV_GF1_GB_DRAM_DMA_CONTROL , dma_cmd ) ;
spin_unlock_irqrestore ( & gus - > reg_lock , flags ) ;
}
2005-11-17 16:36:44 +03:00
static struct snd_gf1_dma_block * snd_gf1_dma_next_block ( struct snd_gus_card * gus )
2005-04-17 02:20:36 +04:00
{
2005-11-17 16:36:44 +03:00
struct snd_gf1_dma_block * block ;
2005-04-17 02:20:36 +04:00
/* PCM block have bigger priority than synthesizer one */
if ( gus - > gf1 . dma_data_pcm ) {
block = gus - > gf1 . dma_data_pcm ;
if ( gus - > gf1 . dma_data_pcm_last = = block ) {
gus - > gf1 . dma_data_pcm =
gus - > gf1 . dma_data_pcm_last = NULL ;
} else {
gus - > gf1 . dma_data_pcm = block - > next ;
}
} else if ( gus - > gf1 . dma_data_synth ) {
block = gus - > gf1 . dma_data_synth ;
if ( gus - > gf1 . dma_data_synth_last = = block ) {
gus - > gf1 . dma_data_synth =
gus - > gf1 . dma_data_synth_last = NULL ;
} else {
gus - > gf1 . dma_data_synth = block - > next ;
}
} else {
block = NULL ;
}
if ( block ) {
gus - > gf1 . dma_ack = block - > ack ;
gus - > gf1 . dma_private_data = block - > private_data ;
}
return block ;
}
2005-11-17 16:36:44 +03:00
static void snd_gf1_dma_interrupt ( struct snd_gus_card * gus )
2005-04-17 02:20:36 +04:00
{
2005-11-17 16:36:44 +03:00
struct snd_gf1_dma_block * block ;
2005-04-17 02:20:36 +04:00
snd_gf1_dma_ack ( gus ) ;
if ( gus - > gf1 . dma_ack )
gus - > gf1 . dma_ack ( gus , gus - > gf1 . dma_private_data ) ;
spin_lock ( & gus - > dma_lock ) ;
if ( gus - > gf1 . dma_data_pcm = = NULL & &
gus - > gf1 . dma_data_synth = = NULL ) {
gus - > gf1 . dma_ack = NULL ;
gus - > gf1 . dma_flags & = ~ SNDRV_GF1_DMA_TRIGGER ;
spin_unlock ( & gus - > dma_lock ) ;
return ;
}
block = snd_gf1_dma_next_block ( gus ) ;
spin_unlock ( & gus - > dma_lock ) ;
snd_gf1_dma_program ( gus , block - > addr , block - > buf_addr , block - > count , ( unsigned short ) block - > cmd ) ;
kfree ( block ) ;
#if 0
2009-01-29 13:46:45 +03:00
snd_printd ( KERN_DEBUG " program dma (IRQ) - "
" addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x \n " ,
block - > addr , block - > buf_addr , block - > count , block - > cmd ) ;
2005-04-17 02:20:36 +04:00
# endif
}
2005-11-17 16:36:44 +03:00
int snd_gf1_dma_init ( struct snd_gus_card * gus )
2005-04-17 02:20:36 +04:00
{
2006-01-16 18:33:08 +03:00
mutex_lock ( & gus - > dma_mutex ) ;
2005-04-17 02:20:36 +04:00
gus - > gf1 . dma_shared + + ;
if ( gus - > gf1 . dma_shared > 1 ) {
2006-01-16 18:33:08 +03:00
mutex_unlock ( & gus - > dma_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
gus - > gf1 . interrupt_handler_dma_write = snd_gf1_dma_interrupt ;
gus - > gf1 . dma_data_pcm =
gus - > gf1 . dma_data_pcm_last =
gus - > gf1 . dma_data_synth =
gus - > gf1 . dma_data_synth_last = NULL ;
2006-01-16 18:33:08 +03:00
mutex_unlock ( & gus - > dma_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-11-17 16:36:44 +03:00
int snd_gf1_dma_done ( struct snd_gus_card * gus )
2005-04-17 02:20:36 +04:00
{
2005-11-17 16:36:44 +03:00
struct snd_gf1_dma_block * block ;
2005-04-17 02:20:36 +04:00
2006-01-16 18:33:08 +03:00
mutex_lock ( & gus - > dma_mutex ) ;
2005-04-17 02:20:36 +04:00
gus - > gf1 . dma_shared - - ;
if ( ! gus - > gf1 . dma_shared ) {
snd_dma_disable ( gus - > gf1 . dma1 ) ;
snd_gf1_set_default_handlers ( gus , SNDRV_GF1_HANDLER_DMA_WRITE ) ;
snd_gf1_dma_ack ( gus ) ;
while ( ( block = gus - > gf1 . dma_data_pcm ) ) {
gus - > gf1 . dma_data_pcm = block - > next ;
kfree ( block ) ;
}
while ( ( block = gus - > gf1 . dma_data_synth ) ) {
gus - > gf1 . dma_data_synth = block - > next ;
kfree ( block ) ;
}
gus - > gf1 . dma_data_pcm_last =
gus - > gf1 . dma_data_synth_last = NULL ;
}
2006-01-16 18:33:08 +03:00
mutex_unlock ( & gus - > dma_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-11-17 16:36:44 +03:00
int snd_gf1_dma_transfer_block ( struct snd_gus_card * gus ,
struct snd_gf1_dma_block * __block ,
2005-04-17 02:20:36 +04:00
int atomic ,
int synth )
{
unsigned long flags ;
2005-11-17 16:36:44 +03:00
struct snd_gf1_dma_block * block ;
2005-04-17 02:20:36 +04:00
block = kmalloc ( sizeof ( * block ) , atomic ? GFP_ATOMIC : GFP_KERNEL ) ;
if ( block = = NULL ) {
2005-10-20 20:26:44 +04:00
snd_printk ( KERN_ERR " gf1: DMA transfer failure; not enough memory \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
}
* block = * __block ;
block - > next = NULL ;
2009-01-29 13:46:45 +03:00
snd_printdd ( " addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x \n " ,
block - > addr , ( long ) block - > buffer , block - > count ,
block - > cmd ) ;
snd_printdd ( " gus->gf1.dma_data_pcm_last = 0x%lx \n " ,
( long ) gus - > gf1 . dma_data_pcm_last ) ;
snd_printdd ( " gus->gf1.dma_data_pcm = 0x%lx \n " ,
( long ) gus - > gf1 . dma_data_pcm ) ;
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & gus - > dma_lock , flags ) ;
if ( synth ) {
if ( gus - > gf1 . dma_data_synth_last ) {
gus - > gf1 . dma_data_synth_last - > next = block ;
gus - > gf1 . dma_data_synth_last = block ;
} else {
gus - > gf1 . dma_data_synth =
gus - > gf1 . dma_data_synth_last = block ;
}
} else {
if ( gus - > gf1 . dma_data_pcm_last ) {
gus - > gf1 . dma_data_pcm_last - > next = block ;
gus - > gf1 . dma_data_pcm_last = block ;
} else {
gus - > gf1 . dma_data_pcm =
gus - > gf1 . dma_data_pcm_last = block ;
}
}
if ( ! ( gus - > gf1 . dma_flags & SNDRV_GF1_DMA_TRIGGER ) ) {
gus - > gf1 . dma_flags | = SNDRV_GF1_DMA_TRIGGER ;
block = snd_gf1_dma_next_block ( gus ) ;
spin_unlock_irqrestore ( & gus - > dma_lock , flags ) ;
if ( block = = NULL )
return 0 ;
snd_gf1_dma_program ( gus , block - > addr , block - > buf_addr , block - > count , ( unsigned short ) block - > cmd ) ;
kfree ( block ) ;
return 0 ;
}
spin_unlock_irqrestore ( & gus - > dma_lock , flags ) ;
return 0 ;
}