/*
 *  Initialization & resource registration routines
 *  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 "control.h"
#include "info.h"

int snd_cards_count;
unsigned int snd_cards_bitmap;		/* valid */
static unsigned int snd_cards_lock;	/* locked for registering/using */
snd_card_t *snd_cards[ SND_CARDS ];
snd_spin_define_static( card_lock );

void snd_driver_init( void )
{
  snd_cards_count = 0;
  snd_cards_bitmap = 0;
  memset( &snd_cards, 0, sizeof( snd_cards ) );
}

snd_card_t *snd_card_new( int idx, char *xid, void (*use_inc)( snd_card_t *card ), void (*use_dec)( snd_card_t *card ) )
{
  unsigned long flags;
  snd_card_t *card;
  
  if ( !use_inc || !use_dec ) return NULL;
  card = (snd_card_t *)snd_malloc( sizeof( snd_card_t ) );
  if ( !card ) return NULL;
  memset( card, 0, sizeof( snd_card_t ) );
  if ( xid ) {
    if ( !snd_info_check_reserved_words( xid ) ) {
      snd_free( card, sizeof( snd_card_t ) );
      return NULL;
    }
    strncpy( card -> id, xid, sizeof( card -> id ) - 1 );
  }
  snd_spin_lock_static( card_lock, &flags );
  if ( idx < 0 ) {
    for ( idx = 0; idx < snd_ecards_limit; idx++ )
      if ( !(snd_cards_lock & (1 << idx)) ) break;
  } else {
    idx--;
    if ( idx >= 0 && idx < snd_ecards_limit )
      if ( snd_cards_lock & (1 << idx) ) idx = -1;	/* invalid */
  }
  if ( idx < 0 || idx >= snd_ecards_limit ) {
    snd_free( card, sizeof( snd_card_t ) );
    snd_spin_unlock_static( card_lock, &flags );
    return NULL;
  }
  snd_cards_lock |= 1 << idx;		/* lock it */
  snd_spin_unlock_static( card_lock, &flags );
  card -> number = idx;
  if ( !card -> id[ 0 ] )
    sprintf( card -> id, "card%i", card -> number + 1 );
  card -> use_inc = use_inc;
  card -> use_dec = use_dec;
  snd_mutex_prepare( card, control );
  /* control interface can't be accessed from user space until */
  /* snd_cards_bitmask and snd_cards are set by snd_card_register */
  if ( snd_control_register( card -> number ) < 0 ) {
    snd_printd( "unable to register control minors\n" );
    /* Not fatal error */
  }
  if ( snd_info_card_register( card ) < 0 ) {
    snd_printd( "unable to register card info\n" );
    /* Not fatal error */
  }
#if 0
  snd_printk( "%i: card = 0x%lx\n", card -> number, (long)card );
#endif
  return card;
}

int snd_card_free( snd_card_t *card )
{
  unsigned long flags;

  if ( !card ) return -EINVAL;
  if ( snd_info_card_unregister( card ) < 0 ) {
    snd_printd( "unable to unregister card info\n" );
    /* Not fatal error */
  }
  if ( snd_control_unregister( card -> number ) < 0 ) {
    snd_printd( "unable to unregister control minors\n" );
    /* Not fatal error */
  }
  snd_spin_lock_static( card_lock, &flags );
  snd_cards_lock &= ~(1 << card -> number);
  snd_spin_unlock_static( card_lock, &flags );
  snd_unregister_interrupts( card );
  snd_unregister_dma_channels( card );
  snd_unregister_ioports( card );
  if ( card -> private_free )
    card -> private_free( card -> private_data );
  snd_free( card, sizeof( snd_card_t ) );
  return 0;
}

int snd_card_register( snd_card_t *card )
{
  unsigned long flags;

  if ( !card ) return -EINVAL;
  snd_spin_lock_static( card_lock, &flags );
  snd_cards_bitmap |= 1 << card -> number;
  snd_cards[ card -> number ] = card;
  snd_cards_count++;
  snd_spin_unlock_static( card_lock, &flags );
  return 0;
}

int snd_card_unregister( snd_card_t *card )
{
  unsigned long flags;
  
  if ( !card ) return -EINVAL;
  snd_spin_lock_static( card_lock, &flags );
  snd_cards_bitmap &= ~(1 << card -> number);
  snd_cards[ card -> number ] = NULL;
  snd_cards_count--;
  snd_spin_unlock_static( card_lock, &flags );
  return 0;
}

static snd_info_entry_t *snd_card_info_entry = NULL;

static void snd_card_info_read( snd_info_buffer_t *buffer, void *private_data )
{
  unsigned long flags;
  int idx, count;
  snd_card_t *card;
  
  for ( idx = count = 0; idx < SND_CARDS; idx++ ) {
    snd_spin_lock_static( card_lock, &flags );
    if ( snd_cards_bitmap & (1 << idx) ) {
      count++;
      card = snd_cards[ idx ];
      snd_iprintf( buffer, "%i [%-15s]: %s - %s\n", idx, card -> id, card -> abbreviation, card -> shortname );
      snd_iprintf( buffer, "                     %s\n", card -> longname );
    }
    snd_spin_unlock_static( card_lock, &flags );
  }
  if ( !count ) {
    snd_iprintf( buffer, "--- no soundcards ---\n" );
  }
}

