/*
 *  Routines for driver 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 "minors.h"
#include "control.h"

struct snd_stru_control_ioctl {
  snd_control_ioctl_t fioctl;
  struct snd_stru_control_ioctl *next;
};
    
static struct snd_stru_control_ioctl *snd_first_ioctl = NULL;
static int snd_first_ioctl_use = 0;

snd_mutex_define_static( register );

static int snd_control_open( unsigned short minor, int cardnum, int device, struct file *file )
{
  snd_card_t *card;
  snd_control_t *control;

  if ( !(card = snd_cards[ cardnum ]) ) return -ENXIO;
  control = (snd_control_t *)snd_malloc( sizeof( snd_control_t ) );
  if ( !control ) return -ENOMEM;
  control -> card = card;
  file -> private_data = control;
  MOD_INC_USE_COUNT;
  card -> use_inc( card );
  return 0;
}

static int snd_control_release( unsigned short minor, int cardnum, int device, struct file *file )
{
  snd_control_t *control;

  if ( file -> private_data ) {
    control = (snd_control_t *)file -> private_data;
    file -> private_data = NULL;
    control -> card -> use_dec( control -> card );
    snd_free( control, sizeof( snd_control_t ) );
  }
  MOD_DEC_USE_COUNT;
  return 0;
}

static int snd_control_hw_info( snd_card_t *card, snd_control_t *control, unsigned int cmd, unsigned long arg )
{
  struct snd_stru_control_ioctl *p;
  struct snd_ctl_hw_info info;

  memset( &info, 0, sizeof( info ) );
  if ( verify_area( VERIFY_WRITE, (void *)arg, sizeof( struct snd_ctl_hw_info ) ) ) return -EFAULT;
  snd_mutex_down( card, control );
  info.type = card -> type;
  strncpy( info.id, card -> id, 8 );
  strncpy( info.abbreviation, card -> abbreviation, sizeof( info.abbreviation ) - 1 );
  strncpy( info.name, card -> shortname, sizeof( info.name ) - 1 );
  strncpy( info.longname, card -> longname, sizeof( info.longname ) - 1 );
  for ( p = snd_first_ioctl; p; p = p -> next )
    p -> fioctl( card, control, cmd, (unsigned long)&info );
  snd_mutex_up( card, control );
  copy_to_user( (void *)arg, &info, sizeof( struct snd_ctl_hw_info ) );
  return 0;
}

static int snd_control_ioctl( struct file *file, unsigned int cmd, unsigned long arg )
{
  snd_control_t *control;
  snd_card_t *card;
  struct snd_stru_control_ioctl *p;
  int err;

  control = (snd_control_t *)file -> private_data;
  if ( !control ) return -EINVAL;
  card = control -> card;
  if ( !card ) return -EINVAL;
  switch ( cmd ) {
    case SND_CTL_IOCTL_PVERSION:
      return snd_ioctl_out( (long *)arg, SND_CTL_VERSION );
    case SND_CTL_IOCTL_HW_INFO:
      return snd_control_hw_info( card, control, cmd, arg );
  }
  snd_mutex_down( card, control );
  for ( p = snd_first_ioctl; p; p = p -> next ) {
    err = p -> fioctl( card, control, cmd, arg );
    if ( err != -EAGAIN ) {
      snd_mutex_up( card, control );
      return err;
    }
  }
  snd_mutex_up( card, control );
  return -ENXIO;
}

int snd_control_register_ioctl( snd_control_ioctl_t fcn )
{
  struct snd_stru_control_ioctl *p, *pn;

  pn = (struct snd_stru_control_ioctl *)snd_malloc( sizeof( struct snd_stru_control_ioctl ) );
  if ( !pn ) return -ENOMEM;
  memset( pn, 0, sizeof( *pn ) );
  pn -> fioctl = fcn;
  snd_mutex_down_static( register ); 
  if ( !snd_first_ioctl ) {
    snd_first_ioctl = pn;
  } else {
    for ( p = snd_first_ioctl; p -> next; p = p -> next );
    p -> next = pn;
  }
  snd_mutex_up_static( register );
  return 0;
}

int snd_control_unregister_ioctl( snd_control_ioctl_t fcn )
{
  struct snd_stru_control_ioctl *p, *pn, *po;

  snd_mutex_down_static( register );
  if ( !snd_first_ioctl ) {
    snd_mutex_up_static( register );
    return -EINVAL;
  }
  p = snd_first_ioctl;
  if ( p -> fioctl == fcn ) {
    snd_first_ioctl = p -> next;
    snd_mutex_up_static( register );
    snd_free( p, sizeof( struct snd_stru_control_ioctl ) );
    return 0;
  }
  for ( ; p; p = pn ) {
    pn = p -> next;
    if ( pn && pn -> fioctl == fcn ) {
      po = pn;
      p -> next = pn = pn -> next;
      snd_mutex_up_static( register );
      snd_free( po, sizeof( struct snd_stru_control_ioctl ) );
      return 0;
    }
  }
  snd_mutex_up_static( register );
  return -EINVAL;
}

/*
 *  INIT PART
 */

static snd_minor_t snd_control_reg = {
  "control",

  NULL,					/* unregister */

  NULL,					/* lseek */
  NULL,					/* read */
  NULL,					/* write */
  snd_control_open,			/* open */
  snd_control_release,			/* release */
#ifdef SND_POLL
  NULL,					/* poll */
#else
  NULL,					/* select */
#endif
  snd_control_ioctl,			/* ioctl */
  NULL					/* mmap */
};

int snd_control_register( int cardnum )
{
  int err;
  
  if ( ( err = snd_register_minor( SND_MINOR_CONTROL + cardnum, &snd_control_reg ) ) < 0 )
    return err;
  snd_first_ioctl_use++;
  return 0;
}

int snd_control_unregister( int cardnum )
{
  int err;
  
  if ( ( err = snd_unregister_minor( SND_MINOR_CONTROL + cardnum ) ) < 0 )
    return err;
  snd_first_ioctl_use--;
  return 0;
}
