patch-2.1.129 linux/drivers/sbus/audio/audio.c

Next file: linux/drivers/sbus/audio/cs4231.c
Previous file: linux/drivers/sbus/audio/amd7930.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.128/linux/drivers/sbus/audio/audio.c linux/drivers/sbus/audio/audio.c
@@ -1,13 +1,17 @@
 /*
  * drivers/sbus/audio/audio.c
  *
- * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu)
- * Copyright (C) 1997 Derrick J. Brashear (shadow@dementia.org)
+ * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
+ * Copyright (C) 1997,1998 Derrick J. Brashear (shadow@dementia.org)
  * Copyright (C) 1997 Brent Baccala (baccala@freesoft.org)
  * 
  * Mixer code adapted from code contributed by and
  * Copyright (C) 1998 Michael Mraka (michael@fi.muni.cz)
+ * The mixer code cheats; Sparc hardware doesn't generally allow independent
+ * line control, and this fakes it badly.
  *
+ * SNDCTL_DSP_SETFMT based on code contributed by
+ * Ion Badulescu (ionut@moisil.cs.columbia.edu)
  *
  * This is the audio midlayer that sits between the VFS character
  * devices and the low-level audio hardware device drivers.
@@ -26,191 +30,345 @@
 #include <linux/interrupt.h>
 #include <linux/init.h>
 #include <linux/soundcard.h>
-#include <asm/uaccess.h>
+#include <linux/version.h>
+#include <asm/delay.h>
 #include <asm/pgtable.h>
 
 #include <asm/audioio.h>
 
+#undef __AUDIO_DEBUG
+#define __AUDIO_ERROR
+#undef __AUDIO_TRACE
+#ifdef __AUDIO_DEBUG
+#define dprintk(x) printk x
+#else
+#define dprintk(x)
+#endif
+#ifdef __AUDIO_ERROR
+#define eprintk(x) printk x
+#else
+#define eprintk(x)
+#endif
+#ifdef __AUDIO_TRACE
+#define tprintk(x) printk x
+#else
+#define tprintk(x)
+#endif
 
-/*
- *	Low-level driver interface.
- */
+static short lis_get_elist_ent( strevent_t *list, pid_t pid );
+static int lis_add_to_elist( strevent_t **list, pid_t pid, short events );
+static int lis_del_from_elist( strevent_t **list, pid_t pid, short events );
+static void lis_free_elist( strevent_t **list);
+static void kill_procs( struct strevent *elist, int sig, short e);
+
+static struct sparcaudio_driver *drivers[SPARCAUDIO_MAX_DEVICES] = {NULL};
+
+/* This crap to be pulled off into a local include file */
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+
+#define COPY_IN(arg, get) verify_area(VERIFY_READ, (void *)arg, sizeof(long)); memcpy_fromfs(&get, (long *)arg, sizeof(get));
+#define COPY_OUT(arg, ret) verify_area(VERIFY_WRITE, (void *)arg, sizeof(long)); memcpy_tofs((long *)arg, &ret, sizeof(ret));
+#define copy_to_user memcpy_tofs
+#define copy_from_user memcpy_fromfs
+#define signal_pending(x) (((x)->signal) & ~((x)->blocked))
+
+#else
 
-/* We only support one low-level audio driver currently. */
-static struct sparcaudio_driver *driver = NULL;
+#include <asm/uaccess.h>
+#include <linux/poll.h>
+#define COPY_IN(arg, get) get_user(get, (int *)arg)
+#define COPY_OUT(arg, ret) put_user(ret, (int *)arg)
+#define sparcaudio_release_ret sparcaudio_release
+#define sparcaudioctl_release_ret sparcaudioctl_release
+#define sparcaudio_select sparcaudio_poll
 
-int register_sparcaudio_driver(struct sparcaudio_driver *drv)
+#endif
+
+int register_sparcaudio_driver(struct sparcaudio_driver *drv, int duplex)
 {
-	int i;
+	int i, dev;
 
-	/* If a driver is already present, don't allow the register. */
-	if (driver)
-		return -EIO;
+	/* If we've used up SPARCAUDIO_MAX_DEVICES, fail */
+	for (dev = 0; dev < SPARCAUDIO_MAX_DEVICES; dev++) {
+	  if (drivers[dev] == NULL) {
+	    break;
+	  }
+	}
+	if (drivers[dev]) {
+	  return -EIO;
+	}
 
 	/* Ensure that the driver has a proper operations structure. */
-	if (!drv->ops || !drv->ops->start_output || !drv->ops->stop_output)
+	if (!drv->ops || !drv->ops->start_output || !drv->ops->stop_output ||
+	    !drv->ops->start_input || !drv->ops->stop_input)
 		return -EINVAL;
 
-	/* Setup the circular queues of output and input buffers
-	 *
-	 * Each buffer is a single page, but output buffers might
-	 * be partially filled (by a write with count < 4096),
-	 * so each output buffer also has a paired output size.
-	 *
-	 * Input buffers, on the other hand, always fill completely,
-	 * so we don't need input counts - each contains 4096
-	 * bytes of audio data.
-	 *
-	 * TODO: Make number of input/output buffers tunable parameters
-	 */
-
-	drv->num_output_buffers = 32;
-        drv->playing_count = 0;
-	drv->output_front = 0;
-	drv->output_rear = 0;
-	drv->output_count = 0;
-	drv->output_active = 0;
-	drv->output_buffers = kmalloc(drv->num_output_buffers * sizeof(__u8 *), GFP_KERNEL);
-	drv->output_sizes = kmalloc(drv->num_output_buffers * sizeof(size_t), GFP_KERNEL);
-	if (!drv->output_buffers || !drv->output_sizes) goto kmalloc_failed1;
-
-	/* Allocate the pages for each output buffer. */
-	for (i = 0; i < drv->num_output_buffers; i++) {
-		drv->output_buffers[i] = (void *) __get_free_page(GFP_KERNEL);
-		if (!drv->output_buffers[i]) goto kmalloc_failed2;
-	}
+        /* Setup the circular queues of output and input buffers
+         *
+         * Each buffer is a single page, but output buffers might
+         * be partially filled (by a write with count < output_buffer_size),
+         * so each output buffer also has a paired output size.
+         *
+         * Input buffers, on the other hand, always fill completely,
+         * so we don't need input counts - each contains input_buffer_size
+         * bytes of audio data.
+         *
+         * TODO: Make number of input/output buffers tunable parameters
+         */
+
+        drv->num_output_buffers = 8;
+	drv->output_buffer_size = (4096 * 2);
+	drv->playing_count = 0;
+	drv->output_offset = 0;
+	drv->output_eof = 0;
+        drv->output_front = 0;
+        drv->output_rear = 0;
+        drv->output_count = 0;
+        drv->output_active = 0;
+        drv->output_buffers = kmalloc(drv->num_output_buffers * 
+				      sizeof(__u8 *), GFP_KERNEL);
+        drv->output_sizes = kmalloc(drv->num_output_buffers * 
+				    sizeof(size_t), GFP_KERNEL);
+        drv->output_notify = kmalloc(drv->num_output_buffers * 
+				    sizeof(char), GFP_KERNEL);
+        if (!drv->output_buffers || !drv->output_sizes || !drv->output_notify)
+	  goto kmalloc_failed1;
+
+	drv->output_buffer = kmalloc((drv->output_buffer_size * 
+					       drv->num_output_buffers),
+					      GFP_KERNEL);
+	if (!drv->output_buffer) goto kmalloc_failed2;
+
+        /* Allocate the pages for each output buffer. */
+        for (i = 0; i < drv->num_output_buffers; i++) {
+	        drv->output_buffers[i] = (void *)(drv->output_buffer + 
+					  (i * drv->output_buffer_size));
+		drv->output_sizes[i] = 0;
+		drv->output_notify[i] = 0;
+        }
 
-	/* Setup the circular queue of input buffers. */
-	drv->num_input_buffers = 32;
-        drv->recording_count = 0;
-	drv->input_front = 0;
-	drv->input_rear = 0;
-	drv->input_count = 0;
-	drv->input_active = 0;
-	drv->input_buffers = kmalloc(drv->num_input_buffers * sizeof(__u8 *), GFP_KERNEL);
-	if (!drv->input_buffers) goto kmalloc_failed3;
-
-	/* Allocate the pages for each input buffer. */
-	for (i = 0; i < drv->num_input_buffers; i++) {
-		drv->input_buffers[i] = (void *) __get_free_page(GFP_KERNEL);
-		if (!drv->input_buffers[i]) goto kmalloc_failed4;
+        /* Setup the circular queue of input buffers. */
+        drv->num_input_buffers = 8;
+	drv->input_buffer_size = (4096 * 2);
+	drv->recording_count = 0;
+        drv->input_front = 0;
+        drv->input_rear = 0;
+        drv->input_count = 0;
+	drv->input_offset = 0;
+        drv->input_size = 0;
+        drv->input_active = 0;
+        drv->input_buffers = kmalloc(drv->num_input_buffers * sizeof(__u8 *),
+				     GFP_KERNEL);
+        drv->input_sizes = kmalloc(drv->num_input_buffers * 
+				    sizeof(size_t), GFP_KERNEL);
+        if (!drv->input_buffers || !drv->input_sizes) goto kmalloc_failed3;
+
+        /* Allocate the pages for each input buffer. */
+	if (duplex == 1) {
+	  drv->input_buffer = kmalloc((drv->input_buffer_size * 
+						drv->num_input_buffers), 
+					       GFP_KERNEL);
+	  if (!drv->input_buffer) goto kmalloc_failed4;
+
+	  for (i = 0; i < drv->num_input_buffers; i++) {
+	    drv->input_buffers[i] = (void *)(drv->input_buffer + 
+					      (i * drv->input_buffer_size));
+	  }
+	} else {
+	  if (duplex == 2) {
+	    drv->input_buffer = drv->output_buffer;
+	    drv->input_buffer_size = drv->output_buffer_size;
+	    drv->num_input_buffers = drv->num_output_buffers;
+	    for (i = 0; i < drv->num_input_buffers; i++) 
+	      drv->input_buffers[i] = drv->output_buffers[i];
+	  } else {
+	    for (i = 0; i < drv->num_input_buffers; i++) 
+	      drv->input_buffers[i] = NULL;
+	  }
 	}
 
+	/* Take note of our duplexity */
+	drv->duplex = duplex;
+
 	/* Ensure that the driver is marked as not being open. */
 	drv->flags = 0;
 
 	MOD_INC_USE_COUNT;
 
-	driver = drv;
-	return 0;
+	/* Take driver slot, note which we took */
+	drv->index = dev;
+	drivers[dev] = drv;
 
+	return 0;
 
 kmalloc_failed4:
-	for (i--; i >= 0; i--)
-		free_page((unsigned long) drv->input_buffers[i]);
+	kfree(drv->input_buffer);
 
 kmalloc_failed3:
-	if (drv->input_buffers)
-		kfree(drv->input_buffers);
-	i = drv->num_output_buffers;
+        if (drv->input_sizes)
+                kfree(drv->input_sizes);
+        if (drv->input_buffers)
+                kfree(drv->input_buffers);
+        i = drv->num_output_buffers;
 
 kmalloc_failed2:
-	for (i--; i >= 0; i--)
-		free_page((unsigned long) drv->output_buffers[i]);
+	kfree(drv->output_buffer);
 
 kmalloc_failed1:
-	if (drv->output_buffers)
-		kfree(drv->output_buffers);
-	if (drv->output_sizes)
-		kfree(drv->output_sizes);
+        if (drv->output_buffers)
+                kfree(drv->output_buffers);
+        if (drv->output_sizes)
+                kfree(drv->output_sizes);
+        if (drv->output_notify)
+                kfree(drv->output_notify);
 
-	return -ENOMEM;
+        return -ENOMEM;
 }
 
-int unregister_sparcaudio_driver(struct sparcaudio_driver *drv)
+int unregister_sparcaudio_driver(struct sparcaudio_driver *drv, int duplex)
 {
-	int i;
-
-	/* Make sure that the current driver is unregistering. */
-	if (driver != drv)
+	/* Figure out which driver is unregistering */
+	if (drivers[drv->index] != drv)
 		return -EIO;
 
 	/* Deallocate the queue of output buffers. */
-	for (i = 0; i < driver->num_output_buffers; i++)
-		free_page((unsigned long) driver->output_buffers[i]);
-	kfree(driver->output_buffers);
-	kfree(driver->output_sizes);
-
-        /* Deallocate the queue of input buffers. */
-        for (i = 0; i < driver->num_input_buffers; i++)
-                free_page((unsigned long) driver->input_buffers[i]);
-        kfree(driver->input_buffers);
+	kfree(drv->output_buffer);
+	kfree(drv->output_buffers);
+	kfree(drv->output_sizes);
+	kfree(drv->output_notify);
+
+	/* Deallocate the queue of input buffers. */
+	if (duplex == 1) {
+	  kfree(drv->input_buffer);
+	  kfree(drv->input_sizes);
+	}
+	kfree(drv->input_buffers);
+
+	if (&(drv->sd_siglist) != NULL)
+	  lis_free_elist( &(drv->sd_siglist) );
 
 	MOD_DEC_USE_COUNT;
 
-	driver = NULL;
+	/* Null the appropriate driver */
+	drivers[drv->index] = NULL;
+
 	return 0;
 }
 
-void sparcaudio_output_done(struct sparcaudio_driver * drv, int reclaim)
+void sparcaudio_output_done(struct sparcaudio_driver * drv, int status)
 {
-    /* Reclaim a buffer unless it's still in the DMA pipe */
-    if (reclaim) {
-        if (drv->output_count > 0) 
-            drv->output_count--;
-        else 
-            if (drv->playing_count > 0) 
-                drv->playing_count--;
-    } else 
-        drv->playing_count++;
-
-	/* Point the queue after the "done" buffer. */
-	drv->output_size -= drv->output_sizes[drv->output_front];
-	drv->output_front = (drv->output_front + 1) % drv->num_output_buffers;
-
-	/* If the output queue is empty, shutdown the driver. */
-	if (drv->output_count == 0) {
-            if (drv->playing_count == 0) {
-		/* Stop the lowlevel driver from outputing. */
-		drv->ops->stop_output(drv);
-		drv->output_active = 0;
-
-		/* Wake up any waiting writers or syncers and return. */
-		wake_up_interruptible(&drv->output_write_wait);
-		wake_up_interruptible(&drv->output_drain_wait);
-		return;
-            }
-	}
-
-    /* If we got back a buffer, see if anyone wants to write to it */
-    if (reclaim || ((drv->output_count + drv->playing_count) 
-                    < drv->num_output_buffers))
-        wake_up_interruptible(&drv->output_write_wait);
+  /* 
+   * If !status, just restart current output.
+   * If status & 1, a buffer is finished; make it available again.
+   * If status & 2, a buffer was claimed for DMA and is still in use.
+   *
+   * The playing_count for non-DMA hardware should never be non-zero.
+   */
+  if (status & 1) {
+    if (drv->playing_count) 
+      drv->playing_count--;
+    else {
+      drv->output_count--;
+      drv->output_size -= drv->output_sizes[drv->output_front];
+      if (drv->output_notify[drv->output_front] == 1) {
+	drv->output_eof++;
+	drv->output_notify[drv->output_front] = 0;
+	kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+      }
+      drv->output_front = (drv->output_front + 1) % 
+	drv->num_output_buffers;
+    }
+  }
     
-    drv->ops->start_output(drv, drv->output_buffers[drv->output_front],
-                           drv->output_sizes[drv->output_front]);
+  if (status & 2) {
+    drv->output_count--;
+    drv->playing_count++;
+    drv->output_size -= drv->output_sizes[drv->output_front];
+    if (drv->output_notify[drv->output_front] == 1) {
+      drv->output_eof++;
+      drv->output_notify[drv->output_front] = 0;
+      kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+    }
+    drv->output_front = (drv->output_front + 1) % 
+      drv->num_output_buffers;
+  }
+
+  /* If we've played everything go inactive. */
+  if ((drv->output_count < 1) && (drv->playing_count < 1)) 
+    drv->output_active = 0;
+
+  /* If we got back a buffer, see if anyone wants to write to it */
+  if ((status & 1) || ((drv->output_count + drv->playing_count) 
+		  < drv->num_output_buffers)) 
+    wake_up_interruptible(&drv->output_write_wait);
+
+  /* If the output queue is empty, shut down the driver. */
+  if ((drv->output_count < 1) && (drv->playing_count < 1)) {
+    kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+
+    /* Stop the lowlevel driver from outputing. */
+    /* drv->ops->stop_output(drv); Should not be necessary  -- DJB 5/25/98 */
+    drv->output_active = 0;
+		  
+    /* Wake up any waiting writers or syncers and return. */
+    wake_up_interruptible(&drv->output_write_wait);
+    wake_up_interruptible(&drv->output_drain_wait);
+    return;
+  }
 
+  /* Start next block of output if we have it */
+  if (drv->output_count > 0) {
+    drv->ops->start_output(drv, drv->output_buffers[drv->output_front],
+			   drv->output_sizes[drv->output_front]);
+    drv->output_active = 1;
+  } else 
+    drv->output_active = 0;
 }
 
-void sparcaudio_input_done(struct sparcaudio_driver * drv)
+void sparcaudio_input_done(struct sparcaudio_driver * drv, int status)
 {
-	/* Point the queue after the "done" buffer. */
-	drv->input_front = (drv->input_front + 1) % drv->num_input_buffers;
-	drv->input_count++;
-
-	/* If the input queue is full, shutdown the driver. */
-	if (drv->input_count == drv->num_input_buffers) {
-		/* Stop the lowlevel driver from inputing. */
-		drv->ops->stop_input(drv);
-		drv->input_active = 0;
-	} else {
-		/* Otherwise, give the driver the next buffer. */
-		drv->ops->start_input(drv, drv->input_buffers[drv->input_front],
-				      4096);
-	}
+  /* Deal with the weird case here */
+  if (drv->duplex == 2) {
+    if (drv->input_count < drv->num_input_buffers)
+      drv->input_count++;
+    drv->ops->start_input(drv, drv->input_buffers[drv->input_front],
+			  drv->input_buffer_size);
+    wake_up_interruptible(&drv->input_read_wait);
+    return;
+  } 
+
+  /* 
+   * If status % 2, they filled a buffer for us. 
+   * If status & 2, they took a buffer from us.
+   */
+
+  if ((status % 2) == 1) {
+    drv->input_count++;
+    drv->recording_count--;
+    drv->input_size+=drv->input_buffer_size;
+  }
+
+  if (status > 1) {
+    drv->recording_count++;
+    drv->input_front = (drv->input_front + 1) % drv->num_input_buffers;
+  }
+
+  dprintk(("f%d r%d c%d u%d\n", drv->input_front, drv->input_rear, drv->input_count, drv->recording_count));
+  /* If the input queue is full, shutdown the driver. */
+  if ((drv->input_count + drv->recording_count) == drv->num_input_buffers) {
+    kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+
+    /* Stop the lowlevel driver from inputing. */
+    drv->ops->stop_input(drv);
+    drv->input_active = 0;
+  } else {
+    /* Otherwise, give the driver the next buffer. */
+    drv->ops->start_input(drv, drv->input_buffers[drv->input_front],
+			  drv->input_buffer_size);
+  }
 
-	/* Wake up any tasks that are waiting. */
-	wake_up_interruptible(&drv->input_read_wait);
+  /* Wake up any tasks that are waiting. */
+  wake_up_interruptible(&drv->input_read_wait);
 }
 
 
@@ -219,803 +377,1680 @@
  *	VFS layer interface
  */
 
-static loff_t sparcaudio_llseek(struct file * file, loff_t offset, int origin)
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static int sparcaudio_select(struct inode * inode, struct file * file,
+			    int sel_type, select_table * wait)
 {
-	return -ESPIPE;
+  struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >>
+					   SPARCAUDIO_DEVICE_SHIFT)];
+  
+  switch (sel_type) {
+  case SEL_IN:
+    if (((!file->f_flags & O_NONBLOCK) && drv->input_count) ||
+	(drv->input_size > drv->buffer_size)) {
+      dprintk(("read ready: c%d o%d\n", drv->input_count, drv->input_offset));
+      return 1;
+    }
+    select_wait(&drv->input_read_wait, wait);
+    break;
+  case SEL_OUT:
+    dprintk(("sel out: c%d o%d p%d\n", drv->output_count, drv->output_offset, drv->playing_count));
+    if ((drv->output_count + drv->playing_count) < (drv->num_output_buffers)) {
+      return 1;
+    }
+    select_wait(&drv->output_write_wait, wait);
+    break;
+  case SEL_EX:
+    break;
+  }
+
+  return 0;
+}
+#else
+static unsigned int sparcaudio_poll(struct file *file, poll_table * wait)
+{
+  unsigned int mask = 0;
+  struct inode *inode = file->f_dentry->d_inode;
+  struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >>
+                                           SPARCAUDIO_DEVICE_SHIFT)];
+
+  poll_wait(file, &drv->input_read_wait, wait);
+  poll_wait(file, &drv->output_write_wait, wait);
+  if (((!file->f_flags & O_NONBLOCK) && drv->input_count) ||
+      (drv->input_size > drv->buffer_size)) {
+    mask |= POLLIN | POLLRDNORM;
+  }
+  if ((drv->output_count + drv->playing_count) < (drv->num_output_buffers)) {
+    mask |= POLLOUT | POLLWRNORM;
+  }
+  return mask;
 }