int snd_card_info_init( void )
{
  snd_info_entry_t *entry;
  
  entry = snd_info_create_entry( NULL, "cards" );
  if ( !entry ) return -ENOMEM;
  entry -> t.text.read_size = PAGE_SIZE;
  entry -> t.text.read = snd_card_info_read;
  if ( snd_info_register( entry ) < 0 ) {
    snd_info_free_entry( entry );
    return -ENOMEM;
  }
  snd_card_info_entry = entry;
  return 0;
}

int snd_card_info_done( void )
{
  if ( snd_card_info_entry )
    snd_info_unregister( snd_card_info_entry );
  return 0;
}

/*
 *  Delay ten microseconds * loops...
 */

void snd_delay( int loops )
{
  int i;

  while( loops-- > 0 )
      for ( i = 0; i < 16; i++ ) inb( 0x80 );
}      

/*
 *  I/O port registration
 */

int snd_register_ioport( snd_card_t *card, int port, int size, char *name )
{
  int idx;
  char *xname;
  struct snd_stru_port *pport;
  
  if ( !card ) return -EINVAL;
  for ( idx = 0; idx < SND_PORTS; idx++ ) {
    if ( card -> ports_map & (1 << idx) ) continue;
    xname = snd_malloc_strdup( name );
    if ( check_region( port, size ) ) {
      snd_free_str( xname );
      return -EBUSY;
    }
    pport = (struct snd_stru_port *)snd_malloc( sizeof( struct snd_stru_port ) );
    if ( !pport ) {
      snd_free_str( xname );
      return -ENOMEM;
    }
    memset( pport, 0, sizeof( *pport ) );
    request_region( port, size, xname );
    card -> ports_map |= 1 << idx;
    pport -> port = port;
    pport -> size = size;
    pport -> name = xname;
    card -> ports[ idx ] = pport;
    return 0;
  }
  return -ENOMEM;
}

int snd_unregister_ioports( snd_card_t *card )
{
  int idx;
  struct snd_stru_port *pport;
  
  if ( !card ) return -EINVAL;
  for ( idx = 0; idx < SND_PORTS; idx++ ) {
    if ( card -> ports_map & (1 << idx) ) {
      pport = card -> ports[ idx ];
      if ( !pport ) { snd_printd( "unregister_ioports: ugh!!!\n" ); continue; }
      card -> ports[ idx ] = NULL;
      release_region( pport -> port, pport -> size );
      snd_free_str( pport -> name );
      snd_free( pport, sizeof( struct snd_stru_port ) );
    }
  }
  card -> ports_map = 0;
  return 0;
}

/*
 *  DMA channel registration
 */

int snd_register_dma_channel( snd_card_t *card, char *name, int number, int type, int rsize, int *possible_numbers )
{
  int idx;
  struct snd_stru_dma *dmaptr;

  if ( !card ) return -EINVAL;
  if ( type < 0 || type > SND_DMA_TYPE_HARDWARE ) return -EINVAL;
  if ( rsize == SND_AUTO_DMA_SIZE ) {
    switch ( type ) {
      case SND_DMA_TYPE_ISA: rsize = 128; break;
      case SND_DMA_TYPE_PCI: rsize = 512; break;
      case SND_DMA_TYPE_HARDWARE: rsize = 1024; break;
      default: return -EINVAL;
    }
  }
  if ( rsize < 4 || rsize > 1024 ) return -EINVAL;
  if ( type == SND_DMA_TYPE_ISA ) {
    if ( rsize > 128 ) return -EINVAL;
  }
  if ( type == SND_DMA_TYPE_ISA ) {
    if ( !possible_numbers ) return -EINVAL;
    if ( number == SND_AUTO_DMA ) {
      for ( ; ( number = *possible_numbers ) >= 0 ; possible_numbers++ ) {
        if ( request_dma( number, "snd test" ) ) continue;
        free_dma( number );
        break;
      }
      if ( number < 0 ) return -ENOMEM;
    } else {
      if ( *possible_numbers >= 0 ) {
        for ( ; *possible_numbers >= 0; possible_numbers++ )
          if ( *possible_numbers == number ) break;
        if ( *possible_numbers < 0 ) return -EINVAL;
      }
    }
    if ( number < 4 && rsize > 64 ) rsize = 64;
    if ( number < 0 || number > 7 ) return -EINVAL;
  } else {
    if ( possible_numbers ) return -EINVAL;
    if ( number < 0 || number >= SND_DMAS ) return -EINVAL;
  }
  for ( idx = 0; idx < SND_DMAS; idx++ ) {
    if ( card -> dmas_map & (1 << idx) ) continue;
    break;
  }
  if ( idx >= SND_DMAS ) return -ENOMEM;
  dmaptr = (struct snd_stru_dma *)snd_malloc( sizeof( struct snd_stru_dma ) );
  if ( !dmaptr ) return -ENOMEM;
  memset( dmaptr, 0, sizeof( *dmaptr ) );
  dmaptr -> type = type;
  dmaptr -> dma = number;
  dmaptr -> rsize = rsize * 1024;
  dmaptr -> name = snd_malloc_strdup( name );
  snd_mutex_prepare( dmaptr, lock );
  snd_mutex_prepare( dmaptr, mutex );
  if ( type == SND_DMA_TYPE_ISA ) {
    if ( request_dma( number, dmaptr -> name ) ) {
      snd_printk( "unable to grab DMA %i for %s\n", number, dmaptr -> name );
      snd_free_str( dmaptr -> name );
      snd_free( dmaptr, sizeof( struct snd_stru_dma ) );
      return -EBUSY;
    }
  }
  card -> dmas[ idx ] = dmaptr;
  card -> dmas_map |= 1 << idx;
  return idx;
}

