/*
 *  Routines for GF1 DMA control
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "driver.h"
#include "gus.h"

void snd_gf1_dma_program( snd_gus_card_t *gus,
			  unsigned int addr, 
			  const void *buf, 
			  unsigned int count, 
                          unsigned int cmd )
{
  unsigned long flags;
  unsigned int address;
  unsigned char dma_cmd;
  unsigned int address_high;

#if 0
  snd_printk( "dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n", addr, (long)buf, count );
#endif

  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 = SND_GF1_DMA_ENABLE | (unsigned short)cmd;
#if 0
  dma_cmd |= 0x08;
#endif
  if ( dma_cmd & SND_GF1_DMA_16BIT )  {
    count++; count &= ~1;			/* align */
  }
  if ( gus -> gf1.dma1 > 3 ) {
    dma_cmd |= SND_GF1_DMA_WIDTH16;
    count++; count &= ~1;			/* align */
  }
  snd_spin_lock( gus, reg, &flags );
  snd_gf1_write8( gus, SND_GF1_GB_DRAM_DMA_CONTROL, 0 );  /* disable GF1 DMA */
  snd_gf1_look8( gus, SND_GF1_GB_DRAM_DMA_CONTROL );
  snd_spin_unlock( gus, reg, &flags );
  snd_dma_program( gus -> gf1.dma1, buf, count, dma_cmd & SND_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE );
#if 0
  snd_printk( "address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n", address << 1, count, dma_cmd );
#endif
#if 0
  snd_printk( "jiffies = %li, used = %i, bpos = %i (%i)\n", jiffies, gus -> gf1.dma_used, gus -> gf1.pcm_bpos, gus -> pcm -> playback.block_lock );
#endif
  snd_spin_lock( gus, reg, &flags );
  if ( gus -> gf1.enh_mode )
    {
      address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f);
      snd_gf1_write16( gus, SND_GF1_GW_DRAM_DMA_LOW, (unsigned short)(address >> 4) );
      snd_gf1_write8( gus, SND_GF1_GB_DRAM_DMA_HIGH, (unsigned char)address_high );
    }
   else
    snd_gf1_write16( gus, SND_GF1_GW_DRAM_DMA_LOW, (unsigned short)(address >> 4) );
  snd_gf1_write8( gus, SND_GF1_GB_DRAM_DMA_CONTROL, dma_cmd );
  snd_spin_unlock( gus, reg, &flags );
}

void snd_gf1_dma_ack( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, SND_GF1_GB_DRAM_DMA_CONTROL, 0x00 );
}

static void snd_gf1_dma_interrupt( snd_gus_card_t *gus )
{
  unsigned long flags;
  void *buffer;
  unsigned int addr, count, cmd, tail;

  snd_gf1_dma_ack( gus );
  snd_spin_lock( gus, dma, &flags );
  if ( --gus -> gf1.dma_used <= 0 ) {
    gus -> gf1.dma_flags &= ~SND_GF1_DMA_TRIGGER;
    if ( gus -> gf1.dma_flags & SND_GF1_DMA_SLEEP )
      snd_wakeup( gus, neutral );
    snd_spin_unlock( gus, dma, &flags );
    return;
  }
  tail = gus -> gf1.dma_tail;
  buffer = gus -> gf1.dma_data[ tail ].buffer;
  addr = gus -> gf1.dma_data[ tail ].addr;
  count = gus -> gf1.dma_data[ tail ].count;
  cmd = gus -> gf1.dma_data[ tail ].cmd;
  gus -> gf1.dma_tail++;
  gus -> gf1.dma_tail %= gus -> gf1.dma_blocks;
  snd_spin_unlock( gus, dma, &flags );
  snd_gf1_dma_program( gus, addr, buffer, count, (unsigned short)cmd );
#if 0
  printk( "program dma (IRQ) - addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", addr, (long)buffer, count, cmd );
#endif
}