+#endif
 
-static ssize_t sparcaudio_read(struct file * file,
-			       char *buf, size_t count, loff_t *ppos)
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static int sparcaudio_lseek(struct inode * inode, struct file * file,
+			    off_t offset, int origin)
+#else
+static loff_t sparcaudio_lseek(struct file * file, loff_t offset, int origin)
+#endif
 {
-	int bytes_to_copy;
+	return -ESPIPE;
+}
 
-	if (! file->f_mode & FMODE_READ)
-		return -EINVAL;
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static int sparcaudio_read(struct inode * inode, struct file * file,
+			   char *buf, int count)
+#else
+static ssize_t sparcaudio_read(struct file * file, char *buf, 
+                               size_t count, loff_t *ppos)
+#endif
+{
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff
+  struct inode *inode = file->f_dentry->d_inode;
+#endif
+  struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >>
+					   SPARCAUDIO_DEVICE_SHIFT)];
+  int bytes_to_copy, bytes_read = 0, err;
 
-	if (driver->input_count == 0) {
-		interruptible_sleep_on(&driver->input_read_wait);
-		if (signal_pending(current))
-			return -EINTR;
-	}
+  if (! file->f_mode & FMODE_READ)
+    return -EINVAL;
 
-	bytes_to_copy = 4096 - driver->input_offset;
-	if (bytes_to_copy > count)
-		bytes_to_copy = count;
-
-	copy_to_user_ret(buf, driver->input_buffers[driver->input_rear]+driver->input_offset,
-			 bytes_to_copy, -EFAULT);
-	driver->input_offset += bytes_to_copy;
-
-	if (driver->input_offset >= 4096) {
-		driver->input_rear = (driver->input_rear + 1) % driver->num_input_buffers;
-		driver->input_count--;
-		driver->input_offset = 0;
-	}
+  if ((file->f_flags & O_NONBLOCK) && (drv->input_size < count))
+    return -EAGAIN;
+    
+  while (count > 0) {
+    if (drv->input_count == 0) {
+      /* This *should* never happen. */
+      if (file->f_flags & O_NONBLOCK) {
+	printk("Warning: audio input leak!\n");
+	return -EAGAIN;
+      }
+      interruptible_sleep_on(&drv->input_read_wait);
+      if (signal_pending(current))
+	return -EINTR;
+    }
+  
+    bytes_to_copy = drv->input_buffer_size - drv->input_offset;
+    
+    if (bytes_to_copy > count)
+      bytes_to_copy = count;
 
-	return bytes_to_copy;
-}
+    err = verify_area(VERIFY_WRITE, buf, bytes_to_copy);
+    if (err)
+      return err;
+
+    copy_to_user(buf, drv->input_buffers[drv->input_rear]+drv->input_offset, 
+                 bytes_to_copy);
+
+    drv->input_offset += bytes_to_copy;
+    drv->input_size -= bytes_to_copy;
+    buf += bytes_to_copy;
+    count -= bytes_to_copy;
+    bytes_read += bytes_to_copy;
+
+    if (drv->input_offset >= drv->input_buffer_size) {
+      drv->input_rear = (drv->input_rear + 1) % 
+	drv->num_input_buffers;
+      drv->input_count--;
+      drv->input_offset = 0;
+    }
+    /* If we're in "loop audio" mode, try waking up the other side
+     * in case they're waiting for us to eat a block. 
+     */
+    if (drv->duplex == 2) {
+      wake_up_interruptible(&drv->output_write_wait);
+    }
+  }
 
-static void sparcaudio_reorganize_buffers(struct sparcaudio_driver * driver)
-{
-  /* It may never matter but if it does this routine will pack */
-  /* buffers to free space for more data */
+  return bytes_read;
 }
 
-static void sparcaudio_sync_output(struct sparcaudio_driver * driver)
+static void sparcaudio_sync_output(struct sparcaudio_driver * drv)
 {
-	unsigned long flags;
+  unsigned long flags;
 
-	/* If the low-level driver is not active, activate it. */
-	save_and_cli(flags);
-        if ((!driver->output_active) && (driver->output_count > 0)) {
-            driver->ops->start_output(driver,
-                                      driver->output_buffers[driver->output_front],
-                                      driver->output_sizes[driver->output_front]);
-            driver->output_active = 1;
-	}
-	restore_flags(flags);
+  /* If the low-level driver is not active, activate it. */
+  save_and_cli(flags);
+  if ((!drv->output_active) && (drv->output_count > 0)) {
+    drv->ops->start_output(drv, 
+			   drv->output_buffers[drv->output_front],
+			   drv->output_sizes[drv->output_front]);
+    drv->output_active = 1;
+  }
+  restore_flags(flags);
 }
 
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static int sparcaudio_write(struct inode * inode, struct file * file,
+			    const char *buf, int count)
+#else
 static ssize_t sparcaudio_write(struct file * file, const char *buf,
-				size_t count, loff_t *ppos)
+                                size_t count, loff_t *ppos)
+#endif
 {
-	int bytes_written = 0, bytes_to_copy;
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff
+  struct inode *inode = file->f_dentry->d_inode;
+#endif
+  struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >>
+					   SPARCAUDIO_DEVICE_SHIFT)];
+  int bytes_written = 0, bytes_to_copy, err;
+  
+  if (! file->f_mode & FMODE_WRITE)
+    return -EINVAL;
+
+  /* 
+   * A signal they want notification when this is processed. Too bad
+   * sys_write doesn't tell us unless you patch it, in 2.0 kernels.
+   */
+  if (count == 0) {
+#ifndef notdef
+    drv->output_eof++;
+    kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+#else
+    /* Nice code, but the world isn't ready yet... */
+    drv->output_notify[drv->output_rear] = 1;
+#endif
+  }
 
-        if (! file->f_mode & FMODE_WRITE)
-            return -EINVAL;
+  /* Loop until all output is written to device. */
+  while (count > 0) {
+    /* Check to make sure that an output buffer is available. */
+    if (drv->num_output_buffers == (drv->output_count + drv->playing_count)) {
+      /* We need buffers, so... */
+      sparcaudio_sync_output(drv);
+      if (file->f_flags & O_NONBLOCK) {
+	return -EAGAIN;
+      }
+      interruptible_sleep_on(&drv->output_write_wait);
+      if (signal_pending(current))
+	return bytes_written > 0 ? bytes_written : -EINTR;
+    }
 
-	/* Loop until all output is written to device. */
-	while (count > 0) {
-            /* Check to make sure that an output buffer is available. */
-            /* If not, make valiant attempt */
-            if (driver->num_output_buffers == 
-                (driver->output_count + driver->playing_count))
-                sparcaudio_reorganize_buffers(driver);
-            
-            if (driver->num_output_buffers == 
-                (driver->output_count + driver->playing_count)) {
-                /* We need buffers, so... */
-                sparcaudio_sync_output(driver);
-                interruptible_sleep_on(&driver->output_write_wait);
-                if (signal_pending(current))
-                    return bytes_written > 0 ? bytes_written : -EINTR;
-		}
+    /* No buffers were freed. Go back to sleep */
+    if (drv->num_output_buffers == (drv->output_count + drv->playing_count)) 
+      continue;
+
+    /* Deal with the weird case of a reader in the write area by trying to
+     * let them keep ahead of us... Go to sleep until they start servicing.
+     */
+    if ((drv->duplex == 2) && (drv->flags & SDF_OPEN_READ) &&
+	(drv->output_rear == drv->input_rear) && (drv->input_count > 0)) {
+      if (file->f_flags & O_NONBLOCK) {
+	return -EAGAIN;
+      }
+      interruptible_sleep_on(&drv->output_write_wait);
+      if (signal_pending(current))
+	return bytes_written > 0 ? bytes_written : -EINTR;
+    }
 
-            /* No buffers were freed. Go back to sleep */
-            if (driver->num_output_buffers == 
-                (driver->output_count + driver->playing_count)) 
-                continue;
-
-            /* Determine how much we can copy in this iteration. */
-            bytes_to_copy = count;
-            if (bytes_to_copy > 4096)
-                bytes_to_copy = 4096;
-
-            copy_from_user_ret(driver->output_buffers[driver->output_rear],
-			       buf, bytes_to_copy, -EFAULT);
-
-            /* Update the queue pointers. */
-            buf += bytes_to_copy;
-            count -= bytes_to_copy;
-            bytes_written += bytes_to_copy;
-            driver->output_sizes[driver->output_rear] = bytes_to_copy;
-            driver->output_rear = (driver->output_rear + 1) % driver->num_output_buffers;
-            driver->output_count++;
-            driver->output_size += bytes_to_copy;
-	}
-        sparcaudio_sync_output(driver);
+    /* Determine how much we can copy in this iteration. */
+    bytes_to_copy = count;
+    if (bytes_to_copy > drv->output_buffer_size - drv->output_offset)
+      bytes_to_copy = drv->output_buffer_size - drv->output_offset;
+    
+    err = verify_area(VERIFY_READ, buf, bytes_to_copy);
+    if (err)
+      return err;
 
-	/* Return the number of bytes written to the caller. */
-	return bytes_written;
+    copy_from_user(drv->output_buffers[drv->output_rear]+drv->output_offset, buf, bytes_to_copy);
+    
+    /* Update the queue pointers. */
+    buf += bytes_to_copy;
+    count -= bytes_to_copy;
+    bytes_written += bytes_to_copy;
+
+    /* A block can get orphaned in a flush and not cleaned up. */
+    if (drv->output_offset)
+      drv->output_sizes[drv->output_rear] += bytes_to_copy;
+    else
+      drv->output_sizes[drv->output_rear] = bytes_to_copy;
+
+    drv->output_notify[drv->output_rear] = 0;
+
+    if (drv->output_sizes[drv->output_rear] == drv->output_buffer_size) {
+      drv->output_rear = (drv->output_rear + 1) 
+	% drv->num_output_buffers;
+      drv->output_count++;
+      drv->output_offset = 0;
+    } else
+      drv->output_offset += bytes_to_copy;
+
+    drv->output_size+=bytes_to_copy;
+  }
+
+  sparcaudio_sync_output(drv);
+  
+  /* Return the number of bytes written to the caller. */
+  return bytes_written;
 }
 