int snd_unregister_dma_channels( snd_card_t *card )
{
  int idx;
  struct snd_stru_dma *dmaptr;

  if ( !card ) return -EINVAL;
  for ( idx = 0; idx < SND_DMAS; idx++ ) {
    if ( !(card -> dmas_map & (1 << idx)) ) continue;
    dmaptr = card -> dmas[ idx ];
    card -> dmas[ idx ] = NULL;
    if ( dmaptr -> type == SND_DMA_TYPE_ISA ) {
      disable_dma( dmaptr -> dma );
    }
    free_dma( dmaptr -> dma );
    snd_free_str( dmaptr -> name );
    snd_free( dmaptr, sizeof( struct snd_stru_dma ) );
  }
  card -> dmas_map = 0;
  return 0;
}

/*
 *  IRQ channel registration
 */

int snd_register_interrupt( snd_card_t *card, char *name, int number, int type, snd_irq_handler_t *handler, void *dev_id, int *possible_numbers )
{
  int idx, sa_flags;
  char *xname;
  struct snd_stru_irq *irqptr;

  if ( !card ) return -EINVAL;
  if ( type < 0 || type > SND_IRQ_TYPE_PCI ) return -EINVAL;
  sa_flags = SA_INTERRUPT;
  if ( type != SND_IRQ_TYPE_ISA ) sa_flags = SA_SHIRQ;
  if ( type == SND_IRQ_TYPE_ISA ) {
    if ( !possible_numbers ) return -EINVAL;
    if ( number == SND_AUTO_IRQ ) {
      for ( ; ( number = *possible_numbers ) >= 0; possible_numbers++ ) {
        if ( request_irq( number, handler, sa_flags, "snd test", dev_id ) ) continue;
        free_irq( number, dev_id );
        break;
      }
      if ( number < 0 ) return -ENOMEM;
    } else {
      if ( *possible_numbers >= 0 ) {
        for ( ; *possible_numbers >= 0; possible_numbers++ )
          if ( *possible_numbers == number ) break;
        if ( *possible_numbers < 0 ) return -EINVAL;
      }
    }
  } else {
    if ( possible_numbers ) return -EINVAL;
  }
  if ( number < 0 || number > 15 ) return -EINVAL;
  for ( idx = 0; idx < SND_IRQS; idx++ ) {
    if ( card -> irqs_map & (1 << idx) ) continue;
    break;
  }
  if ( idx >= SND_IRQS ) return -ENOMEM;
  xname = snd_malloc_strdup( name );
  if ( !xname ) return -ENOMEM;
  irqptr = (struct snd_stru_irq *)snd_malloc( sizeof( struct snd_stru_irq ) );
  if ( !irqptr ) {
    snd_free_str( xname );
    return -ENOMEM;
  }
  memset( irqptr, 0, sizeof( struct snd_stru_irq ) );
  if ( request_irq( number, handler, sa_flags, xname, dev_id ) ) {
    snd_printk( "unable to grab IRQ %i for %s\n", number, xname );
    snd_free_str( xname );
    snd_free( irqptr, sizeof( struct snd_stru_irq ) );
    return -EBUSY;
  }
  irqptr -> type = type;
  irqptr -> irq = number;
  irqptr -> name = xname;
  irqptr -> dev_id = dev_id;
  card -> irqs[ idx ] = irqptr;
  card -> irqs_map |= (1 << idx);
  return idx;
}

int snd_unregister_interrupts( snd_card_t *card )
{
  int idx;
  struct snd_stru_irq *irqptr;
  
  if ( !card ) return -EINVAL;
  for ( idx = 0; idx < SND_IRQS; idx++ ) {
    if ( card -> irqs_map & (1 << idx) ) {
      irqptr = card -> irqs[ idx ];
      if ( irqptr -> type == SND_IRQ_TYPE_ISA ) {
        disable_irq( irqptr -> irq );
      }
      free_irq( irqptr -> irq, irqptr -> dev_id );
      snd_free_str( irqptr -> name );
      snd_free( irqptr, sizeof( struct snd_stru_irq ) );
    }
  }
  card -> irqs_map = 0;
  return 0;
}
