/*
 *  Digital Audio (PCM) abstract layer
 *  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.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "minors.h"
#include "pcm.h"
#include "control.h"
#include "ulaw.h"

snd_pcm_t *snd_pcm_devices[ SND_CARDS * SND_PCM_DEVICES ];

static struct snd_stru_pcm_notify *snd_pcm_notify_first = NULL;

snd_mutex_define_static( register );

static int snd_pcm_control_ioctl( snd_card_t *card, snd_control_t *control, unsigned int cmd, unsigned long arg )
{
  unsigned int tmp;
  int idx;
  
  tmp = card -> number << 2;
  switch ( cmd ) {
    case SND_CTL_IOCTL_HW_INFO:
      {
        struct snd_ctl_hw_info *ptr = (struct snd_ctl_hw_info *)arg;

        ptr -> pcmdevs = 0;
        for ( idx = SND_PCM_DEVICES - 1; idx >= 0; idx-- ) {
          if ( snd_pcm_devices[ tmp + idx ] ) {
             ptr -> pcmdevs = idx + 1;
             break;
          }
        }
        return 0;
      }
    case SND_CTL_IOCTL_PCM_DEVICE:
      {
        int val = snd_ioctl_in( (long *)arg );
        if ( val < 0 || val >= SND_PCM_DEVICES ) return -EINVAL;
        if ( !snd_pcm_devices[ tmp + val ] ) return -EINVAL;
        control -> pcm_device = val;
        return 0;
      }
  }
  return -EAGAIN;              
}

snd_pcm_t *snd_pcm_new_device( snd_card_t *card, char *id )
{
  snd_pcm_t *pcm;

  if ( !card ) return NULL;
  pcm = (snd_pcm_t *)snd_malloc( sizeof( snd_pcm_t ) );
  if ( !pcm ) return NULL;
  memset( pcm, 0, sizeof( snd_pcm_t ) );
  pcm -> card = card;
  if ( id ) {
    strncpy( pcm -> id, id, sizeof( pcm -> id ) - 1 );
  }
  return pcm;
}

int snd_pcm_free( snd_pcm_t *pcm )
{
  if ( !pcm ) return -EINVAL;
  if ( pcm -> private_free )
    pcm -> private_free( pcm -> private_data );
  snd_free( pcm, sizeof( snd_pcm_t ) );
  return 0;  
}

int snd_pcm_register( snd_pcm_t *pcm, int pcm_device, snd_minor_t *reg )
{
  int idx, err;
  unsigned short minor;
  struct snd_stru_pcm_notify *notify;

  snd_mutex_down_static( register );
  if ( pcm_device < 0 || pcm_device >= SND_PCM_DEVICES ) {
    snd_mutex_up_static( register );
    return -EINVAL;
  }
  idx = (pcm -> card -> number * SND_PCM_DEVICES) + pcm_device;
  if ( snd_pcm_devices[ idx ] ) {
    snd_mutex_up_static( register );
    return -EBUSY;
  }
  pcm -> device = pcm_device;
  snd_pcm_devices[ idx ] = pcm;
  minor = SND_MINOR_PCM + idx;
  if ( (err = snd_register_minor( minor, reg )) < 0 ) {
    snd_pcm_devices[ idx ] = NULL;
    snd_mutex_up_static( register );
    return err;
  }
  for ( notify = snd_pcm_notify; notify; notify = notify -> next ) {
    if ( notify -> n_register )
      notify -> n_register( minor, pcm );
  }
  snd_pcm_proc_init( pcm );
  snd_mutex_up_static( register );
  return 0;
}

int snd_pcm_unregister( snd_pcm_t *pcm )
{
  int idx;
  struct snd_stru_pcm_notify *notify;
  
  if ( !pcm ) return -EINVAL;
  snd_mutex_down_static( register );  
  idx = (pcm -> card -> number * SND_PCM_DEVICES) + pcm -> device;
  if ( snd_pcm_devices[ idx ] != pcm ) {
    snd_mutex_up_static( register );
    return -EINVAL;
  }
  snd_pcm_proc_done( pcm );
  snd_unregister_minor( SND_MINOR_PCM + idx );
  for ( notify = snd_pcm_notify; notify; notify = notify -> next ) {
    if ( notify -> n_unregister )
      notify -> n_unregister( SND_MINOR_PCM + idx, pcm );
  }
  snd_pcm_devices[ idx ] = NULL;
  snd_mutex_up_static( register );
  return snd_pcm_free( pcm );
}

int snd_pcm_notify( struct snd_stru_pcm_notify *notify, int nfree )
{
  struct snd_stru_pcm_notify *tnotify;

  if ( !notify ) return -EINVAL;
  if ( nfree ) {
    tnotify = snd_pcm_notify;
    if ( tnotify == notify ) {
      snd_pcm_notify = notify -> next;
    } else {
      while ( tnotify && tnotify -> next != notify ) notify = notify -> next;
      if ( !tnotify ) return -ENOENT;
      tnotify -> next = tnotify -> next -> next;
    }
    snd_free( notify, sizeof( struct snd_pcm_stru_notify ) );
  } else {
    tnotify = (struct snd_pcm_stru_notify *)snd_malloc( sizeof( struct snd_pcm_stru_notify ) );
    if ( !tnotify ) return -ENOMEM;
    memcpy( tnotify, notify, sizeof( struct snd_pcm_stru_notify ) );    
    tnotify -> next = NULL;
    if ( !snd_pcm_notify ) {
      snd_pcm_notify = tnotify;
    } else {
      for ( notify = snd_pcm_notify; notify -> next; notify = notify -> next );
      notify -> next = tnotify;
    }
  }
  return 0;
}

/*
 *  ENTRY functions
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_pcm1_export;
#endif

int init_module( void )
{
#ifndef LINUX_2_1
  if ( register_symtab( &snd_symbol_table_pcm1_export ) < 0 )
    return -ENOMEM;
#endif
  snd_control_register_ioctl( snd_pcm_control_ioctl );
  return 0;
}

void cleanup_module( void )
{
  snd_control_unregister_ioctl( snd_pcm_control_ioctl );
}