-#define COPY_IN(arg, get) get_user(get, (int *)arg)
-#define COPY_OUT(arg, ret) put_user(ret, (int *)arg)
-
 /* Add these in as new devices are supported. Belongs in audioio.h, actually */
-#define SUPPORTED_MIXER_DEVICES         (SOUND_MASK_VOLUME)
-#define MONO_DEVICES (SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_SPEAKER | SOUND_MASK_MIC)
+#define MONO_DEVICES (SOUND_MASK_SPEAKER | SOUND_MASK_MIC)
 
-static inline int sparcaudio_mixer_ioctl(struct inode * inode, struct file * file,
-					 unsigned int cmd, unsigned long arg)
+static int sparcaudio_mixer_ioctl(struct inode * inode, struct file * file,
+			    unsigned int cmd, unsigned long arg)
 {
-	int i = 0, j = 0;
-	if (_IOC_DIR(cmd) & _IOC_WRITE) {
-		/* For any missing routines, pretend we changed things anyhow for now */
-		switch (cmd & 0xff) {
-		case SOUND_MIXER_VOLUME:
-			if (driver->ops->get_output_channels)
-				j = driver->ops->get_output_channels(driver);
-			COPY_IN(arg, i);
-			if (j == 1) {
-				i = s_to_m(i);
-				if (driver->ops->set_output_volume)
-					driver->ops->set_output_volume(driver, i * 255/100);
-				if (driver->ops->get_output_volume)
-					i = driver->ops->get_output_volume(driver);
-				i = m_to_s(i);
-			} else {
-				/* there should be stuff here which calculates balance and
-				   volume on a stereo device. will do it eventually */
-				i = s_to_g(i);
-				if (driver->ops->set_output_volume)
-					driver->ops->set_output_volume(driver, i * 255/100);
-				if (driver->ops->get_output_volume)
-					i = driver->ops->get_output_volume(driver);
-				j = s_to_b(i);
-				if (driver->ops->set_output_balance)
-					driver->ops->set_output_balance(driver, j);
-				if (driver->ops->get_output_balance)
-					j = driver->ops->get_output_balance(driver);
-				i = b_to_s(i,j);
-			}
-			return COPY_OUT(arg, i);
-		default:
-			/* Play like we support other things */
-			return COPY_OUT(arg, i);
-		}
-	} else {
-		switch (cmd & 0xff) {
-		case SOUND_MIXER_RECSRC:
-			if (driver->ops->get_input_port)
-				i = driver->ops->get_input_port(driver);
-			/* only one should ever be selected */
-			if (i & AUDIO_ANALOG_LOOPBACK) j = SOUND_MASK_IMIX; /* ? */
-			if (i & AUDIO_CD) j = SOUND_MASK_CD;
-			if (i & AUDIO_LINE_IN) j = SOUND_MASK_LINE;
-			if (i & AUDIO_MICROPHONE) j = SOUND_MASK_MIC;
-
-			return COPY_OUT(arg, j);
-
-		case SOUND_MIXER_RECMASK:
-			if (driver->ops->get_input_ports)
-				i = driver->ops->get_input_ports(driver);
-			/* what do we support? */
-			if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC;
-			if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE;
-			if (i & AUDIO_CD) j |= SOUND_MASK_CD;
-			if (i & AUDIO_ANALOG_LOOPBACK) j |= SOUND_MASK_IMIX; /* ? */
-
-			return COPY_OUT(arg, j);
-
-		case SOUND_MIXER_CAPS: /* mixer capabilities */
-			i = SOUND_CAP_EXCL_INPUT;
-			return COPY_OUT(arg, i);
-
-		case SOUND_MIXER_DEVMASK: /* all supported devices */
-		case SOUND_MIXER_STEREODEVS: /* what supports stereo */
-			if (driver->ops->get_input_ports)
-				i = driver->ops->get_input_ports(driver);
-			/* what do we support? */
-			if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC;
-			if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE;
-			if (i & AUDIO_CD) j |= SOUND_MASK_CD;
-			if (i & AUDIO_ANALOG_LOOPBACK) j |= SOUND_MASK_IMIX; /* ? */
-
-			if (driver->ops->get_output_ports)
-				i = driver->ops->get_output_ports(driver);
-			if (i & AUDIO_SPEAKER) j |= SOUND_MASK_SPEAKER;
-			if (i & AUDIO_HEADPHONE) j |= SOUND_MASK_LINE; /* ? */
-			if (i & AUDIO_LINE_OUT) j |= SOUND_MASK_LINE;
-			
-			j |= SOUND_MASK_VOLUME;
-
-			if ((cmd & 0xff) == SOUND_MIXER_STEREODEVS)
-			j &= ~(MONO_DEVICES);
-			return COPY_OUT(arg, j);
-
-		case SOUND_MIXER_VOLUME:
-			if (driver->ops->get_output_channels)
-				j = driver->ops->get_output_channels(driver);
-			if (j == 1) {
-				if (driver->ops->get_output_volume)
-					i = driver->ops->get_output_volume(driver);
-				i = m_to_s(i);
-			} else {
-				/* there should be stuff here which calculates balance and
-				   volume on a stereo device. will do it eventually */
-				if (driver->ops->get_output_volume)
-					i = driver->ops->get_output_volume(driver);
-				if (driver->ops->get_output_balance)
-					j = driver->ops->get_output_balance(driver);
-				i = b_to_s(i,j);
-			}
-			return COPY_OUT(arg, i);
-
-		default:
-			/* Play like we support other things */
-			return COPY_OUT(arg, i);
-		}
-	}
+  struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >>
+					   SPARCAUDIO_DEVICE_SHIFT)];
+  unsigned long i = 0, j = 0, k = 0;
+
+  k = (unsigned long) &arg;
+
+  switch (cmd) {
+  case SOUND_MIXER_WRITE_RECLEV:
+  case SOUND_MIXER_WRITE_MIC:
+  case SOUND_MIXER_WRITE_CD:
+  case SOUND_MIXER_WRITE_LINE:
+    tprintk(("setting input volume (0x%x)", k));
+    if (drv->ops->get_input_channels)
+      j = drv->ops->get_input_channels(drv);
+    if (j == 1) {
+      i = s_to_m(k);
+      tprintk((" for mono to %d\n", i));
+      if (drv->ops->set_input_volume)
+	drv->ops->set_input_volume(drv, i);
+      if (drv->ops->get_input_volume)
+	i = drv->ops->get_input_volume(drv);
+      i = m_to_s(i);
+    } else {
+      i = s_to_g(k);
+      j = s_to_b(k);
+      tprintk((" for stereo to to %d (bal %d)\n", i, j));
+      if (drv->ops->set_input_volume)
+	drv->ops->set_input_volume(drv, i);
+      if (drv->ops->get_input_volume)
+	i = drv->ops->get_input_volume(drv);
+      if (drv->ops->set_input_balance)
+	drv->ops->set_input_balance(drv, j);
+      if (drv->ops->get_input_balance)
+	j = drv->ops->get_input_balance(drv);
+      i = b_to_s(i,j);
+    }
+    return COPY_OUT(arg, i);
+  case SOUND_MIXER_WRITE_PCM:
+  case SOUND_MIXER_WRITE_VOLUME:
+  case SOUND_MIXER_WRITE_SPEAKER:
+    tprintk(("setting output volume (0x%x)", k));
+    if (drv->ops->get_output_channels)
+      j = drv->ops->get_output_channels(drv);
+    if (j == 1) {
+      i = s_to_m(k);
+      tprintk((" for mono to %d\n", i));
+      if (drv->ops->set_output_volume)
+	drv->ops->set_output_volume(drv, i);
+      if (drv->ops->get_output_volume)
+	i = drv->ops->get_output_volume(drv);
+      i = m_to_s(i);
+    } else {
+      i = s_to_g(k);
+      j = s_to_b(k);
+      tprintk((" for stereo to to %d (bal %d)\n", i, j));
+      if (drv->ops->set_output_volume)
+	drv->ops->set_output_volume(drv, i);
+      if (drv->ops->get_output_volume)
+	i = drv->ops->get_output_volume(drv);
+      if (drv->ops->set_output_balance)
+	drv->ops->set_output_balance(drv, j);
+      if (drv->ops->get_output_balance)
+	j = drv->ops->get_output_balance(drv);
+      i = b_to_s(i,j);
+    }
+    return COPY_OUT(arg, i);
+  case SOUND_MIXER_READ_RECSRC: 
+    if (drv->ops->get_input_port)
+      i = drv->ops->get_input_port(drv);
+    /* only one should ever be selected */
+    if (i & AUDIO_ANALOG_LOOPBACK) j = SOUND_MASK_IMIX; /* ? */
+    if (i & AUDIO_CD) j = SOUND_MASK_CD;
+    if (i & AUDIO_LINE_IN) j = SOUND_MASK_LINE;
+    if (i & AUDIO_MICROPHONE) j = SOUND_MASK_MIC;
+    
+    return COPY_OUT(arg, j);
+  case SOUND_MIXER_WRITE_RECSRC: 
+    if (!drv->ops->set_input_port)
+      return -EINVAL;
+    if (arg & SOUND_MASK_IMIX) j |= AUDIO_ANALOG_LOOPBACK;
+    if (arg & SOUND_MASK_CD) j |= AUDIO_CD;
+    if (arg & SOUND_MASK_LINE) j |= AUDIO_LINE_IN;
+    if (arg & SOUND_MASK_MIC) j |= AUDIO_MICROPHONE;
+    tprintk(("setting inport to %d\n", j));
+    i = drv->ops->set_input_port(drv, j);
+    
+    return COPY_OUT(arg, i);
+  case SOUND_MIXER_READ_RECMASK: 
+    if (drv->ops->get_input_ports)
+      i = drv->ops->get_input_ports(drv);
+    /* what do we support? */
+    if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC;
+    if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE;
+    if (i & AUDIO_CD) j |= SOUND_MASK_CD;
+    if (i & AUDIO_ANALOG_LOOPBACK) j |= SOUND_MASK_IMIX; /* ? */
+    
+    return COPY_OUT(arg, j);
+  case SOUND_MIXER_READ_CAPS: /* mixer capabilities */
+    i = SOUND_CAP_EXCL_INPUT;
+    return COPY_OUT(arg, i);
+
+  case SOUND_MIXER_READ_DEVMASK: /* all supported devices */
+  case SOUND_MIXER_READ_STEREODEVS: /* what supports stereo */
+    if (drv->ops->get_input_ports)
+      i = drv->ops->get_input_ports(drv);
+    /* what do we support? */
+    if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC;
+    if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE;
+    if (i & AUDIO_CD) j |= SOUND_MASK_CD;
+    if (i & AUDIO_ANALOG_LOOPBACK) j |= SOUND_MASK_IMIX; /* ? */
+    
+    if (drv->ops->get_output_ports)
+      i = drv->ops->get_output_ports(drv);
+    if (i & AUDIO_SPEAKER) j |= SOUND_MASK_SPEAKER;
+    if (i & AUDIO_HEADPHONE) j |= SOUND_MASK_LINE; /* ? */
+    if (i & AUDIO_LINE_OUT) j |= SOUND_MASK_LINE;
+    
+    j |= SOUND_MASK_VOLUME;
+    
+    if (cmd == SOUND_MIXER_READ_STEREODEVS)
+      j &= ~(MONO_DEVICES);
+    return COPY_OUT(arg, j);
+  case SOUND_MIXER_READ_PCM:
+  case SOUND_MIXER_READ_SPEAKER:
+  case SOUND_MIXER_READ_VOLUME:
+    if (drv->ops->get_output_channels)
+      j = drv->ops->get_output_channels(drv);
+    if (j == 1) {
+      if (drv->ops->get_output_volume)
+	i = drv->ops->get_output_volume(drv);
+      i = m_to_s(i);
+    } else {
+      if (drv->ops->get_output_volume)
+	i = drv->ops->get_output_volume(drv);
+      if (drv->ops->get_output_balance)
+	j = drv->ops->get_output_balance(drv);
+      i = b_to_s(i,j);
+    }
+    return COPY_OUT((int *)arg, i);
+  case SOUND_MIXER_READ_RECLEV:
+  case SOUND_MIXER_READ_MIC:
+  case SOUND_MIXER_READ_CD:
+  case SOUND_MIXER_READ_LINE:
+    if (drv->ops->get_input_channels)
+      j = drv->ops->get_input_channels(drv);
+    if (j == 1) {
+      if (drv->ops->get_input_volume)
+	i = drv->ops->get_input_volume(drv);
+      i = m_to_s(i);
+    } else {
+      if (drv->ops->get_input_volume)
+	i = drv->ops->get_input_volume(drv);
+      if (drv->ops->get_input_balance)
+	j = drv->ops->get_input_balance(drv);
+      i = b_to_s(i,j);
+    }
+    return COPY_OUT((int *)arg, i);
+  default:
+    return -EINVAL;
+  }
 }
 