int snd_gf1_dma_init( snd_gus_card_t *gus )
{
  snd_mutex_down( gus, dma );
  gus -> gf1.dma_shared++;
  if ( gus -> gf1.dma_shared > 1 ) {
    snd_mutex_up( gus, dma );
    return 0;
  }    
  gus -> gf1.dma_flags = 0;
  gus -> gf1.dma_blocks = 1024;
  gus -> gf1.dma_data = snd_malloc( sizeof( snd_gf1_dma_block_t ) * gus -> gf1.dma_blocks );
  gus -> gf1.dma_head =
  gus -> gf1.dma_tail =
  gus -> gf1.dma_used = 0;
  if ( gus -> gf1.dma_data == NULL ) {
    gus -> gf1.dma_shared--;
    snd_mutex_up( gus, dma );
    return -ENOMEM;
  }
  gus -> gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt;
  snd_mutex_up( gus, dma );
  return 0;
}

int snd_gf1_dma_done( snd_gus_card_t *gus )
{
  snd_gf1_dma_block_t *dma_data;
  
  snd_mutex_down( gus, dma );
  gus -> gf1.dma_shared--;
  if ( !gus -> gf1.dma_shared ) {
    snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_DMA_WRITE );
    dma_data = gus -> gf1.dma_data;
    gus -> gf1.dma_data = NULL;
    snd_free( dma_data, sizeof( snd_gf1_dma_block_t ) * gus -> gf1.dma_blocks );
  }
  snd_mutex_up( gus, dma );
  return 0;
}

int snd_gf1_dma_transfer_block( snd_gus_card_t *gus,
                                unsigned int addr,
                                void *buffer,
                                unsigned int count,
                                unsigned int cmd )
{
  unsigned long flags;

#if 0
  printk( "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", addr, (long)buffer, count, cmd );
#endif
  if ( !gus -> gf1.dma_data ) return -EINVAL;
  snd_spin_lock( gus, dma, &flags );
  if ( gus -> gf1.dma_used >= gus -> gf1.dma_blocks ) {
    snd_spin_unlock( gus, dma, &flags );
    snd_printk( "sound: gf1 - dma block buffer full (%i)!!!\n", gus -> gf1.dma_blocks );
    return -ENOMEM;
  }                  
  gus -> gf1.dma_data[ gus -> gf1.dma_head ].buffer = buffer;
  gus -> gf1.dma_data[ gus -> gf1.dma_head ].addr = addr;
  gus -> gf1.dma_data[ gus -> gf1.dma_head ].count = count;
  gus -> gf1.dma_data[ gus -> gf1.dma_head ].cmd = cmd; 
  gus -> gf1.dma_head++;
  gus -> gf1.dma_head %= gus -> gf1.dma_blocks;
  gus -> gf1.dma_used++;
  if ( !(gus -> gf1.dma_flags & SND_GF1_DMA_TRIGGER) ) {
    gus -> gf1.dma_flags |= SND_GF1_DMA_TRIGGER;
    buffer = gus -> gf1.dma_data[ gus -> gf1.dma_tail ].buffer;
    addr = gus -> gf1.dma_data[ gus -> gf1.dma_tail ].addr;
    count = gus -> gf1.dma_data[ gus -> gf1.dma_tail ].count;
    cmd = gus -> gf1.dma_data[ gus -> gf1.dma_tail ].cmd;
    gus -> gf1.dma_tail++;
    gus -> gf1.dma_tail %= gus -> gf1.dma_blocks;
    snd_spin_unlock( gus, dma, &flags );
    snd_gf1_dma_program( gus, addr, buffer, count, (unsigned short)cmd );
#if 0
    printk( "transfer block: addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x, dma_used = %i\n", addr, (long)buffer, count, cmd, gus -> gf1.dma_used );
#endif
    return 0;
  }
  snd_spin_unlock( gus, dma, &flags );
  return 0;
}