-static int sparcaudio_ioctl(struct inode * inode, struct file * file,
-			    unsigned int cmd, unsigned long arg)
+/* AUDIO_SETINFO uses these to set values if possible. */
+static __inline__ int 
+__sparcaudio_if_set_do(struct sparcaudio_driver *drv, 
+		       int (*set_function)(struct sparcaudio_driver *, int), 
+		       int (*get_function)(struct sparcaudio_driver *), 
+		       unsigned int value)
 {
-	int retval = 0;
-	struct audio_info ainfo;
-
-        if (((cmd >> 8) & 0xff) == 'M') {
-            return sparcaudio_mixer_ioctl(inode, file, cmd, arg);
-        }
-
-	switch (cmd) {
-	case SNDCTL_DSP_SYNC:
-	case AUDIO_DRAIN:
-		if (driver->output_count > 0) {
-			interruptible_sleep_on(&driver->output_drain_wait);
-			retval = signal_pending(current) ? -EINTR : 0;
-		}
-		break;
-
-        case AUDIO_FLUSH:
-                if (driver->output_active && (file->f_mode & FMODE_WRITE)) {
-                    wake_up_interruptible(&driver->output_write_wait);
-                    driver->ops->stop_output(driver);
-                    driver->output_active = 0;
-                    driver->output_front = 0;
-                    driver->output_rear = 0;
-                    driver->output_count = 0;
-                    driver->output_size = 0;
-                    driver->playing_count = 0;
-                }
-                if (driver->input_active && (file->f_mode & FMODE_READ)) {
-                    wake_up_interruptible(&driver->input_read_wait);
-                    driver->ops->stop_input(driver);
-                    driver->input_active = 0;
-                    driver->input_front = 0;
-                    driver->input_rear = 0;
-                    driver->input_count = 0;
-                    driver->recording_count = 0;
-                }
-                if ((file->f_mode & FMODE_READ) && 
-                    !(driver->flags & SDF_OPEN_READ)) {
-                    driver->ops->start_input(driver, 
-                                             driver->input_buffers[driver->input_front],
-                                             4096);
-                    driver->input_active = 1;
-                    }
-                if ((file->f_mode & FMODE_WRITE) && 
-                    !(driver->flags & SDF_OPEN_WRITE)) {
-                    sparcaudio_sync_output(driver);
-                    }
-                break;
-            
-
-	case AUDIO_GETDEV:
-		if (driver->ops->sunaudio_getdev) {
-			audio_device_t tmp;
-
-			driver->ops->sunaudio_getdev(driver, &tmp);
-
-			copy_to_user_ret((audio_device_t *)arg, &tmp, sizeof(tmp), -EFAULT);
-		} else
-			retval = -EINVAL;
-
-		break;
-
-	case AUDIO_GETDEV_SUNOS:
-		if (driver->ops->sunaudio_getdev_sunos) {
-			int tmp=driver->ops->sunaudio_getdev_sunos(driver);
-
-			if (put_user(tmp, (int *)arg))
-				retval = -EFAULT;
-		} else
-			retval = -EINVAL;
-
-		break;
-
-	case AUDIO_GETINFO:
-
-		AUDIO_INITINFO(&ainfo);
-
-		if (driver->ops->get_input_rate)
-		  ainfo.record.sample_rate =
-		    driver->ops->get_input_rate(driver);
-		if (driver->ops->get_input_channels)
-		  ainfo.record.channels =
-		    driver->ops->get_input_channels(driver);
-		if (driver->ops->get_input_precision)
-		  ainfo.record.precision =
-		    driver->ops->get_input_precision(driver);
-		if (driver->ops->get_input_encoding)
-		  ainfo.record.encoding =
-		    driver->ops->get_input_encoding(driver);
-		if (driver->ops->get_input_volume)
-		  ainfo.record.gain =
-		    driver->ops->get_input_volume(driver);
-		if (driver->ops->get_input_port)
-		  ainfo.record.port =
-		    driver->ops->get_input_port(driver);
-		if (driver->ops->get_input_ports)
-		  ainfo.record.avail_ports =
-		    driver->ops->get_input_ports(driver);
-		ainfo.record.buffer_size = 4096;
-		ainfo.record.samples = 0;
-		ainfo.record.eof = 0;
-		ainfo.record.pause = 0;
-		ainfo.record.error = 0;
-		ainfo.record.waiting = 0;
-		if (driver->ops->get_input_balance)
-		  ainfo.record.balance =
-		    driver->ops->get_input_balance(driver);
-		ainfo.record.minordev = 4;
-		ainfo.record.open = 1;
-		ainfo.record.active = 0;
-
-		if (driver->ops->get_output_rate)
-		  ainfo.play.sample_rate =
-		    driver->ops->get_output_rate(driver);
-		if (driver->ops->get_output_channels)
-		  ainfo.play.channels =
-		    driver->ops->get_output_channels(driver);
-		if (driver->ops->get_output_precision)
-		  ainfo.play.precision =
-		    driver->ops->get_output_precision(driver);
-		if (driver->ops->get_output_encoding)
-		  ainfo.play.encoding =
-		    driver->ops->get_output_encoding(driver);
-		if (driver->ops->get_output_volume)
-		  ainfo.play.gain =
-		    driver->ops->get_output_volume(driver);
-		if (driver->ops->get_output_port)
-		  ainfo.play.port =
-		    driver->ops->get_output_port(driver);
-		if (driver->ops->get_output_ports)
-		  ainfo.play.avail_ports =
-		    driver->ops->get_output_ports(driver);
-                /* This is not defined in the play context in Solaris */
-		ainfo.play.buffer_size = 0;
-		ainfo.play.samples = 0;
-		ainfo.play.eof = 0;
-		ainfo.play.pause = 0;
-		ainfo.play.error = 0;
-		ainfo.play.waiting = waitqueue_active(&driver->open_wait);
-		if (driver->ops->get_output_balance)
-		  ainfo.play.balance =
-                      (unsigned char)driver->ops->get_output_balance(driver);
-		ainfo.play.minordev = 4;
-		ainfo.play.open = 1;
-		ainfo.play.active = driver->output_active;
-
-		if (driver->ops->get_monitor_volume)
-		  ainfo.monitor_gain =
-		    driver->ops->get_monitor_volume(driver);
-
-		if (driver->ops->get_output_muted)
-		  ainfo.output_muted =
-                      (unsigned char)driver->ops->get_output_muted(driver);
-
-		copy_to_user_ret((struct audio_info *)arg, &ainfo,
-				 sizeof(ainfo), -EFAULT);
+  if (set_function && Modify(value))
+    return (int)set_function(drv, value);
+  else if (get_function)
+    return (int)get_function(drv);
+  else 
+    return 0;
+}
 
-		break;
+static __inline__ int 
+__sparcaudio_if_setc_do(struct sparcaudio_driver *drv, 
+			int (*set_function)(struct sparcaudio_driver *, int), 
+			int (*get_function)(struct sparcaudio_driver *), 
+			unsigned char value)
+{
+  if (set_function && Modifyc(value))
+    return (char)set_function(drv, (int)value);
+  else if (get_function)
+    return (char)get_function(drv);
+  else 
+    return 0;
+}
 
-	case AUDIO_SETINFO:
-	  {
-	    audio_info_t curinfo, newinfo;
+/* I_FLUSH, I_{G,S}ETSIG, I_NREAD provided for SunOS compatibility
+ *
+ * I must admit I'm quite ashamed of the state of the ioctl handling,
+ * but I do have several optimizations which I'm planning. -- DJB
+ */
 
-	    copy_from_user_ret(&ainfo, (audio_info_t *) arg, sizeof(audio_info_t), -EFAULT);
+static int sparcaudio_ioctl(struct inode * inode, struct file * file,
+			    unsigned int cmd, unsigned long arg)
+{
+	int retval = 0, i, j, k;
+	int minor = MINOR(inode->i_rdev);
+	struct audio_info ainfo;
+	audio_buf_info binfo;
+	count_info cinfo;
+	struct sparcaudio_driver *drv = 
+	  drivers[(minor >> SPARCAUDIO_DEVICE_SHIFT)];
+
+	switch (minor & 0xf) {
+	case SPARCAUDIO_MIXER_MINOR:
+	  return sparcaudio_mixer_ioctl(inode, file, cmd, arg);
+	case SPARCAUDIO_DSP16_MINOR:
+        case SPARCAUDIO_DSP_MINOR:
+	case SPARCAUDIO_AUDIO_MINOR:
+	case SPARCAUDIO_AUDIOCTL_MINOR:
+	  switch (cmd) {
+	  case I_GETSIG:
+	  case I_GETSIG_SOLARIS:
+	    j = (int)lis_get_elist_ent(drv->sd_siglist,current->pid);
+	    COPY_OUT(arg, j);
+	    retval = drv->input_count;
+	    break;
 
-	    /* Without these there's no point in trying */
-	    if (!driver->ops->get_input_precision ||
-		!driver->ops->get_input_channels ||
-		!driver->ops->get_input_rate ||
-		!driver->ops->get_input_encoding ||
-		!driver->ops->get_output_precision ||
-		!driver->ops->get_output_channels ||
-                !driver->ops->get_output_rate ||
-                !driver->ops->get_output_encoding) 
-	      {
-		retval = -EINVAL;
-		break;
+	  case I_SETSIG:
+	  case I_SETSIG_SOLARIS:
+	    if ((minor & 0xf) == SPARCAUDIO_AUDIOCTL_MINOR) {
+	      if (!arg){
+		if (lis_del_from_elist(&(drv->sd_siglist),current->pid,S_ALL))
+		  retval = -EINVAL;
+		else 
+		  if (!drv->sd_siglist)
+		    drv->sd_sigflags=0;
 	      }
-
-	    /* Do bounds checking for things which always apply.
-	     * Follow with enforcement of basic tenets of certain
-	     * encodings. Everything over and above generic is
-	     * enforced by the driver, which can assume that
-	     * Martian cases are taken care of here. */
-	    if (Modify(ainfo.play.gain) && 
-		((ainfo.play.gain > AUDIO_MAX_GAIN) || 
-		 (ainfo.play.gain < AUDIO_MIN_GAIN))) {
-	      /* Need to differentiate this from e.g. the above error */
+	      else 
+		if (lis_add_to_elist(&(drv->sd_siglist),current->pid,
+				     (short)arg))
+		  retval = -EAGAIN;
+		else
+		  ((drv->sd_sigflags) |= (arg));
+	    }
+	    break;
+	  case I_NREAD:
+	  case I_NREAD_SOLARIS:
+	    /* According to the Solaris man page, this copies out
+	     * the size of the first streams buffer and returns 
+             * the number of streams messages on the read queue as
+	     * as its retval. (streamio(7I)) This should work. */
+             
+	    j = (drv->input_count > 0) ? drv->input_buffer_size : 0;
+	    COPY_OUT(arg, j);
+	    retval = drv->input_count;
+	    break;
+	    /*
+	     * A poor substitute until we do true resizable buffers.
+	     */
+	  case SNDCTL_DSP_GETISPACE:
+	    binfo.fragstotal = drv->num_input_buffers;
+	    binfo.fragments = drv->num_input_buffers - 
+	      (drv->input_count + drv->recording_count);
+	    binfo.fragsize = drv->input_buffer_size;
+	    binfo.bytes = binfo.fragments*binfo.fragsize;
+	    
+	    retval = verify_area(VERIFY_WRITE, (int *)arg, sizeof(binfo));
+	    if (retval) break; 
+	    copy_to_user(&((char *)arg)[0], (char *)&binfo, sizeof(binfo));
+	    break;
+	  case SNDCTL_DSP_GETOSPACE:
+	    binfo.fragstotal = drv->num_output_buffers;
+	    binfo.fragments = drv->num_output_buffers - 
+	      (drv->output_count + drv->playing_count + 
+	       (drv->output_offset ? 1 : 0));
+	    binfo.fragsize = drv->output_buffer_size;
+	    binfo.bytes = binfo.fragments*binfo.fragsize + 
+	      (drv->output_buffer_size - drv->output_offset);
+	    
+	    retval = verify_area(VERIFY_WRITE, (int *)arg, sizeof(binfo));
+	    if (retval) break; 
+	    copy_to_user(&((char *)arg)[0], (char *)&binfo, sizeof(binfo));
+	    break;
+	  case SNDCTL_DSP_GETIPTR:
+	  case SNDCTL_DSP_GETOPTR:
+	    /*
+	     * int bytes (number of bytes read/written since last)
+             * int blocks (number of frags read/wrote since last call)
+             * int ptr (current position of dma in buffer)
+	     */
+	    retval = 0;
+	    cinfo.bytes = 0;
+	    cinfo.ptr = 0;
+	    cinfo.blocks = 0;
+	    cinfo.bytes += cinfo.ptr;
+	    
+	    retval = verify_area(VERIFY_WRITE, (int *)arg, sizeof(cinfo));
+	    if (retval) break; 
+	    copy_to_user(&((char *)arg)[0], (char *)&cinfo, sizeof(cinfo));
+	    break;
+	  case SNDCTL_DSP_SETFRAGMENT:
+            /* XXX Small hack to get ESD/Enlightenment to work.  --DaveM */
+            retval = 0;
+            break;
+
+	  case SNDCTL_DSP_SUBDIVIDE:
+	    /*
+	     * I don't understand what I need to do yet.
+	     */
+	    retval = -EINVAL;
+	    break;
+	  case SNDCTL_DSP_SETTRIGGER:
+	    /* This may not be 100% correct */
+	    if ((arg & PCM_ENABLE_INPUT) && drv->ops->get_input_pause &&
+		drv->ops->set_input_pause) {
+	      if (drv->ops->get_input_pause(drv))
+		drv->ops->set_input_pause(drv, 0);
+	    } else {
+	      if (!drv->ops->get_input_pause(drv))
+		drv->ops->set_input_pause(drv, 1);
+	    }
+	    if ((arg & PCM_ENABLE_OUTPUT) && drv->ops->get_output_pause &&
+		drv->ops->set_output_pause) {
+	      if (drv->ops->get_output_pause(drv))
+		drv->ops->set_output_pause(drv, 0);
+	    } else {
+	      if (!drv->ops->get_output_pause(drv))
+		drv->ops->set_output_pause(drv, 1);
+	    }
+	    break;
+	  case SNDCTL_DSP_GETTRIGGER:
+            j = 0;
+	    if (drv->ops->get_input_pause)
+	      if (drv->ops->get_input_pause(drv))
+		j = PCM_ENABLE_INPUT;
+	    if (drv->ops->get_output_pause)
+	      if (drv->ops->get_output_pause(drv))
+		j |= PCM_ENABLE_OUTPUT;
+	    COPY_OUT(arg, j);
+	    break;
+	  case SNDCTL_DSP_GETBLKSIZE:
+	    j = drv->input_buffer_size;
+	    COPY_OUT(arg, j);
+	    break;
+	  case SNDCTL_DSP_SPEED:
+	    if ((!drv->ops->set_output_rate) && 
+		(!drv->ops->set_input_rate)) {
 	      retval = -EINVAL;
 	      break;
 	    }
-	    if (Modify(ainfo.record.gain) &&
-		((ainfo.record.gain > AUDIO_MAX_GAIN) ||
-		 (ainfo.record.gain < AUDIO_MIN_GAIN))) {
+	    COPY_IN(arg, i);
+	    tprintk(("setting speed to %d\n", i));
+	    drv->ops->set_input_rate(drv, i);
+	    drv->ops->set_output_rate(drv, i);
+	    j = drv->ops->get_output_rate(drv);
+	    COPY_OUT(arg, j);
+	    break;
+	  case SNDCTL_DSP_GETCAPS:
+	    /* 
+	     * All Sparc audio hardware is full duplex.
+	     * 4231 supports DMA pointer reading, 7930 is byte at a time.
+             * Pause functionality emulates trigger
+             */
+	    j = DSP_CAP_DUPLEX | DSP_CAP_TRIGGER | DSP_CAP_REALTIME;
+	    COPY_OUT(arg, j);
+	    break;
+	  case SNDCTL_DSP_GETFMTS:
+	    if (drv->ops->get_formats) {
+	      j = drv->ops->get_formats(drv);
+	      COPY_OUT(arg, j);
+	    } else
 	      retval = -EINVAL;
+	    break;
+	  case SNDCTL_DSP_SETFMT:
+	    /* need to decode into encoding, precision */
+	    COPY_IN(arg, i);
+	    
+	    /* handle special case here */
+	    if (i == AFMT_QUERY) {
+	      j = drv->ops->get_output_encoding(drv);
+	      k = drv->ops->get_output_precision(drv);
+	      if (j == AUDIO_ENCODING_DVI)
+                i = AFMT_IMA_ADPCM;
+	      else if (k == 8) {
+		switch (j) {
+		case AUDIO_ENCODING_ULAW:
+		  i = AFMT_MU_LAW;
+		  break;
+		case AUDIO_ENCODING_ALAW:
+		  i = AFMT_A_LAW;
+		  break;
+		case AUDIO_ENCODING_LINEAR8:
+		  i = AFMT_U8;
+		  break;
+		}
+	      } else if (k == 16) {
+		case AUDIO_ENCODING_LINEAR:
+		  i = AFMT_S16_BE;
+		  break;
+		case AUDIO_ENCODING_LINEARLE:
+		  i = AFMT_S16_LE;
+		  break;
+	      } 
+	      COPY_OUT(arg, i);
 	      break;
 	    }
-	    if (Modify(ainfo.monitor_gain) &&
-		((ainfo.monitor_gain > AUDIO_MAX_GAIN) ||
-		 (ainfo.monitor_gain < AUDIO_MIN_GAIN))) {
+
+            /* Without these there's no point in trying */
+            if (!drv->ops->set_input_precision ||
+                !drv->ops->set_input_encoding ||
+                !drv->ops->set_output_precision ||
+                !drv->ops->set_output_encoding) {
+	      eprintk(("missing set routines: failed\n"));
 	      retval = -EINVAL;
 	      break;
-	    }
-	    /* Don't need to check less than zero on these */
-	    if (Modifyc(ainfo.play.balance) &&
-		(ainfo.play.balance > AUDIO_RIGHT_BALANCE)) {
+            }
+
+	    if (drv->ops->get_formats)
+	      if (!(drv->ops->get_formats(drv) & i)) {
+		dprintk(("format not supported\n"));
+		return -EINVAL;
+	      }
+
+            switch (i) {
+            case AFMT_S16_LE:
+              ainfo.record.precision = ainfo.play.precision = 16;
+              ainfo.record.encoding = ainfo.play.encoding =
+		AUDIO_ENCODING_LINEARLE;
+              break;
+            case AFMT_S16_BE:
+              ainfo.record.precision = ainfo.play.precision = 16;
+              ainfo.record.encoding = ainfo.play.encoding =
+		AUDIO_ENCODING_LINEAR;
+              break;
+            case AFMT_MU_LAW:
+              ainfo.record.precision = ainfo.play.precision = 8;
+              ainfo.record.encoding = ainfo.play.encoding =
+		AUDIO_ENCODING_ULAW;
+              break;
+            case AFMT_A_LAW:
+              ainfo.record.precision = ainfo.play.precision = 8;
+              ainfo.record.encoding = ainfo.play.encoding =
+		AUDIO_ENCODING_ALAW;
+              break;
+            case AFMT_U8:
+              ainfo.record.precision = ainfo.play.precision = 8;
+              ainfo.record.encoding = ainfo.play.encoding =
+		AUDIO_ENCODING_LINEAR8;
+              break;
+            }
+	    tprintk(("setting fmt to enc %d pr %d\n", ainfo.play.encoding,
+		     ainfo.play.precision));
+            if ((drv->ops->set_input_precision(drv,
+						  ainfo.record.precision) 
+		 < 0) ||
+                (drv->ops->set_output_precision(drv,
+						   ainfo.play.precision)  
+		 < 0) ||
+                (drv->ops->set_input_encoding(drv,
+						 ainfo.record.encoding)
+		 < 0) ||
+                (drv->ops->set_output_encoding(drv,
+						  ainfo.play.encoding)
+		 < 0)) {
+	      dprintk(("setting format: failed\n"));
+	      return -EINVAL;
+            }
+	    COPY_OUT(arg, i);
+            break;
+	  case SNDCTL_DSP_CHANNELS:
+	    if ((!drv->ops->set_output_channels) && 
+		(!drv->ops->set_input_channels)) {
 	      retval = -EINVAL;
 	      break;
 	    }
-	    if (Modifyc(ainfo.record.balance) &&
-		(ainfo.record.balance > AUDIO_RIGHT_BALANCE)) {
+	    COPY_IN(arg, i);
+	    drv->ops->set_input_channels(drv, i);
+	    drv->ops->set_output_channels(drv, i);
+	    i = drv->ops->get_output_channels(drv);
+	    COPY_OUT(arg, i);
+	    break;
+	  case SNDCTL_DSP_STEREO:
+	    if ((!drv->ops->set_output_channels) && 
+		(!drv->ops->set_input_channels)) {
 	      retval = -EINVAL;
 	      break;
 	    }
-	    
-	    /* If any of these changed, record them all, then make
-	     * changes atomically. If something fails, back it all out. */
-	    if (Modify(ainfo.record.precision) || 
-		Modify(ainfo.record.sample_rate) ||
-		Modify(ainfo.record.channels) ||
-		Modify(ainfo.record.encoding) || 
-		Modify(ainfo.play.precision) || 
-		Modify(ainfo.play.sample_rate) ||
-		Modify(ainfo.play.channels) ||
-		Modify(ainfo.play.encoding)) 
-	      {
-		/* If they're trying to change something we
-		 * have no routine for, they lose */
-		if ((!driver->ops->set_input_encoding && 
-		    Modify(ainfo.record.encoding)) ||
-		    (!driver->ops->set_input_rate && 
-		    Modify(ainfo.record.sample_rate)) ||
-		    (!driver->ops->set_input_precision && 
-		    Modify(ainfo.record.precision)) ||
-		    (!driver->ops->set_input_channels && 
-		    Modify(ainfo.record.channels))) {
-		  retval = -EINVAL;
-		  break;
-		}		  
-
-                curinfo.record.encoding = driver->ops->get_input_encoding(driver);
-                curinfo.record.sample_rate = driver->ops->get_input_rate(driver);
-                curinfo.record.precision = driver->ops->get_input_precision(driver);
-                curinfo.record.channels = driver->ops->get_input_channels(driver);
-                newinfo.record.encoding = Modify(ainfo.record.encoding) ?
-                  ainfo.record.encoding : curinfo.record.encoding;
-                newinfo.record.sample_rate = Modify(ainfo.record.sample_rate)?
-                  ainfo.record.sample_rate : curinfo.record.sample_rate;
-                newinfo.record.precision = Modify(ainfo.record.precision) ?
-                  ainfo.record.precision : curinfo.record.precision;
-                newinfo.record.channels = Modify(ainfo.record.channels) ?
-                  ainfo.record.channels : curinfo.record.channels;
+	    COPY_IN(arg, i);
+	    drv->ops->set_input_channels(drv, (i + 1));
+	    drv->ops->set_output_channels(drv, (i + 1));
+	    i = ((drv->ops->get_output_channels(drv)) - 1);
+	    COPY_OUT(arg, i);
+	    break;
+	  case SNDCTL_DSP_POST:
+	  case SNDCTL_DSP_SYNC:
+	  case AUDIO_DRAIN:
+	    /* Deal with weirdness so we can fill buffers */
+	    if (drv->output_offset) {
+	      drv->output_offset = 0;
+	      drv->output_rear = (drv->output_rear + 1)
+		% drv->num_output_buffers;
+	      drv->output_count++;
+	    }
+	    if (drv->output_count > 0) {
+	      sparcaudio_sync_output(drv);
+	      /* Only pause for DRAIN/SYNC, not POST */
+	      if (cmd != SNDCTL_DSP_POST) {
+		interruptible_sleep_on(&drv->output_drain_wait);
+		retval = (signal_pending(current)) ? -EINTR : 0;
+	      }
+	    }
+	    break;
+	  case I_FLUSH:
+	  case I_FLUSH_SOLARIS:
+	    if (((unsigned int)arg == FLUSHW) || 
+		((unsigned int)arg == FLUSHRW)) {
+	      if (file->f_mode & FMODE_WRITE) {
+		sparcaudio_sync_output(drv);
+		if (drv->output_active) {
+		  wake_up_interruptible(&drv->output_write_wait);
+		  drv->ops->stop_output(drv);
+		}
+		drv->output_offset = 0;
+		drv->output_active = 0;
+		drv->output_front = 0;
+		drv->output_rear = 0;
+		drv->output_count = 0;
+		drv->output_size = 0;
+		drv->playing_count = 0;
+		drv->output_eof = 0;
+	      }
+	    }
+	    if (((unsigned int)arg == FLUSHR) || 
+		((unsigned int)arg == FLUSHRW)) {
+	      if (drv->input_active && (file->f_mode & FMODE_READ)) {
+		wake_up_interruptible(&drv->input_read_wait);
+		drv->ops->stop_input(drv);
+		drv->input_active = 0;
+		drv->input_front = 0;
+		drv->input_rear = 0;
+		drv->input_count = 0;
+		drv->input_size = 0;
+		drv->input_offset = 0;
+		drv->recording_count = 0;
+	      }
+	      if ((file->f_mode & FMODE_READ) && 
+		  (drv->flags & SDF_OPEN_READ)) {
+		if (drv->duplex == 2)
+		  drv->input_count = drv->output_count;
+		drv->ops->start_input(drv, 
+					 drv->input_buffers[drv->input_front],
+					 drv->input_buffer_size);
+		drv->input_active = 1;
+	      }
+	    }
+	    if (((unsigned int)arg == FLUSHW) || 
+		((unsigned int)arg == FLUSHRW)) {
+	      if ((file->f_mode & FMODE_WRITE) && 
+		  !(drv->flags & SDF_OPEN_WRITE)) {
+		kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+		sparcaudio_sync_output(drv);
+	      }
+	    }
+	    break;
+	  case SNDCTL_DSP_RESET:
+	  case AUDIO_FLUSH:
+	    if (drv->output_active && (file->f_mode & FMODE_WRITE)) {
+	      wake_up_interruptible(&drv->output_write_wait);
+	      drv->ops->stop_output(drv);
+	      drv->output_active = 0;
+	      drv->output_front = 0;
+	      drv->output_rear = 0;
+	      drv->output_count = 0;
+	      drv->output_size = 0;
+	      drv->playing_count = 0;
+	      drv->output_offset = 0;
+	      drv->output_eof = 0;
+	    }
+	    if (drv->input_active && (file->f_mode & FMODE_READ)) {
+	      wake_up_interruptible(&drv->input_read_wait);
+	      drv->ops->stop_input(drv);
+	      drv->input_active = 0;
+	      drv->input_front = 0;
+	      drv->input_rear = 0;
+	      drv->input_count = 0;
+	      drv->input_size = 0;
+	      drv->input_offset = 0;
+	      drv->recording_count = 0;
+	    }
+	    if ((file->f_mode & FMODE_READ) && 
+		!(drv->flags & SDF_OPEN_READ)) {
+	      drv->ops->start_input(drv, 
+				       drv->input_buffers[drv->input_front],
+				       drv->input_buffer_size);
+	      drv->input_active = 1;
+	    }
+	    if ((file->f_mode & FMODE_WRITE) && 
+		!(drv->flags & SDF_OPEN_WRITE)) {
+	      sparcaudio_sync_output(drv);
+	    }
+	    break;
+	  case AUDIO_GETDEV:
+	    if (drv->ops->sunaudio_getdev) {
+	      audio_device_t tmp;
+	      
+	      retval = verify_area(VERIFY_WRITE, (void *)arg, 
+				   sizeof(audio_device_t));
+	      if (!retval)
+		drv->ops->sunaudio_getdev(drv, &tmp);
+	      copy_to_user((audio_device_t *)arg, &tmp, sizeof(tmp));
+	    } else
+	      retval = -EINVAL;
+	    break;
+	  case AUDIO_GETDEV_SUNOS:
+	    if (drv->ops->sunaudio_getdev_sunos) {
+	      int tmp = drv->ops->sunaudio_getdev_sunos(drv);
+	      
+	      retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(int));
+	      if (!retval)
+		copy_to_user((int *)arg, &tmp, sizeof(tmp));
+	    } else
+	      retval = -EINVAL;
+	    break;
+	  case AUDIO_GETINFO:
+	    AUDIO_INITINFO(&ainfo);
 
-		switch (newinfo.record.encoding) {
-		case AUDIO_ENCODING_ALAW:
-		case AUDIO_ENCODING_ULAW:
-                  if (newinfo.record.precision != 8) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  if (newinfo.record.channels != 1) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  break;
-                case AUDIO_ENCODING_LINEAR:
-                case AUDIO_ENCODING_LINEARLE:
-                  if (newinfo.record.precision != 16) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  if (newinfo.record.channels != 1 &&
-                       newinfo.record.channels != 2)
-                    {
-                      retval = -EINVAL;
-                      break;
-                    }
-                  break;
-                case AUDIO_ENCODING_LINEAR8:
-                  if (newinfo.record.precision != 8) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  if (newinfo.record.channels != 1 &&
-                       newinfo.record.channels != 2)
-                    {
-                      retval = -EINVAL;
-                      break;
-                    }
-                }
+	    if (drv->ops->get_input_rate)
+	      ainfo.record.sample_rate =
+		drv->ops->get_input_rate(drv);
+	    else
+	      ainfo.record.sample_rate = (8000);
+	    if (drv->ops->get_input_channels)
+	      ainfo.record.channels =
+		drv->ops->get_input_channels(drv);
+	    else
+	      ainfo.record.channels = (1);
+	    if (drv->ops->get_input_precision)
+	      ainfo.record.precision =
+		drv->ops->get_input_precision(drv);
+	    else
+	      ainfo.record.precision = (8);
+	    if (drv->ops->get_input_encoding)
+	      ainfo.record.encoding =
+		drv->ops->get_input_encoding(drv);
+	    else
+	      ainfo.record.encoding = (AUDIO_ENCODING_ULAW);
+	    if (drv->ops->get_input_volume)
+	      ainfo.record.gain =
+		drv->ops->get_input_volume(drv);
+	    else
+	      ainfo.record.gain = (0);
+	    if (drv->ops->get_input_port)
+	      ainfo.record.port =
+		drv->ops->get_input_port(drv);
+	    else
+	      ainfo.record.port = (0);
+	    if (drv->ops->get_input_ports)
+	      ainfo.record.avail_ports = 
+		drv->ops->get_input_ports(drv);
+	    else
+	      ainfo.record.avail_ports = (0);
+	    /* To make e.g. vat happy, we let them think they control this */
+	    ainfo.record.buffer_size = drv->buffer_size;
+	    if (drv->ops->get_input_samples)
+	      ainfo.record.samples = drv->ops->get_input_samples(drv);
+	    else
+	      ainfo.record.samples = 0;
+	    /* This is undefined in the record context in Solaris */
+	    ainfo.record.eof = 0;
+	    if (drv->ops->get_input_pause)
+	      ainfo.record.pause =
+		drv->ops->get_input_pause(drv);
+	    else
+	      ainfo.record.pause = 0;
+	    if (drv->ops->get_input_error)
+	      ainfo.record.error = 
+		(unsigned char)drv->ops->get_input_error(drv);
+	    else
+	      ainfo.record.error = 0;
+	    ainfo.record.waiting = 0;
+	    if (drv->ops->get_input_balance)
+	      ainfo.record.balance =
+		(unsigned char)drv->ops->get_input_balance(drv);
+	    else
+	      ainfo.record.balance = (unsigned char)(AUDIO_MID_BALANCE);
+	    ainfo.record.minordev = 4 + (minor << SPARCAUDIO_DEVICE_SHIFT);
+	    ainfo.record.open = (drv->flags & SDF_OPEN_READ);
+	    ainfo.record.active = 0;
+
+	    if (drv->ops->get_output_rate)
+	      ainfo.play.sample_rate =
+		drv->ops->get_output_rate(drv);
+	    else
+	      ainfo.play.sample_rate = (8000);
+	    if (drv->ops->get_output_channels)
+	      ainfo.play.channels =
+		drv->ops->get_output_channels(drv);
+	    else
+	      ainfo.play.channels = (1);
+	    if (drv->ops->get_output_precision)
+	      ainfo.play.precision =
+		drv->ops->get_output_precision(drv);
+	    else
+	      ainfo.play.precision = (8);
+	    if (drv->ops->get_output_encoding)
+	      ainfo.play.encoding =
+		drv->ops->get_output_encoding(drv);
+	    else
+	      ainfo.play.encoding = (AUDIO_ENCODING_ULAW);
+	    if (drv->ops->get_output_volume)
+	      ainfo.play.gain =
+		drv->ops->get_output_volume(drv);
+	    else
+	      ainfo.play.gain = (0);
+	    if (drv->ops->get_output_port)
+	      ainfo.play.port =
+		drv->ops->get_output_port(drv);
+	    else
+	      ainfo.play.port = (0);
+	    if (drv->ops->get_output_ports)
+	      ainfo.play.avail_ports = 
+		drv->ops->get_output_ports(drv);
+	    else
+	      ainfo.play.avail_ports = (0);
+	    /* This is not defined in the play context in Solaris */
+	    ainfo.play.buffer_size = 0;
+	    if (drv->ops->get_output_samples)
+	      ainfo.play.samples = drv->ops->get_output_samples(drv);
+	    else
+	      ainfo.play.samples = 0;
+	    ainfo.play.eof = drv->output_eof;
+	    if (drv->ops->get_output_pause)
+	      ainfo.play.pause =
+		drv->ops->get_output_pause(drv);
+	    else
+	      ainfo.play.pause = 0;
+	    if (drv->ops->get_output_error)
+	      ainfo.play.error =
+		(unsigned char)drv->ops->get_output_error(drv);
+	    else
+	      ainfo.play.error = 0;
+	    ainfo.play.waiting = waitqueue_active(&drv->open_wait);
+	    if (drv->ops->get_output_balance)
+	      ainfo.play.balance =
+		(unsigned char)drv->ops->get_output_balance(drv);
+	    else
+	      ainfo.play.balance = (unsigned char)(AUDIO_MID_BALANCE);
+	    ainfo.play.minordev = 4 + (minor << SPARCAUDIO_DEVICE_SHIFT);
+	    ainfo.play.open = (drv->flags & SDF_OPEN_WRITE);
+	    ainfo.play.active = drv->output_active;
+	    
+	    if (drv->ops->get_monitor_volume)
+	      ainfo.monitor_gain =
+		drv->ops->get_monitor_volume(drv);
+	    else
+	      ainfo.monitor_gain = (0);
+
+	    if (drv->ops->get_output_muted)
+	      ainfo.output_muted = 
+		(unsigned char)drv->ops->get_output_muted(drv);
+	    else
+	      ainfo.output_muted = (unsigned char)(0);
+
+	    retval = verify_area(VERIFY_WRITE, (void *)arg,
+				 sizeof(struct audio_info));
+	    if (retval < 0)
+	      break;
 
-		if (retval < 0)
-		  break;
+	    copy_to_user((struct audio_info *)arg, &ainfo, sizeof(ainfo));
+	    
+	    break;
+	  case AUDIO_SETINFO:
+	    {
+	      audio_info_t curinfo, newinfo;
+	      
+	      if (verify_area(VERIFY_READ, (audio_info_t *)arg, 
+			      sizeof(audio_info_t))) {
+		dprintk(("verify_area failed\n"));
+		return -EINVAL;
+	      }
+	      copy_from_user(&ainfo, (audio_info_t *)arg, sizeof(audio_info_t));
 
-		/* If they're trying to change something we
-		 * have no routine for, they lose */
-		if ((!driver->ops->set_output_encoding && 
-		    Modify(ainfo.play.encoding)) ||
-		    (!driver->ops->set_output_rate && 
-		    Modify(ainfo.play.sample_rate)) ||
-		    (!driver->ops->set_output_precision && 
-		    Modify(ainfo.play.precision)) ||
-		    (!driver->ops->set_output_channels && 
-		    Modify(ainfo.play.channels))) {
+	      /* Without these there's no point in trying */
+	      if (!drv->ops->get_input_precision ||
+		  !drv->ops->get_input_channels ||
+		  !drv->ops->get_input_rate ||
+		  !drv->ops->get_input_encoding ||
+		  !drv->ops->get_output_precision ||
+		  !drv->ops->get_output_channels ||
+		  !drv->ops->get_output_rate ||
+		  !drv->ops->get_output_encoding) 
+		{
+		  eprintk(("missing get routines: failed\n"));
 		  retval = -EINVAL;
 		  break;
-		}		  
-
-                curinfo.play.encoding = driver->ops->get_output_encoding(driver);
-                curinfo.play.sample_rate = driver->ops->get_output_rate(driver);
-                curinfo.play.precision = driver->ops->get_output_precision(driver);
-                curinfo.play.channels = driver->ops->get_output_channels(driver);
-                newinfo.play.encoding = Modify(ainfo.play.encoding) ?
-                  ainfo.play.encoding : curinfo.play.encoding;
-                newinfo.play.sample_rate = Modify(ainfo.play.sample_rate) ?
-                  ainfo.play.sample_rate : curinfo.play.sample_rate;
-                newinfo.play.precision = Modify(ainfo.play.precision) ?
-                  ainfo.play.precision : curinfo.play.precision;
-                newinfo.play.channels = Modify(ainfo.play.channels) ?
-                  ainfo.play.channels : curinfo.play.channels;
-
-		switch (newinfo.play.encoding) {
-		case AUDIO_ENCODING_ALAW:
-		case AUDIO_ENCODING_ULAW:
-                  if (newinfo.play.precision != 8) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  if (newinfo.play.channels != 1) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  break;
-                case AUDIO_ENCODING_LINEAR:
-                case AUDIO_ENCODING_LINEARLE:
-                  if (newinfo.play.precision != 16) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  if (newinfo.play.channels != 1 &&
-                       newinfo.play.channels != 2)
-                    {
-                      retval = -EINVAL;
-                      break;
-                    }
-                  break;
-                case AUDIO_ENCODING_LINEAR8:
-                  if (newinfo.play.precision != 8) {
-                    retval = -EINVAL;
-                    break;
-                  }
-                  if (newinfo.play.channels != 1 &&
-                       newinfo.play.channels != 2)
-                    {
-                      retval = -EINVAL;
-                      break;
-                    }
-                }
-		
-		if (retval < 0)
-		  break;
+		}
 
-		/* If we got this far, we're at least sane with
-		 * respect to generics. Try the changes. */
-		if ((driver->ops->set_input_precision(driver, ainfo.record.precision) < 0) ||
-		    (driver->ops->set_output_precision(driver, ainfo.play.precision) < 0) ||
-		    (driver->ops->set_input_channels(driver, ainfo.record.channels) < 0) ||
-		    (driver->ops->set_output_channels(driver, ainfo.play.channels) < 0) ||
-		    (driver->ops->set_input_rate(driver, ainfo.record.sample_rate) < 0) ||
-		    (driver->ops->set_output_rate(driver, ainfo.play.sample_rate) < 0) ||
-		    (driver->ops->set_input_encoding(driver, ainfo.record.encoding) < 0) ||
-		    (driver->ops->set_output_encoding(driver, ainfo.play.encoding) < 0)) 
-		  {
-		    /* Pray we can set it all back. If not, uh... */
-		    driver->ops->set_input_precision(driver, curinfo.record.precision);
-		    driver->ops->set_output_precision(driver, curinfo.play.precision);
-		    driver->ops->set_input_channels(driver, curinfo.record.channels);
-		    driver->ops->set_output_channels(driver, curinfo.play.channels);
-		    driver->ops->set_input_rate(driver, curinfo.record.sample_rate);
-		    driver->ops->set_output_rate(driver, curinfo.play.sample_rate);
-		    driver->ops->set_input_encoding(driver, curinfo.record.encoding); 
-		    driver->ops->set_output_encoding(driver, curinfo.play.encoding);
+	      /* Do bounds checking for things which always apply.
+	       * Follow with enforcement of basic tenets of certain
+	       * encodings. Everything over and above generic is
+	       * enforced by the driver, which can assume that
+	       * Martian cases are taken care of here. */
+	      if (Modify(ainfo.play.gain) && 
+		  ((ainfo.play.gain > AUDIO_MAX_GAIN) || 
+		   (ainfo.play.gain < AUDIO_MIN_GAIN))) {
+		/* Need to differentiate this from e.g. the above error */
+		eprintk(("play gain bounds: failed %d\n", ainfo.play.gain));
+		retval = -EINVAL;
+		break;
+	      }
+	      if (Modify(ainfo.record.gain) &&
+		  ((ainfo.record.gain > AUDIO_MAX_GAIN) ||
+		   (ainfo.record.gain < AUDIO_MIN_GAIN))) {
+		eprintk(("rec gain bounds: failed %d\n", ainfo.record.gain));
+		retval = -EINVAL;
+		break;
+	      }
+	      if (Modify(ainfo.monitor_gain) &&
+		  ((ainfo.monitor_gain > AUDIO_MAX_GAIN) ||
+		   (ainfo.monitor_gain < AUDIO_MIN_GAIN))) {
+		eprintk(("monitor gain bounds: failed\n"));
+		retval = -EINVAL;
+		break;
+	      }
+	      /* Don't need to check less than zero on these */
+	      if (Modifyc(ainfo.play.balance) &&
+		  (ainfo.play.balance > AUDIO_RIGHT_BALANCE)) {
+		eprintk(("play balance bounds: %d failed\n", 
+			 (int)ainfo.play.balance));
+		retval = -EINVAL;
+		break;
+	      }
+	      if (Modifyc(ainfo.record.balance) &&
+		  (ainfo.record.balance > AUDIO_RIGHT_BALANCE)) {
+		eprintk(("rec balance bounds: failed\n"));
+		retval = -EINVAL;
+		break;
+	      }
+	      
+	      /* If any of these changed, record them all, then make
+	       * changes atomically. If something fails, back it all out. */
+	      if (Modify(ainfo.record.precision) || 
+		  Modify(ainfo.record.sample_rate) ||
+		  Modify(ainfo.record.channels) ||
+		  Modify(ainfo.record.encoding) || 
+		  Modify(ainfo.play.precision) || 
+		  Modify(ainfo.play.sample_rate) ||
+		  Modify(ainfo.play.channels) ||
+		  Modify(ainfo.play.encoding)) 
+		{
+		  /* If they're trying to change something we
+		   * have no routine for, they lose */
+		  if ((!drv->ops->set_input_encoding && 
+		       Modify(ainfo.record.encoding)) ||
+		      (!drv->ops->set_input_rate && 
+		       Modify(ainfo.record.sample_rate)) ||
+		      (!drv->ops->set_input_precision && 
+		       Modify(ainfo.record.precision)) ||
+		      (!drv->ops->set_input_channels && 
+		       Modify(ainfo.record.channels))) {
+		    eprintk(("rec set no routines: failed\n"));
+		    retval = -EINVAL;
+		    break;
+		  }		  
+		  
+		  curinfo.record.encoding = 
+		    drv->ops->get_input_encoding(drv);
+		  curinfo.record.sample_rate = 
+		    drv->ops->get_input_rate(drv);	   
+		  curinfo.record.precision = 
+		    drv->ops->get_input_precision(drv);	   
+		  curinfo.record.channels = 
+		    drv->ops->get_input_channels(drv);	   
+		  newinfo.record.encoding = Modify(ainfo.record.encoding) ? 
+		    ainfo.record.encoding : curinfo.record.encoding;
+		  newinfo.record.sample_rate =
+		    Modify(ainfo.record.sample_rate)?
+		    ainfo.record.sample_rate : curinfo.record.sample_rate;
+		  newinfo.record.precision = Modify(ainfo.record.precision) ? 
+		    ainfo.record.precision : curinfo.record.precision;
+		  newinfo.record.channels = Modify(ainfo.record.channels) ? 
+		    ainfo.record.channels : curinfo.record.channels;
+		    
+		  switch (newinfo.record.encoding) {
+		  case AUDIO_ENCODING_ALAW:
+		  case AUDIO_ENCODING_ULAW:
+		    if (newinfo.record.precision != 8) {
+		      eprintk(("rec law precision bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    if (newinfo.record.channels != 1) {
+		      eprintk(("rec law channel bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    break;
+		  case AUDIO_ENCODING_LINEAR:
+		  case AUDIO_ENCODING_LINEARLE:
+		    if (newinfo.record.precision != 16) {
+		      eprintk(("rec lin precision bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    if (newinfo.record.channels != 1 && 
+			newinfo.record.channels != 2) 
+		      {
+			eprintk(("rec lin channel bounds: failed\n"));
+			retval = -EINVAL;
+			break;
+		      }
+		    break;
+		  case AUDIO_ENCODING_LINEAR8:
+		    if (newinfo.record.precision != 8) {
+		      eprintk(("rec lin8 precision bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    if (newinfo.record.channels != 1 && 
+			newinfo.record.channels != 2)
+		      {
+			eprintk(("rec lin8 channel bounds: failed\n"));
+			retval = -EINVAL;
+			break;
+		      }
+		  }
+		  
+		  if (retval < 0)
+		    break;
+		  
+		  /* If they're trying to change something we
+		   * have no routine for, they lose */
+		  if ((!drv->ops->set_output_encoding && 
+		       Modify(ainfo.play.encoding)) ||
+		      (!drv->ops->set_output_rate && 
+		       Modify(ainfo.play.sample_rate)) ||
+		      (!drv->ops->set_output_precision && 
+		       Modify(ainfo.play.precision)) ||
+		      (!drv->ops->set_output_channels && 
+		       Modify(ainfo.play.channels))) {
+		    eprintk(("play set no routine: failed\n"));
+		    retval = -EINVAL;
+		    break;
+		  }		  
+		  
+		  curinfo.play.encoding = 
+		    drv->ops->get_output_encoding(drv);
+		  curinfo.play.sample_rate = 
+		    drv->ops->get_output_rate(drv);	   
+		  curinfo.play.precision = 
+		    drv->ops->get_output_precision(drv);	   
+		  curinfo.play.channels = 
+		    drv->ops->get_output_channels(drv);	   
+		  newinfo.play.encoding = Modify(ainfo.play.encoding) ? 
+		    ainfo.play.encoding : curinfo.play.encoding;
+		  newinfo.play.sample_rate = Modify(ainfo.play.sample_rate) ? 
+		    ainfo.play.sample_rate : curinfo.play.sample_rate;
+		  newinfo.play.precision = Modify(ainfo.play.precision) ? 
+		    ainfo.play.precision : curinfo.play.precision;
+		  newinfo.play.channels = Modify(ainfo.play.channels) ? 
+		    ainfo.play.channels : curinfo.play.channels;
+		  
+		  switch (newinfo.play.encoding) {
+		  case AUDIO_ENCODING_ALAW:
+		  case AUDIO_ENCODING_ULAW:
+		    if (newinfo.play.precision != 8) {
+		      eprintk(("play law precision bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    if (newinfo.play.channels != 1) {
+		      eprintk(("play law channel bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    break;
+		  case AUDIO_ENCODING_LINEAR:
+		  case AUDIO_ENCODING_LINEARLE:
+		    if (newinfo.play.precision != 16) {
+		      eprintk(("play lin precision bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    if (newinfo.play.channels != 1 && 
+			newinfo.play.channels != 2) 
+		      {
+			eprintk(("play lin channel bounds: failed\n"));
+			retval = -EINVAL;
+			break;
+		      }
+		    break;
+		  case AUDIO_ENCODING_LINEAR8:
+		    if (newinfo.play.precision != 8) {
+		      eprintk(("play lin8 precision bounds: failed\n"));
+		      retval = -EINVAL;
+		      break;
+		    }
+		    if (newinfo.play.channels != 1 && 
+			newinfo.play.channels != 2) 
+		      {
+			eprintk(("play lin8 channel bounds: failed\n"));
+			retval = -EINVAL;
+			break;
+		      }
 		  }
+		  
+		  if (retval < 0)
+		    break;
+		  
+		  /* If we got this far, we're at least sane with
+		   * respect to generics. Try the changes. */
+		  if ((drv->ops->set_input_channels &&
+		      (drv->ops->set_input_channels(drv, 
+						    newinfo.record.channels)
+		       < 0)) ||
+		      (drv->ops->set_output_channels &&
+		       (drv->ops->set_output_channels(drv, 
+						      newinfo.play.channels)
+			< 0)) ||
+		      (drv->ops->set_input_rate &&
+		       (drv->ops->set_input_rate(drv, 
+						 newinfo.record.sample_rate) 
+			< 0)) ||
+		      (drv->ops->set_output_rate &&
+		       (drv->ops->set_output_rate(drv, 
+						  newinfo.play.sample_rate) 
+			< 0)) ||
+		      (drv->ops->set_input_precision &&
+		       (drv->ops->set_input_precision(drv, 
+						      newinfo.record.precision)
+			< 0)) ||
+		      (drv->ops->set_output_precision &&
+		       (drv->ops->set_output_precision(drv, 
+						       newinfo.play.precision)
+			< 0)) ||
+		      (drv->ops->set_input_encoding &&
+		       (drv->ops->set_input_encoding(drv, 
+						     newinfo.record.encoding)
+			< 0)) ||
+		      (drv->ops->set_output_encoding &&
+		       (drv->ops->set_output_encoding(drv, 
+						      newinfo.play.encoding)
+			< 0))) 
+		    {
+		      dprintk(("setting format: failed\n"));
+		      /* Pray we can set it all back. If not, uh... */
+		      if (drv->ops->set_input_channels)
+			drv->ops->set_input_channels(drv, 
+						     curinfo.record.channels);
+		      if (drv->ops->set_output_channels)
+			drv->ops->set_output_channels(drv, 
+						      curinfo.play.channels);
+		      if (drv->ops->set_input_rate)
+			drv->ops->set_input_rate(drv, 
+						 curinfo.record.sample_rate);
+		      if (drv->ops->set_output_rate)
+			drv->ops->set_output_rate(drv, 
+						  curinfo.play.sample_rate);
+		      if (drv->ops->set_input_precision)
+			drv->ops->set_input_precision(drv, 
+						      curinfo.record.precision);
+		      if (drv->ops->set_output_precision)
+			drv->ops->set_output_precision(drv, 
+						       curinfo.play.precision);
+		      if (drv->ops->set_input_encoding)
+			drv->ops->set_input_encoding(drv, 
+						     curinfo.record.encoding);
+		      if (drv->ops->set_output_encoding)
+			drv->ops->set_output_encoding(drv, 
+						      curinfo.play.encoding);
+		      retval = -EINVAL;
+		      break;
+		    }
+		}
+	      
+	      if (retval < 0)
+		break;
+	      
+	      newinfo.record.balance =
+		__sparcaudio_if_setc_do(drv, 
+					drv->ops->set_input_balance, 
+					drv->ops->get_input_balance,
+					ainfo.record.balance);
+	      newinfo.play.balance =
+		__sparcaudio_if_setc_do(drv, 
+					drv->ops->set_output_balance, 
+					drv->ops->get_output_balance,
+					ainfo.play.balance);
+	      newinfo.record.error =
+		__sparcaudio_if_setc_do(drv, 
+					drv->ops->set_input_error, 
+					drv->ops->get_input_error,
+					ainfo.record.error);
+	      newinfo.play.error =
+		__sparcaudio_if_setc_do(drv, 
+					drv->ops->set_output_error, 
+					drv->ops->get_output_error,
+					ainfo.play.error);
+	      newinfo.output_muted =
+		__sparcaudio_if_setc_do(drv, 
+					drv->ops->set_output_muted, 
+					drv->ops->get_output_muted,
+					ainfo.output_muted);
+	      newinfo.record.gain =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_input_volume, 
+					drv->ops->get_input_volume,
+					ainfo.record.gain);
+	      newinfo.play.gain =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_output_volume, 
+					drv->ops->get_output_volume,
+					ainfo.play.gain);
+	      newinfo.record.port =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_input_port, 
+					drv->ops->get_input_port,
+					ainfo.record.port);
+	      newinfo.play.port =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_output_port, 
+					drv->ops->get_output_port,
+					ainfo.play.port);
+	      newinfo.record.samples =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_input_samples, 
+					drv->ops->get_input_samples,
+					ainfo.record.samples);
+	      newinfo.play.samples =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_output_samples, 
+					drv->ops->get_output_samples,
+					ainfo.play.samples);
+	      newinfo.monitor_gain =
+		__sparcaudio_if_set_do(drv, 
+					drv->ops->set_monitor_volume, 
+					drv->ops->get_monitor_volume,
+					ainfo.monitor_gain);
+
+	      if (Modify(ainfo.record.buffer_size)) {
+		/* Should sanity check this */
+		newinfo.record.buffer_size = ainfo.record.buffer_size;
+		drv->buffer_size = ainfo.record.buffer_size;
+	      } else 
+		newinfo.record.buffer_size = drv->buffer_size;
+
+
+	      if (Modify(ainfo.play.eof)) {
+		ainfo.play.eof = newinfo.play.eof;
+		newinfo.play.eof = drv->output_eof;
+		drv->output_eof = ainfo.play.eof;
+	      } else
+		newinfo.play.eof = drv->output_eof;
+		
+	      if (drv->flags & SDF_OPEN_READ) {
+		newinfo.record.pause =
+		  __sparcaudio_if_setc_do(drv, 
+					  drv->ops->set_input_pause, 
+					  drv->ops->get_input_pause,
+					  ainfo.record.pause);
+	      } else if (drv->ops->get_input_pause) {
+		newinfo.record.pause = drv->ops->get_input_pause(drv);
+	      } else newinfo.record.pause = 0;
+
+	      if (drv->flags & SDF_OPEN_WRITE) {
+		newinfo.play.pause =
+		  __sparcaudio_if_setc_do(drv, 
+					  drv->ops->set_output_pause, 
+					  drv->ops->get_output_pause,
+					  ainfo.play.pause);
+	      } else if (drv->ops->get_output_pause) {
+		newinfo.play.pause = drv->ops->get_output_pause(drv);
+	      } else newinfo.play.pause = 0;
+	      
+	      retval = verify_area(VERIFY_WRITE, (void *)arg,
+				   sizeof(struct audio_info));
+
+	      /* Even if we fail, if we made changes let's try notification */
+	      if (!retval) 
+		copy_to_user((struct audio_info *)arg, &newinfo, 
+			     sizeof(newinfo));
+	    
+#ifdef REAL_AUDIO_SIGNALS
+	      kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+#endif
 
+	      break;
 	    }
-            /* Maybe this should be a routine instead of a macro */
-#define IF_SET_DO(x,y) if ((x) && Modify(y)) x(driver, y)
-#define IF_SETC_DO(x,y) if ((x) && Modifyc(y)) x(driver, y)
-            IF_SETC_DO(driver->ops->set_input_balance, (int)ainfo.record.balance);
-            IF_SETC_DO(driver->ops->set_output_balance, (int)ainfo.play.balance);
-            IF_SET_DO(driver->ops->set_input_volume, ainfo.record.gain);
-            IF_SET_DO(driver->ops->set_output_volume, ainfo.play.gain);
-            IF_SET_DO(driver->ops->set_input_port, ainfo.record.port);
-            IF_SET_DO(driver->ops->set_output_port, ainfo.play.port);
-            IF_SET_DO(driver->ops->set_monitor_volume, ainfo.monitor_gain);
-            IF_SETC_DO(driver->ops->set_output_muted, (int)ainfo.output_muted);
-#undef IF_SET_DO
-#undef IF_SETC_DO
-
-	    break;
+	  
+	  default:
+	    if (drv->ops->ioctl)
+	      retval = drv->ops->ioctl(inode,file,cmd,arg,drv);
+	    else
+	      retval = -EINVAL;
 	  }
-
+	  break;
+	case SPARCAUDIO_STATUS_MINOR:
+	  eprintk(("status minor not yet implemented\n"));
+	  retval = -EINVAL;
 	default:
-		if (driver->ops->ioctl)
-			retval = driver->ops->ioctl(inode,file,cmd,arg,driver);
-		else {
-			retval = -EINVAL;
-		}
+	  eprintk(("unknown minor device number\n"));
+	  retval = -EINVAL;
 	}
-
+	
 	return retval;
 }
 
-static int sparcaudioctl_release(struct inode * inode, struct file * file)
+static int sparcaudioctl_release_ret(struct inode * inode, struct file * file)
 {
-	MOD_DEC_USE_COUNT;
+      MOD_DEC_USE_COUNT;
+      return 0;
+}
 
-	return 0;
+/* For 2.0 kernels */
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static void sparcaudioctl_release(struct inode * inode, struct file * file)
+{
+  sparcaudioctl_release_ret(inode, file);
 }
+#endif
 
 static struct file_operations sparcaudioctl_fops = {
-	NULL,
-	NULL,
-	NULL,
-	NULL,			/* sparcaudio_readdir */
-	NULL,			/* sparcaudio_select */
-	sparcaudio_ioctl,
-	NULL,			/* sparcaudio_mmap */
-	NULL,
-	NULL,			/* flush */
-	sparcaudioctl_release
+      NULL,
+      NULL,
+      NULL,
+      NULL,                   /* sparcaudio_readdir */
+      sparcaudio_select,
+      sparcaudio_ioctl,
+      NULL,                   /* sparcaudio_mmap */
+      NULL,
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff
+      NULL,                   /* sparcaudio_flush */
+#endif
+      sparcaudioctl_release,
 };
 
 static int sparcaudio_open(struct inode * inode, struct file * file)
 {
-    int minor = MINOR(inode->i_rdev);
-    int err;
+        int minor = MINOR(inode->i_rdev);
+	struct sparcaudio_driver *drv = 
+	  drivers[(minor >> SPARCAUDIO_DEVICE_SHIFT)];
+	int err;
+
+	/* A low-level audio driver must exist. */
+	if (!drv)
+		return -ENODEV;
+
+	switch (minor & 0xf) {
+	case SPARCAUDIO_AUDIOCTL_MINOR:
+	  file->f_op = &sparcaudioctl_fops;
+	  break;
+	case SPARCAUDIO_DSP16_MINOR:
+	case SPARCAUDIO_DSP_MINOR:
+	case SPARCAUDIO_AUDIO_MINOR:
+	  /* If the driver is busy, then wait to get through. */
+	  retry_open:
+	  if (file->f_mode & FMODE_READ && drv->flags & SDF_OPEN_READ) {
+	    if (file->f_flags & O_NONBLOCK)
+	      return -EBUSY;
+
+	    /* If something is now waiting, signal control device */
+	    kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
 
-    /* A low-level audio driver must exist. */
-    if (!driver)
-        return -ENODEV;
-
-    switch (minor) {
-    case SPARCAUDIO_AUDIOCTL_MINOR:
-        file->f_op = &sparcaudioctl_fops;
-        break;
-
-    case SPARCAUDIO_DSP16_MINOR:
-    case SPARCAUDIO_DSP_MINOR:
-    case SPARCAUDIO_AUDIO_MINOR:
-        /* If the driver is busy, then wait to get through. */
-    retry_open:
-	if (file->f_mode & FMODE_READ && driver->flags & SDF_OPEN_READ) {
-            if (file->f_flags & O_NONBLOCK)
-                return -EBUSY;
-            
-            interruptible_sleep_on(&driver->open_wait);
+	    interruptible_sleep_on(&drv->open_wait);
             if (signal_pending(current))
-                return -EINTR;
-            goto retry_open;
-	}
-	if (file->f_mode & FMODE_WRITE && driver->flags & SDF_OPEN_WRITE) {
-            if (file->f_flags & O_NONBLOCK)
-                return -EBUSY;
-            
-            interruptible_sleep_on(&driver->open_wait);
+	      return -EINTR;
+	    goto retry_open;
+	  }
+	  if (file->f_mode & FMODE_WRITE && drv->flags & SDF_OPEN_WRITE) {
+	    if (file->f_flags & O_NONBLOCK)
+	      return -EBUSY;
+	    
+	    /* If something is now waiting, signal control device */
+	    kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
+
+	    interruptible_sleep_on(&drv->open_wait);
             if (signal_pending(current))
-                return -EINTR;
-            goto retry_open;
-	}
+	      return -EINTR;
+	    goto retry_open;
+	  }
 
-	/* Allow the low-level driver to initialize itself. */
-	if (driver->ops->open) {
-            err = driver->ops->open(inode,file,driver);
-            if (err < 0)
-                return err;
-	}
-        
-	/* Mark the driver as locked for read and/or write. */
-	if (file->f_mode & FMODE_READ) {
-            driver->input_offset = 0;
-            driver->input_front = 0;
-            driver->input_rear = 0;
-            driver->input_count = 0;
-            driver->recording_count = 0;
-            driver->ops->start_input(driver, driver->input_buffers[driver->input_front],
-                                     4096);
-            driver->input_active = 1;
-            driver->flags |= SDF_OPEN_READ;
+	  /* Allow the low-level driver to initialize itself. */
+	  if (drv->ops->open) {
+	    err = drv->ops->open(inode,file,drv);
+	    if (err < 0)
+	      return err;
+	  }
+
+	  /* Mark the driver as locked for read and/or write. */
+	  if (file->f_mode & FMODE_READ) {
+	    drv->input_offset = 0;
+	    drv->input_front = 0;
+	    drv->input_rear = 0;
+	    drv->input_count = 0;
+	    drv->input_size = 0;
+	    drv->recording_count = 0;
+	    /* Clear pause */
+	    if (drv->ops->set_input_pause)
+	      drv->ops->set_input_pause(drv, 0); 
+	    drv->ops->start_input(drv, drv->input_buffers[drv->input_front],
+				     drv->input_buffer_size);
+	    drv->input_active = 1;
+	    drv->flags |= SDF_OPEN_READ;
+	  }
+	  if (file->f_mode & FMODE_WRITE) {
+	    drv->output_offset = 0;
+	    drv->output_eof = 0;
+	    drv->playing_count = 0;
+	    drv->output_size = 0;
+	    drv->output_front = 0;
+	    drv->output_rear = 0;
+	    drv->output_count = 0;
+	    drv->output_active = 0;
+	    /* Clear pause */
+	    if (drv->ops->set_output_pause)
+	      drv->ops->set_output_pause(drv, 0); 
+	    drv->flags |= SDF_OPEN_WRITE;
+	  }  
+
+	  break;
+	case SPARCAUDIO_MIXER_MINOR:     
+	  file->f_op = &sparcaudioctl_fops;
+	  break;
+
+	default:
+	  return -ENXIO;
 	}
-	if (file->f_mode & FMODE_WRITE) {
-            driver->playing_count = 0;
-            driver->output_size = 0;
-            driver->output_front = 0;
-            driver->output_rear = 0;
-            driver->output_count = 0;
-            driver->output_active = 0;
-            driver->flags |= SDF_OPEN_WRITE;
-	}  
-        break;
-    case SPARCAUDIO_MIXER_MINOR:     
-        file->f_op = &sparcaudioctl_fops;
-        break;
 
-    default:
-        return -ENXIO;
-    }
+	MOD_INC_USE_COUNT;
 
-    MOD_INC_USE_COUNT;
-    
-    /* Success! */
-    return 0;
+	/* Success! */
+	return 0;
 }
 
-static int sparcaudio_release(struct inode * inode, struct file * file)
+static int sparcaudio_release_ret(struct inode * inode, struct file * file)
 {
-        /* Anything in the queue? */
-        sparcaudio_sync_output(driver);
+  struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >>
+                                           SPARCAUDIO_DEVICE_SHIFT)];
 
-	/* Stop input */
-	driver->ops->stop_input(driver);
-	driver->input_active = 0;
+  if (file->f_mode & FMODE_READ) {
+    /* Stop input */
+    drv->ops->stop_input(drv);
+    drv->input_active = 0;
+  }
+
+  if (file->f_mode & FMODE_WRITE) {
+    /* Anything in the queue? */
+    if (drv->output_offset) {
+      drv->output_offset = 0;
+      drv->output_rear = (drv->output_rear + 1)
+	% drv->num_output_buffers;
+      drv->output_count++;
+    }
+    sparcaudio_sync_output(drv);
 
-	/* Wait for any output still in the queue to be played. */
-	if (driver->output_count > 0)
-		interruptible_sleep_on(&driver->output_drain_wait);
+    /* Wait for any output still in the queue to be played. */
+    if ((drv->output_count > 0) || (drv->playing_count > 0))
+      interruptible_sleep_on(&drv->output_drain_wait);
 
-	/* Force any output to be stopped. */
-	driver->ops->stop_output(driver);
-	driver->output_active = 0;
+    /* Force any output to be stopped. */
+    drv->ops->stop_output(drv);
+    drv->output_active = 0;
+    drv->playing_count = 0;
+    drv->output_eof = 0;
 
-	/* Let the low-level driver do any release processing. */
-	if (driver->ops->release)
-		driver->ops->release(inode,file,driver);
+  }
 
-	if (file->f_mode & FMODE_READ)
-		driver->flags &= ~(SDF_OPEN_READ);
+  /* Let the low-level driver do any release processing. */
+  if (drv->ops->release)
+    drv->ops->release(inode,file,drv);
 
-	if (file->f_mode & FMODE_WRITE)
-		driver->flags &= ~(SDF_OPEN_WRITE);
+  if (file->f_mode & FMODE_READ)
+    drv->flags &= ~(SDF_OPEN_READ);
 
-	MOD_DEC_USE_COUNT;
+  if (file->f_mode & FMODE_WRITE) 
+    drv->flags &= ~(SDF_OPEN_WRITE);
 
-	wake_up_interruptible(&driver->open_wait);
+  /* Status changed. Signal control device */
+  kill_procs(drv->sd_siglist,SIGPOLL,S_MSG);
 
-	return 0;
+  MOD_DEC_USE_COUNT;
+
+  wake_up_interruptible(&drv->open_wait);
+
+  return 0;
+}
+
+/* For 2.0 kernels */
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static void sparcaudio_release(struct inode * inode, struct file * file)
+{
+  sparcaudio_release_ret(inode, file);
 }
+#endif
 
 static struct file_operations sparcaudio_fops = {
-	sparcaudio_llseek,
+	sparcaudio_lseek,
 	sparcaudio_read,
 	sparcaudio_write,
 	NULL,			/* sparcaudio_readdir */
-	NULL,			/* sparcaudio_select */
+	sparcaudio_select,
 	sparcaudio_ioctl,
 	NULL,			/* sparcaudio_mmap */
 	sparcaudio_open,
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff
+      NULL,                   /* sparcaudio_flush */
+#endif
 	sparcaudio_release
 };
 
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+static struct symbol_table sparcaudio_syms = {
+#include <linux/symtab_begin.h>
+	X(register_sparcaudio_driver),
+	X(unregister_sparcaudio_driver),
+	X(sparcaudio_output_done),
+	X(sparcaudio_input_done),
+#include <linux/symtab_end.h>
+};
+#else
 EXPORT_SYMBOL(register_sparcaudio_driver);
 EXPORT_SYMBOL(unregister_sparcaudio_driver);
 EXPORT_SYMBOL(sparcaudio_output_done);
 EXPORT_SYMBOL(sparcaudio_input_done);
+#endif
 
 #ifdef MODULE
 int init_module(void)
@@ -1023,20 +2058,27 @@
 __initfunc(int sparcaudio_init(void))
 #endif
 {
+#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x20100
+	/* Export symbols for use by the low-level drivers. */
+	register_symtab(&sparcaudio_syms);
+#endif
+
 	/* Register our character device driver with the VFS. */
 	if (register_chrdev(SOUND_MAJOR, "sparcaudio", &sparcaudio_fops))
 		return -EIO;
 
+	
 #ifdef CONFIG_SPARCAUDIO_AMD7930
 	amd7930_init();
 #endif
-
+#ifdef CONFIG_SPARCAUDIO_DBRI
+        dbri_init();
+#endif
 #ifdef CONFIG_SPARCAUDIO_CS4231
 	cs4231_init();
 #endif
-
-#ifdef CONFIG_SPARCAUDIO_DBRI
-	dbri_init();
+#ifdef CONFIG_SPARCAUDIO_DUMMY
+	dummy_init();
 #endif
 
 	return 0;
@@ -1048,6 +2090,147 @@
 	unregister_chrdev(SOUND_MAJOR, "sparcaudio");
 }
 #endif
+
+/*
+ * Code from Linux Streams, Copyright 1995 by
+ * Graham Wheeler, Francisco J. Ballesteros, Denis Froschauer
+ * and available under GPL 
+ */
+
+static int
+lis_add_to_elist( strevent_t **list, pid_t pid, short events )
+{
+    strevent_t *ev = NULL;
+
+    if (*list != NULL)
+      {
+        for (ev=(*list)->se_next;
+             ev != *list && ev->se_pid < pid;
+             ev=ev->se_next
+            );    
+      }
+
+    if (ev == NULL || ev == *list)              /* no slot for pid in list */
+      {
+        if ((ev = (strevent_t*)kmalloc(sizeof(strevent_t),GFP_KERNEL))==NULL)
+            return(-ENOMEM);
+
+        if (!*list)                     /* create dummy head node */
+	  {
+            strevent_t *hd;
+            if ((hd = (strevent_t*)kmalloc(sizeof(strevent_t),GFP_KERNEL)
+                                         )==NULL)
+	      {
+                kfree(ev);
+                return(-ENOMEM);
+	      }
+            (*list=hd)->se_pid=0;
+            hd->se_next=hd->se_prev=hd;         /* empty list */
+	  }
+
+        /* link node last in the list */
+        ev->se_prev=(*list)->se_prev;
+        (*list)->se_prev->se_next=ev;
+        ((*list)->se_prev=ev)->se_next=*list;
+
+        ev->se_pid=pid;
+        ev->se_evs=0;
+      }
+    else if (ev->se_pid!=pid){  /* link node in the middle of the list */
+        strevent_t *new;
+        if ((new = (strevent_t*)kmalloc(sizeof(strevent_t),GFP_KERNEL))==NULL){
+            return(-ENOMEM);
+        }
+        new->se_prev=ev->se_prev;
+        new->se_next=ev;
+        ev->se_prev->se_next=new;
+        ev->se_prev=new;
+        ev = new ;                              /* use new element */
+        ev->se_pid=pid;
+        ev->se_evs=0;
+    }
+    ev->se_evs|=events;
+    return(0);
+}
+
+static int
+lis_del_from_elist( strevent_t **list, pid_t pid, short events )
+{
+    strevent_t *ev = NULL;     
+
+    if (*list != NULL)
+      {
+        for (ev=(*list)->se_next;
+             ev != *list && ev->se_pid < pid;
+             ev=ev->se_next
+            );    
+      }
+
+    if (ev == NULL || ev == *list || ev->se_pid != pid )
+        return(1);
+
+    if ( (ev->se_evs &= ~events) == 0 ){        /* unlink */
+        if (ev->se_next)                        /* should always be true */
+            ev->se_next->se_prev=ev->se_prev;
+        if (ev->se_prev)                        /* should always be true */
+            ev->se_prev->se_next=ev->se_next;
+        kfree(ev);
+    }
+    return(0);
+}
+
+static void
+lis_free_elist( strevent_t **list )
+{
+    strevent_t  *ev;     
+    strevent_t  *nxt ;
+
+    for (ev = *list; ev != NULL; )
+      {
+        nxt = ev->se_next ;
+        kfree(ev) ;
+        ev = nxt ;
+        if (ev == *list) break ;                /* all done */
+      }
+
+    *list = NULL ;
+}
+
+static short
+lis_get_elist_ent( strevent_t *list, pid_t pid )
+{
+    strevent_t *ev = NULL;
+
+    if (list == NULL) return(0) ;
+
+    for(ev = list->se_next ; ev != list && ev->se_pid < pid; ev=ev->se_next )
+        ;
+    if (ev != list && ev->se_pid == pid)
+        return(ev->se_evs);
+    else
+        return(0);
+}
+
+static void 
+kill_procs( struct strevent *elist, int sig, short e)
+{
+  strevent_t *ev;
+  int res;
+
+  (void) sig ;
+  if (elist) {
+    for(ev = elist->se_next ; ev != elist; ev=ev->se_next )
+      if ((ev->se_evs & e) != 0){
+	if ((res=kill_proc(ev->se_pid,SIGPOLL,1))<0) {
+	  if (res == -3) {
+	    lis_del_from_elist(&elist, ev->se_pid, S_ALL);
+	    continue;
+	  }
+	  dprintk(("kill_proc: errno %d\n",res));
+	}
+      }
+  }
+}
 
 /*
  * Overrides for Emacs so that we follow Linus's tabbing style.

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov