patch-2.1.101 linux/drivers/sbus/audio/cs4231.c

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

diff -u --recursive --new-file v2.1.100/linux/drivers/sbus/audio/cs4231.c linux/drivers/sbus/audio/cs4231.c
@@ -1,13 +1,20 @@
 /*
- * drivers/sbus/audio/cs4231.c
+ * Drivers/sbus/audio/cs4231.c
  *
+ * Copyright (C) 1996, 1997 Derrick J Brashear (shadow@andrew.cmu.edu)
+ *
+ * Based on the AMD7930 driver:
  * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
- * Copyright (C) 1996 Derrick J Brashear (shadow@andrew.cmu.edu)
  *
  * This is the lowlevel driver for the CS4231 audio chip found on some
- * sun4m machines.
+ * sun4m and sun4u machines.
+ * 
+ * This was culled from the Crystal docs on the 4231a, and the addendum they
+ * faxed me on the 4231.
+ * The APC DMA controller support unfortunately is not documented. Thanks, Sun
  */
 
+#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/sched.h>
@@ -15,15 +22,15 @@
 #include <linux/interrupt.h>
 #include <linux/malloc.h>
 #include <linux/init.h>
+#include <linux/delay.h>
 #include <asm/openprom.h>
 #include <asm/oplib.h>
 #include <asm/system.h>
 #include <asm/irq.h>
 #include <asm/io.h>
-#include <asm/delay.h>
 #include <asm/sbus.h>
 
-#include "audio.h"
+#include <asm/audioio.h>
 #include "cs4231.h"
 
 /* Stolen for now from compat.h */
@@ -34,194 +41,700 @@
 #define MIN(_a,_b)      ((_a)<(_b)?(_a):(_b))
 #endif
 
+#undef __CS4231_DEBUG
+#undef __CS4231_TRACE
+#undef __CS4231_ERROR
+#ifdef __CS4231_ERROR
+#define eprintk(x) printk x
+#else
+#define eprintk(x)
+#endif
+#ifdef __CS4231_TRACE
+#define tprintk(x) printk x
+#else
+#define tprintk(x)
+#endif
+#ifdef __CS4231_DEBUG
+#define dprintk(x) printk x
+#else
+#define dprintk(x)
+#endif
+
 #define MAX_DRIVERS 1
 static struct sparcaudio_driver drivers[MAX_DRIVERS];
 static int num_drivers;
 
-static int cs4231_playintr(struct sparcaudio_driver *drv);
+static int cs4231_record_gain(struct sparcaudio_driver *drv, int value, unsigned char balance);
+static int cs4231_play_gain(struct sparcaudio_driver *drv, int value, unsigned char balance);
+static void cs4231_ready(struct sparcaudio_driver *drv);
+static void cs4231_playintr(struct sparcaudio_driver *drv);
 static int cs4231_recintr(struct sparcaudio_driver *drv);
-static void cs4231_output_muted(struct sparcaudio_driver *drv, unsigned int value);
-static void cs4231_mute(struct sparcaudio_driver *drv);
+static int cs4231_output_muted(struct sparcaudio_driver *drv, int value);
 static void cs4231_pollinput(struct sparcaudio_driver *drv);
+static int cs4231_length_to_samplecount(struct audio_prinfo *thisdir, unsigned int length);
+static void cs4231_getsamplecount(struct sparcaudio_driver *drv, unsigned int length, unsigned int value);
 
-#define CHIP_BUG udelay(100); cs4231_ready(drv); udelay(1000);
+#define CHIP_READY udelay(100); cs4231_ready(drv); udelay(1000);
 
-/* Disable mode change, let chip auto-calibrate */
-static void cs4231_ready(struct sparcaudio_driver *drv) 
+/* Enable cs4231 interrupts atomically. */
+static __inline__ void cs4231_enable_interrupts(struct sparcaudio_driver *drv)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  unsigned int x = 0;
+  register unsigned long flags;
 
-  cs4231_chip->pioregs->iar = (u_char)IAR_AUTOCAL_END;
-  while (cs4231_chip->pioregs->iar == IAR_NOT_READY && x <= CS_TIMEOUT) {
-    x++;
+  if (cs4231_chip->status & CS_STATUS_INTS_ON)
+    return;
+
+  tprintk(("enabling interrupts\n"));
+  save_flags(flags);
+  cli();
+  cs4231_chip->regs->iar = 0xa;
+  cs4231_chip->regs->idr = INTR_ON;
+  restore_flags(flags);
+
+  cs4231_chip->status |= CS_STATUS_INTS_ON;
+}
+
+/* Disable cs4231 interrupts atomically. */
+static __inline__ void cs4231_disable_interrupts(struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  register unsigned long flags;
+
+  if (!(cs4231_chip->status & CS_STATUS_INTS_ON))
+    return;
+
+  tprintk(("disabling interrupts\n"));
+  save_flags(flags);
+  cli();
+  cs4231_chip->regs->iar = 0xa;
+  cs4231_chip->regs->idr = INTR_OFF;
+  restore_flags(flags);
+
+  cs4231_chip->status &= ~CS_STATUS_INTS_ON;
+}
+
+static __inline__ void cs4231_enable_play(struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  register unsigned long flags;
+
+  tprintk(("enabling play\n"));
+  save_flags(flags);
+  cli();
+  cs4231_chip->regs->iar = 0x9;
+  cs4231_chip->regs->idr |= PEN_ENABLE;
+  restore_flags(flags);
+}
+
+static __inline__ void cs4231_disable_play(struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  register unsigned long flags;
+
+  tprintk(("disabling play\n"));
+  save_flags(flags);
+  cli();
+  cs4231_chip->regs->iar = 0x9;
+  cs4231_chip->regs->idr &= PEN_DISABLE;
+  restore_flags(flags);
+}
+
+static __inline__ void cs4231_enable_rec(struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  register unsigned long flags;
+
+  tprintk(("enabling rec\n"));
+  save_flags(flags);
+  cli();
+  cs4231_chip->regs->iar = 0x9;
+  cs4231_chip->regs->idr |= CEN_ENABLE;
+  restore_flags(flags);
+}
+
+static __inline__ void cs4231_disable_rec(struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  register unsigned long flags;
+
+  tprintk(("disabling rec\n"));
+  save_flags(flags);
+  cli();
+  cs4231_chip->regs->iar = 0x9;
+  cs4231_chip->regs->idr &= CEN_DISABLE;
+  restore_flags(flags);
+}
+
+static int 
+cs4231_rate_to_bits(struct sparcaudio_driver *drv, int value)
+{
+  int set_bits;
+
+  switch (value) {
+  case 5512:
+    set_bits = CS4231_DFR_5512;
+    break;
+  case 6615:
+    set_bits = CS4231_DFR_6615;
+    break;
+  case 8000:
+    set_bits = CS4231_DFR_8000;
+    break;
+  case 9600:
+    set_bits = CS4231_DFR_9600;
+    break;
+  case 11025:
+    set_bits = CS4231_DFR_11025;
+    break;
+  case 16000:
+    set_bits = CS4231_DFR_16000;
+    break;
+  case 18900:
+    set_bits = CS4231_DFR_18900;
+    break;
+  case 22050:
+    set_bits = CS4231_DFR_22050;
+    break;
+  case 27429:
+    set_bits = CS4231_DFR_27429;
+    break;
+  case 32000:
+    set_bits = CS4231_DFR_32000;
+    break;
+  case 33075:
+    set_bits = CS4231_DFR_33075;
+    break;
+  case 37800:
+    set_bits = CS4231_DFR_37800;
+    break;
+  case 44100:
+    set_bits = CS4231_DFR_44100;
+    break;
+  case 48000:
+    set_bits = CS4231_DFR_48000;
+    break;
+  default:
+    set_bits = -(EINVAL);
+    break;
   }
+  return set_bits;
+}
 
-  x = 0;
-  cs4231_chip->pioregs->iar = 0x0b;
-  while (cs4231_chip->pioregs->idr == AUTOCAL_IN_PROGRESS && x <= CS_TIMEOUT) {
-    x++;
+static int 
+cs4231_encoding_to_bits(struct sparcaudio_driver *drv, int value)
+{
+  int set_bits;
+  
+  switch (value) {
+  case AUDIO_ENCODING_ULAW:
+    set_bits = CS4231_DFR_ULAW;
+    break;
+  case AUDIO_ENCODING_ALAW:
+    set_bits = CS4231_DFR_ALAW;
+    break;
+  case AUDIO_ENCODING_DVI:
+    set_bits = CS4231_DFR_ADPCM;
+    break;
+  case AUDIO_ENCODING_LINEARLE:
+    set_bits = CS4231_DFR_LINEARLE;
+    break;
+  case AUDIO_ENCODING_LINEAR:
+    set_bits = CS4231_DFR_LINEARBE;
+    break;
+  case AUDIO_ENCODING_LINEAR8:
+    set_bits = CS4231_DFR_LINEAR8;
+    break;
+  default:
+    set_bits = -(EINVAL);
+    break;
   }
+  
+  return set_bits;
 }
 
-/* Audio interrupt handler. */
-static void cs4231_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+static int
+cs4231_set_output_encoding(struct sparcaudio_driver *drv, int value)
 {
-  struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id;
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  __u8 dummy;
-  int ic = 1;
-  
-  /* Clear the interrupt. */
-  dummy = cs4231_chip->dmaregs.dmacsr;
-  cs4231_chip->dmaregs.dmacsr = dummy;
+  int tmp_bits, set_bits;
 
-  /* now go through and figure out what gets to claim the interrupt */
-  if (dummy & CS_PLAY_INT) {
-    if (dummy & CS_XINT_PNVA) {
-      /* recalculate number of samples */
-      cs4231_playintr(drv);
+  tprintk(("output encoding %d\n", value));
+  if (value != 0) {
+    set_bits = cs4231_encoding_to_bits(drv, value);
+    if (set_bits >= 0) {
+      cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x8;
+      tmp_bits = cs4231_chip->regs->idr;
+      cs4231_chip->regs->idr = CHANGE_ENCODING(tmp_bits, set_bits);
+    
+      CHIP_READY
+
+      cs4231_chip->perchip_info.play.encoding = value;
+      return 0;
     }
-    ic = 0;
   }
-  if (dummy & CS_CAPT_INT) {
-    if (dummy & CS_XINT_CNVA) {
-      /* recalculate number of samples */
-      cs4231_recintr(drv);
+  eprintk(("output enc failed\n"));
+  return -EINVAL;
+}
+
+static int cs4231_get_output_encoding(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.play.encoding;
+}
+
+static int
+cs4231_set_input_encoding(struct sparcaudio_driver *drv, int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int tmp_bits, set_bits;
+
+  dprintk(("input encoding %d\n", value));
+  if (value != 0) {
+    set_bits = cs4231_encoding_to_bits(drv, value);
+    if (set_bits >= 0) {
+      cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x1c;
+      tmp_bits = cs4231_chip->regs->idr;
+      cs4231_chip->regs->idr = CHANGE_ENCODING(tmp_bits, set_bits);
+
+      CHIP_READY
+
+      cs4231_chip->perchip_info.record.encoding = value;
+      return 0;
     }
-    ic = 0;
   }
-  if ((dummy & CS_XINT_CEMP) 
-      && (cs4231_chip->perchip_info.record.active == 0)) 
-    {
-      ic = 0;
+  eprintk(("input enc failed\n"));
+  return -EINVAL;
+}
+
+static int cs4231_get_input_encoding(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.record.encoding;
+}
+
+static int
+cs4231_set_output_rate(struct sparcaudio_driver *drv, int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int tmp_bits, set_bits;
+
+  tprintk(("output rate %d\n", value));
+  if (value != 0) {
+    set_bits = cs4231_rate_to_bits(drv, value);
+    if (set_bits >= 0) {
+      cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x8;
+      tmp_bits = cs4231_chip->regs->idr;
+      cs4231_chip->regs->idr = CHANGE_DFR(tmp_bits, set_bits);
+
+      CHIP_READY
+
+      cs4231_chip->perchip_info.play.sample_rate = value;
+      return 0;
     }
-  if ((dummy & CS_XINT_EMPT) && (cs4231_chip->perchip_info.play.active == 0)) {
-    cs4231_chip->dmaregs.dmacsr |= (CS_PPAUSE);
-    cs4231_chip->pioregs->iar = 0x9;
-    cs4231_chip->pioregs->idr &= PEN_DISABLE;
-    
-    cs4231_mute(drv);
+  }
+  eprintk(("output rate failed\n"));
+  return -EINVAL;
+}
+
+static int cs4231_get_output_rate(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.play.sample_rate;
+}
+
+static int
+cs4231_set_input_rate(struct sparcaudio_driver *drv, int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int tmp_bits, set_bits;
+
+  dprintk(("input rate %d\n", value));
+  if (value != 0) {
+    set_bits = cs4231_rate_to_bits(drv, value);
+    if (set_bits >= 0) {
+      cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x1c;
+      tmp_bits = cs4231_chip->regs->idr;
+      cs4231_chip->regs->idr = CHANGE_DFR(tmp_bits, set_bits);
+      
+      CHIP_READY
+
+      cs4231_chip->perchip_info.record.sample_rate = value;
+      return 0;
+    }
+  }
+  eprintk(("input rate failed\n"));
+  return -EINVAL;
+}
+
+static int cs4231_get_input_rate(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.record.sample_rate;
+}
+
+/* Generically we support 4 channels. This hardware does 2 */
+static int
+cs4231_set_input_channels(struct sparcaudio_driver *drv, int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int tmp_bits;
+
+  dprintk(("input channels %d\n", value));
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x1c;
+  tmp_bits = cs4231_chip->regs->idr;
+  switch (value) {
+  case 1:
+    cs4231_chip->regs->idr = CS4231_MONO_ON(tmp_bits);
+    break;
+  case 2:
+    cs4231_chip->regs->idr = CS4231_STEREO_ON(tmp_bits);
+    break;
+  default:
+    eprintk(("input chan failed\n"));
+    return -(EINVAL);
+  }  
+
+  CHIP_READY
+
+  cs4231_chip->perchip_info.record.channels = value;
+  return 0;
+}
+
+static int cs4231_get_input_channels(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.record.channels;
+}
+
+/* Generically we support 4 channels. This hardware does 2 */
+static int
+cs4231_set_output_channels(struct sparcaudio_driver *drv, int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int tmp_bits;
+
+  tprintk(("output channels %d\n", value));
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x8;
+  tmp_bits = cs4231_chip->regs->idr;
+  switch (value) {
+  case 1:
+    cs4231_chip->regs->idr = CS4231_MONO_ON(tmp_bits);
+    break;
+  case 2:
+    cs4231_chip->regs->idr = CS4231_STEREO_ON(tmp_bits);
+    break;
+  default:
+    eprintk(("output chan failed\n"));
+    return -(EINVAL);
+  }  
+
+  CHIP_READY
     
-    /* recalculate number of samples */
-    /* cleanup DMA */
-    ic = 0;
+  cs4231_chip->perchip_info.play.channels = value;
+  return 0;
+}
+
+static int cs4231_get_output_channels(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.play.channels;
+}
+
+static int cs4231_set_input_precision(struct sparcaudio_driver *drv, int value)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      cs4231_chip->perchip_info.record.precision = value;
+      return 0;
+}
+
+static int cs4231_get_input_precision(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.record.precision;
+}
+
+static int cs4231_set_output_precision(struct sparcaudio_driver *drv, int value)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      cs4231_chip->perchip_info.play.precision = value;
+      return 0;
+}
+
+static int cs4231_get_output_precision(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.play.precision;
+}
+
+/* Wait until the auto calibration process has finished */
+static void
+cs4231_ready(struct sparcaudio_driver *drv) 
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  unsigned int x = 0;
+
+  cs4231_chip->regs->iar = IAR_AUTOCAL_END;
+  while (cs4231_chip->regs->iar == IAR_NOT_READY && x <= CS_TIMEOUT) {
+    x++;
   }
-  if (dummy & CS_GENL_INT) {
-    ic = 0;
+
+  x = 0;
+  cs4231_chip->regs->iar = 0x0b;
+  while (cs4231_chip->regs->idr == AUTOCAL_IN_PROGRESS && x <= CS_TIMEOUT) {
+    x++;
   }
 }
 
 /* Set output mute */
-static void cs4231_output_muted(struct sparcaudio_driver *drv, unsigned int value)
+static int cs4231_output_muted(struct sparcaudio_driver *drv, int value)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  tprintk(("in cs4231_output_muted: %d\n", value));
   if (!value) {
-    cs4231_chip->pioregs->iar = 0x7;
-    cs4231_chip->pioregs->idr &= OUTCR_UNMUTE;
-    cs4231_chip->pioregs->iar = 0x6;
-    cs4231_chip->pioregs->idr &= OUTCR_UNMUTE;
+    cs4231_chip->regs->iar = 0x7;
+    cs4231_chip->regs->idr &= OUTCR_UNMUTE;
+    cs4231_chip->regs->iar = 0x6;
+    cs4231_chip->regs->idr &= OUTCR_UNMUTE;
     cs4231_chip->perchip_info.output_muted = 0;
   } else {
-    cs4231_chip->pioregs->iar = 0x7;
-    cs4231_chip->pioregs->idr |= OUTCR_MUTE;
-    cs4231_chip->pioregs->iar = 0x6;
-    cs4231_chip->pioregs->idr |= OUTCR_MUTE;
+    cs4231_chip->regs->iar = 0x7;
+    cs4231_chip->regs->idr |= OUTCR_MUTE;
+    cs4231_chip->regs->iar = 0x6;
+    cs4231_chip->regs->idr |= OUTCR_MUTE;
     cs4231_chip->perchip_info.output_muted = 1;
   }
-  return /*(cs4231_chip->perchip_info.output_muted)*/;
+  return 0;
 }
 
-/* Set chip "output" port */
-static unsigned int cs4231_out_port(struct sparcaudio_driver *drv, unsigned int value)
+static int cs4231_get_output_muted(struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  return cs4231_chip->perchip_info.output_muted;
+}
+
+static int cs4231_get_output_ports(struct sparcaudio_driver *drv)
+{
+      return (AUDIO_LINE_OUT | AUDIO_SPEAKER | AUDIO_HEADPHONE);
+}
+
+static int cs4231_get_input_ports(struct sparcaudio_driver *drv)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  unsigned int r = 0;
+  if (cs4231_chip->status & CS_STATUS_IS_ULTRA)
+    return (AUDIO_LINE_IN | AUDIO_MICROPHONE | AUDIO_ANALOG_LOOPBACK);
+  else
+    return (AUDIO_INTERNAL_CD_IN | AUDIO_LINE_IN | AUDIO_MICROPHONE | 
+	    AUDIO_ANALOG_LOOPBACK);
+}
 
-  /* You can have any combo you want. Just don't tell anyone. */
+/* Set chip "output" port */
+static int cs4231_set_output_port(struct sparcaudio_driver *drv, int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int retval = 0;
 
-  cs4231_chip->pioregs->iar = 0x1a;
-  cs4231_chip->pioregs->idr |= MONO_IOCR_MUTE;
-  cs4231_chip->pioregs->iar = 0x0a;
-  cs4231_chip->pioregs->idr |= PINCR_LINE_MUTE;
-  cs4231_chip->pioregs->idr |= PINCR_HDPH_MUTE;
+  tprintk(("output port: %d\n", value));
+  /* Aaaaaah! It's all coming so fast! Turn it all off, then selectively
+   * enable things.
+   */
+  cs4231_chip->regs->iar = 0x1a;
+  cs4231_chip->regs->idr |= MONO_IOCR_MUTE;
+  cs4231_chip->regs->iar = 0x0a;
+  cs4231_chip->regs->idr |= PINCR_LINE_MUTE;
+  cs4231_chip->regs->idr |= PINCR_HDPH_MUTE;
 
   if (value & AUDIO_SPEAKER) {
-    cs4231_chip->pioregs->iar = 0x1a;
-    cs4231_chip->pioregs->idr &= ~MONO_IOCR_MUTE;
-    r |= AUDIO_SPEAKER;
+    cs4231_chip->regs->iar = 0x1a;
+    cs4231_chip->regs->idr &= ~MONO_IOCR_MUTE;
+    retval |= AUDIO_SPEAKER;
   }
 
   if (value & AUDIO_HEADPHONE) {
-   cs4231_chip->pioregs->iar = 0x0a;
-   cs4231_chip->pioregs->idr &= ~PINCR_HDPH_MUTE;
-   r |= AUDIO_HEADPHONE;
+   cs4231_chip->regs->iar = 0x0a;
+   cs4231_chip->regs->idr &= ~PINCR_HDPH_MUTE;
+   retval |= AUDIO_HEADPHONE;
   }
 
   if (value & AUDIO_LINE_OUT) {
-    cs4231_chip->pioregs->iar = 0x0a;
-    cs4231_chip->pioregs->idr &= ~PINCR_LINE_MUTE;
-    r |= AUDIO_LINE_OUT;
+    cs4231_chip->regs->iar = 0x0a;
+    cs4231_chip->regs->idr &= ~PINCR_LINE_MUTE;
+    retval |= AUDIO_LINE_OUT;
   }
   
-  return (r);
+  cs4231_chip->perchip_info.play.port = retval;
+
+  return (retval);
+}
+
+static int cs4231_get_output_port(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.play.port;
 }
 
 /* Set chip "input" port */
-static unsigned int cs4231_in_port(struct sparcaudio_driver *drv, unsigned int value)
+static int cs4231_set_input_port(struct sparcaudio_driver *drv, int value)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  unsigned int r = 0;
+  int retval = 0;
 
-  /* The order of these seems to matter. Can't tell yet why. */
-  if (value & AUDIO_INTERNAL_CD_IN) {
-    cs4231_chip->pioregs->iar = 0x1;
-    cs4231_chip->pioregs->idr = CDROM_ENABLE(cs4231_chip->pioregs->idr);
-    cs4231_chip->pioregs->iar = 0x0;
-    cs4231_chip->pioregs->idr = CDROM_ENABLE(cs4231_chip->pioregs->idr);
-    r = AUDIO_INTERNAL_CD_IN;
+  dprintk(("input port: %d\n", value));
+
+  /* You can have one and only one. This is probably wrong, but
+   * appears to be how SunOS is doing it. Should be able to mix.
+   * More work to be done.
+   */
+
+  /* Ultra systems do not support AUDIO_INTERNAL_CD_IN */
+  if (!cs4231_chip->status & CS_STATUS_IS_ULTRA) {
+    if (value & AUDIO_INTERNAL_CD_IN) {
+      cs4231_chip->regs->iar = 0x1;
+      cs4231_chip->regs->idr = CDROM_ENABLE(cs4231_chip->regs->idr);
+      cs4231_chip->regs->iar = 0x0;
+      cs4231_chip->regs->idr = CDROM_ENABLE(cs4231_chip->regs->idr);
+      retval = AUDIO_INTERNAL_CD_IN;
+    }
   }
   if ((value & AUDIO_LINE_IN)) {
-    cs4231_chip->pioregs->iar = 0x1;
-    cs4231_chip->pioregs->idr = LINE_ENABLE(cs4231_chip->pioregs->idr);
-    cs4231_chip->pioregs->iar = 0x0;
-    cs4231_chip->pioregs->idr = LINE_ENABLE(cs4231_chip->pioregs->idr);
-    r = AUDIO_LINE_IN;
+    cs4231_chip->regs->iar = 0x1;
+    cs4231_chip->regs->idr = LINE_ENABLE(cs4231_chip->regs->idr);
+    cs4231_chip->regs->iar = 0x0;
+    cs4231_chip->regs->idr = LINE_ENABLE(cs4231_chip->regs->idr);
+    retval = AUDIO_LINE_IN;
   } else if (value & AUDIO_MICROPHONE) {
-    cs4231_chip->pioregs->iar = 0x1;
-    cs4231_chip->pioregs->idr = MIC_ENABLE(cs4231_chip->pioregs->idr);
-    cs4231_chip->pioregs->iar = 0x0;
-    cs4231_chip->pioregs->idr = MIC_ENABLE(cs4231_chip->pioregs->idr);
-    r = AUDIO_MICROPHONE;
+    cs4231_chip->regs->iar = 0x1;
+    cs4231_chip->regs->idr = MIC_ENABLE(cs4231_chip->regs->idr);
+    cs4231_chip->regs->iar = 0x0;
+    cs4231_chip->regs->idr = MIC_ENABLE(cs4231_chip->regs->idr);
+    retval = AUDIO_MICROPHONE;
+  } else if (value & AUDIO_ANALOG_LOOPBACK) {
+    cs4231_chip->regs->iar = 0x1;
+    cs4231_chip->regs->idr = OUTPUTLOOP_ENABLE(cs4231_chip->regs->idr);
+    cs4231_chip->regs->iar = 0x0;
+    cs4231_chip->regs->idr = OUTPUTLOOP_ENABLE(cs4231_chip->regs->idr);
+    retval = AUDIO_ANALOG_LOOPBACK;
   }
 
-  return (r);
+  cs4231_chip->perchip_info.record.port = retval;
+
+  return (retval);
+}
+
+static int cs4231_get_input_port(struct sparcaudio_driver *drv)
+{
+      struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+      return cs4231_chip->perchip_info.record.port;
 }
 
 /* Set chip "monitor" gain */
-static unsigned int cs4231_monitor_gain(struct sparcaudio_driver *drv, unsigned int value)
+static int cs4231_set_monitor_volume(struct sparcaudio_driver *drv, int value)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
   int a = 0;
 
+  tprintk(("monitor gain: %d\n", value));
+
+  /* This interpolation really sucks. The question is, be compatible 
+   * with ScumOS/Sloaris or not?
+   */
   a = CS4231_MON_MAX_ATEN - (value * (CS4231_MON_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1));
 
-  cs4231_chip->pioregs->iar = 0x0d;
+  cs4231_chip->regs->iar = 0x0d;
   if (a >= CS4231_MON_MAX_ATEN) 
-    cs4231_chip->pioregs->idr = LOOPB_OFF;
+    cs4231_chip->regs->idr = LOOPB_OFF;
+  else 
+    cs4231_chip->regs->idr = ((a << 2) | LOOPB_ON);
+
+  if (value == AUDIO_MAX_GAIN) 
+    cs4231_chip->perchip_info.monitor_gain = AUDIO_MAX_GAIN;
   else 
-    cs4231_chip->pioregs->idr = ((a << 2) | LOOPB_ON);
+    cs4231_chip->perchip_info.monitor_gain = ((CS4231_MAX_DEV_ATEN - a) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_DEV_ATEN + 1));
+
+  return 0;
+}
+
+static int cs4231_get_monitor_volume(struct sparcaudio_driver *drv)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+        return (int)cs4231_chip->perchip_info.monitor_gain;
+}
+
+/* But for play/record we have these cheesy jacket routines because of 
+ * how this crap gets set */
+static int cs4231_set_input_volume(struct sparcaudio_driver *drv, int value)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+	cs4231_record_gain(drv, value, cs4231_chip->perchip_info.record.balance);
+	
+        return 0;
+}
+
+static int cs4231_get_input_volume(struct sparcaudio_driver *drv)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+        return (int)cs4231_chip->perchip_info.record.gain;
+}
+
+static int cs4231_set_output_volume(struct sparcaudio_driver *drv, int value)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+	cs4231_play_gain(drv, value, cs4231_chip->perchip_info.play.balance);
+	
+        return 0;
+}
+
+static int cs4231_get_output_volume(struct sparcaudio_driver *drv)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+        return cs4231_chip->perchip_info.play.gain;
+}
+
+/* Likewise for balance */
+static int cs4231_set_input_balance(struct sparcaudio_driver *drv, int value)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+	cs4231_chip->perchip_info.record.balance = value;
+	cs4231_record_gain(drv, cs4231_chip->perchip_info.record.gain, cs4231_chip->perchip_info.record.balance);
+	
+        return 0;
+}
+
+static int cs4231_get_input_balance(struct sparcaudio_driver *drv)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+        return (int)cs4231_chip->perchip_info.record.balance;
+}
+
+static int cs4231_set_output_balance(struct sparcaudio_driver *drv, int value)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
-  if (value == AUDIO_MAX_GAIN) return AUDIO_MAX_GAIN;
+	cs4231_chip->perchip_info.play.balance = value;
+	cs4231_play_gain(drv, cs4231_chip->perchip_info.play.gain, cs4231_chip->perchip_info.play.balance);
+	
+        return 0;
+}
+
+static int cs4231_get_output_balance(struct sparcaudio_driver *drv)
+{
+        struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
-  return ((CS4231_MAX_DEV_ATEN - a) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_DEV_ATEN + 1));
+        return (int)cs4231_chip->perchip_info.play.balance;
 }
 
 /* Set chip record gain */
-static unsigned int cs4231_record_gain(struct sparcaudio_driver *drv, unsigned int value, unsigned char balance)
+static int cs4231_record_gain(struct sparcaudio_driver *drv, int value, unsigned char balance)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  unsigned int tmp = 0, r, l, ra, la;
+  int tmp = 0, r, l, r_adj, l_adj;
   unsigned char old_gain;
 
   r = l = value;
@@ -232,31 +745,33 @@
     l = MAX(0, (int)(value - ((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT)));
   }
 
-  la = l * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
-  ra = r * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
+  l_adj = l * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
+  r_adj = r * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
   
-  cs4231_chip->pioregs->iar = 0x0;
-  old_gain = cs4231_chip->pioregs->idr;
-  cs4231_chip->pioregs->idr = RECGAIN_SET(old_gain, la);
-  cs4231_chip->pioregs->iar = 0x1;
-  old_gain = cs4231_chip->pioregs->idr;
-  cs4231_chip->pioregs->idr = RECGAIN_SET(old_gain, ra);
+  cs4231_chip->regs->iar = 0x0;
+  old_gain = cs4231_chip->regs->idr;
+  cs4231_chip->regs->idr = RECGAIN_SET(old_gain, l_adj);
+  cs4231_chip->regs->iar = 0x1;
+  old_gain = cs4231_chip->regs->idr;
+  cs4231_chip->regs->idr = RECGAIN_SET(old_gain, r_adj);
   
   if (l == value) {
-    (l == 0) ? (tmp = 0) : (tmp = ((la + 1) * AUDIO_MAX_GAIN) / (CS4231_MAX_GAIN + 1));
+    (l == 0) ? (tmp = 0) : (tmp = ((l_adj + 1) * AUDIO_MAX_GAIN) / (CS4231_MAX_GAIN + 1));
   } else if (r == value) {
-    (r == 0) ? (tmp = 0) : (tmp = ((ra + 1) * AUDIO_MAX_GAIN) / (CS4231_MAX_GAIN + 1));
+    (r == 0) ? (tmp = 0) : (tmp = ((r_adj + 1) * AUDIO_MAX_GAIN) / (CS4231_MAX_GAIN + 1));
   }
-  return (tmp);
+  cs4231_chip->perchip_info.record.gain = tmp;
+  return 0;
 }
 
 /* Set chip play gain */
-static unsigned int cs4231_play_gain(struct sparcaudio_driver *drv, unsigned int value, unsigned char balance)
+static int cs4231_play_gain(struct sparcaudio_driver *drv, int value, unsigned char balance)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  unsigned int tmp = 0, r, l, ra, la;
+  int tmp = 0, r, l, r_adj, l_adj;
   unsigned char old_gain;
 
+  tprintk(("in play_gain: %d %c\n", value, balance));
   r = l = value;
   if (balance < AUDIO_MID_BALANCE) {
     r = MAX(0, (int)(value - ((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT)));
@@ -264,156 +779,179 @@
     l = MAX(0, (int)(value - ((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT)));
   }
 
-  if (l == 0) {
-    la = CS4231_MAX_DEV_ATEN;
-  } else {
-    la = CS4231_MAX_ATEN - (l * (CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1));
-  }
-  if (r == 0) {
-    ra = CS4231_MAX_DEV_ATEN;
-  } else {
-    ra = CS4231_MAX_ATEN - (r * (CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1));
-  }
+  (l == 0) ? (l_adj = CS4231_MAX_DEV_ATEN) : (l_adj = CS4231_MAX_ATEN - (l * (CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1)));
+  (r == 0) ? (r_adj = CS4231_MAX_DEV_ATEN) : (r_adj = CS4231_MAX_ATEN - (r * (CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1)));
   
-  cs4231_chip->pioregs->iar = 0x6;
-  old_gain = cs4231_chip->pioregs->idr;
-  cs4231_chip->pioregs->idr = GAIN_SET(old_gain, la);
-  cs4231_chip->pioregs->iar = 0x7;
-  old_gain = cs4231_chip->pioregs->idr;
-  cs4231_chip->pioregs->idr = GAIN_SET(old_gain, ra);
+  cs4231_chip->regs->iar = 0x6;
+  old_gain = cs4231_chip->regs->idr;
+  cs4231_chip->regs->idr = GAIN_SET(old_gain, l_adj);
+  cs4231_chip->regs->iar = 0x7;
+  old_gain = cs4231_chip->regs->idr;
+  cs4231_chip->regs->idr = GAIN_SET(old_gain, r_adj);
   
   if ((value == 0) || (value == AUDIO_MAX_GAIN)) {
     tmp = value;
   } else {
     if (l == value) {
-      tmp = ((CS4231_MAX_ATEN - la) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_ATEN + 1));
+      tmp = ((CS4231_MAX_ATEN - l_adj) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_ATEN + 1));
     } else if (r == value) {
-      tmp = ((CS4231_MAX_ATEN - ra) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_ATEN + 1));
+      tmp = ((CS4231_MAX_ATEN - r_adj) * (AUDIO_MAX_GAIN + 1) / (CS4231_MAX_ATEN + 1));
     }
   }
-  return (tmp);
+  cs4231_chip->perchip_info.play.gain = tmp;
+
+  return 0;
 }
 
 /* Reset the audio chip to a sane state. */
-static void cs4231_reset(struct sparcaudio_driver *drv)
+static void cs4231_chip_reset(struct sparcaudio_driver *drv)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
-  cs4231_chip->dmaregs.dmacsr = CS_CHIP_RESET;
-  cs4231_chip->dmaregs.dmacsr = 0x00;
-  cs4231_chip->dmaregs.dmacsr |= CS_CDC_RESET;
-  
-  udelay(100);
-  
-  cs4231_chip->dmaregs.dmacsr &= ~(CS_CDC_RESET);
-  cs4231_chip->pioregs->iar |= IAR_AUTOCAL_BEGIN;
+  tprintk(("in cs4231_chip_reset\n"));
+
+  cs4231_chip->regs->dmacsr = CS_CHIP_RESET;
+  cs4231_chip->regs->dmacsr = 0x00;
+  cs4231_chip->regs->dmacsr |= CS_CDC_RESET;
   
-  CHIP_BUG
-    
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x0c;
-  cs4231_chip->pioregs->idr = MISC_IR_MODE2;
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x08;
-  cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; /* Ulaw */
+  udelay(20);
   
-  CHIP_BUG
-    
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1c;
-  cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT; /* Ulaw */
+  cs4231_chip->regs->dmacsr &= ~(CS_CDC_RESET);
+  cs4231_chip->regs->iar |= IAR_AUTOCAL_BEGIN;
   
-  CHIP_BUG
+  CHIP_READY
     
-  cs4231_chip->pioregs->iar = 0x19;
-  
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x0c;
+  cs4231_chip->regs->idr = MISC_IR_MODE2;
+
+  /* This is the equivalent of DEFAULT_DATA_FMAT */
+  cs4231_set_input_encoding(drv, AUDIO_ENCODING_ULAW);
+  cs4231_set_input_rate(drv, CS4231_RATE);
+  cs4231_set_input_channels(drv, CS4231_CHANNELS);
+  cs4231_set_input_precision(drv, CS4231_PRECISION);
+
+  cs4231_set_output_encoding(drv, AUDIO_ENCODING_ULAW);
+  cs4231_set_output_rate(drv, CS4231_RATE);
+  cs4231_set_output_channels(drv, CS4231_CHANNELS);
+  cs4231_set_output_precision(drv, CS4231_PRECISION);
+
+  cs4231_chip->regs->iar = 0x19;
   /* see what we can turn on */
-  if (cs4231_chip->pioregs->idr & CS4231A)
+  if (cs4231_chip->regs->idr & CS4231A) {
+    tprintk(("This is a CS4231A\n"));
     cs4231_chip->status |= CS_STATUS_REV_A;
-  else
+  } else
     cs4231_chip->status &= ~CS_STATUS_REV_A;
   
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x10;
-  cs4231_chip->pioregs->idr = OLB_ENABLE;
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x10;
+  cs4231_chip->regs->idr = OLB_ENABLE;
   
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x11;
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x11;
   if (cs4231_chip->status & CS_STATUS_REV_A)
-    cs4231_chip->pioregs->idr = (HPF_ON | XTALE_ON);
+    cs4231_chip->regs->idr = (HPF_ON | XTALE_ON);
   else
-    cs4231_chip->pioregs->idr = (HPF_ON);
+    cs4231_chip->regs->idr = (HPF_ON);
   
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1a;
-  cs4231_chip->pioregs->idr = 0x00;
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x1a;
+  cs4231_chip->regs->idr = 0x00;
   
   /* Now set things up for defaults */
-  cs4231_chip->perchip_info.play.port = cs4231_out_port(drv, AUDIO_SPEAKER);
-  cs4231_chip->perchip_info.record.port = cs4231_in_port(drv, AUDIO_MICROPHONE);
-  cs4231_chip->perchip_info.play.gain = cs4231_play_gain(drv, CS4231_DEFAULT_PLAYGAIN, AUDIO_MID_BALANCE);
-  cs4231_chip->perchip_info.record.gain = cs4231_record_gain(drv, CS4231_DEFAULT_RECGAIN, AUDIO_MID_BALANCE);
-  cs4231_chip->perchip_info.monitor_gain = cs4231_monitor_gain(drv, LOOPB_OFF);
+  cs4231_set_input_balance(drv, AUDIO_MID_BALANCE);
+  cs4231_set_output_balance(drv, AUDIO_MID_BALANCE);
+
+  cs4231_set_input_volume(drv, CS4231_DEFAULT_RECGAIN);
+  cs4231_set_output_volume(drv, CS4231_DEFAULT_PLAYGAIN);
+
+  cs4231_set_input_port(drv, AUDIO_MICROPHONE);
+  cs4231_set_output_port(drv, AUDIO_SPEAKER);
+
+  cs4231_set_monitor_volume(drv, LOOPB_OFF);
   
-  cs4231_chip->pioregs->iar = (u_char)IAR_AUTOCAL_END;
+  cs4231_chip->regs->iar = IAR_AUTOCAL_END;
   
   cs4231_ready(drv);
   
-  cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x09;
-  cs4231_chip->pioregs->idr &= ACAL_DISABLE;
-  cs4231_chip->pioregs->iar = (u_char)IAR_AUTOCAL_END;
+  cs4231_chip->regs->iar = IAR_AUTOCAL_BEGIN | 0x09;
+  cs4231_chip->regs->idr &= ACAL_DISABLE;
+  cs4231_chip->regs->iar = IAR_AUTOCAL_END;
   
   cs4231_ready(drv);
 
   cs4231_output_muted(drv, 0);
+
+  cs4231_chip->recording_count = 0;
+  cs4231_chip->playing_count = 0;
 }
 
-static void cs4231_mute(struct sparcaudio_driver *drv)
+static int 
+cs4231_length_to_samplecount(struct audio_prinfo *thisdir, unsigned int length)
 {
-  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  unsigned int count;
 
-  if (!(cs4231_chip->status & CS_STATUS_REV_A)) {
-    cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN;
-    udelay(100);
-    cs4231_chip->pioregs->iar = IAR_AUTOCAL_END;
-    CHIP_BUG
-  }
+  if (thisdir->channels == 2)
+    count = (length/2);
+  else 
+    count = length;
+  
+  if (thisdir->encoding == AUDIO_ENCODING_LINEAR)
+    count = (count/2);
+  else if (thisdir->encoding == AUDIO_ENCODING_DVI)
+    count = (count/4);
+  
+  return count;
 }
 
-/* Not yet useful */
-#if 0
-static int cs4231_len_to_sample(struct sparcaudio_driver *drv, int length, int direction)
+static void cs4231_getsamplecount(struct sparcaudio_driver *drv, unsigned int length, unsigned int direction)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
-  int sample;
+  struct audio_prinfo *thisdir;
+  unsigned int count, nextcount, curcount;
 
-  if (/* number of channels == 2*/0) {
-    sample = (length/2);
-  } else {
-    sample = length;
-  }
-  if (/*encoding == AUDIO_ENCODING_LINEAR*/0) {
-    sample = sample/2;
-  }
-  return (sample);
+  if (direction == 1) /* record */ 
+    {
+      thisdir = &cs4231_chip->perchip_info.record;
+      curcount = 
+	cs4231_length_to_samplecount(thisdir, cs4231_chip->regs->dmacc);
+      nextcount = 
+	cs4231_length_to_samplecount(thisdir, cs4231_chip->regs->dmacnc);
+    }
+  else /* play */
+    {
+      thisdir = &cs4231_chip->perchip_info.play;
+      curcount = 
+	cs4231_length_to_samplecount(thisdir, cs4231_chip->regs->dmapc);
+      nextcount = 
+	cs4231_length_to_samplecount(thisdir, cs4231_chip->regs->dmapnc);
+    }
+  count = thisdir->samples;
+  length = cs4231_length_to_samplecount(thisdir, length);
+  thisdir->samples = ((count - nextcount) + (length - curcount));
 }
-#endif
 
 static int cs4231_open(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
 {	
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
-  /* Set the default audio parameters. */
-  
-  cs4231_chip->perchip_info.play.sample_rate = CS4231_RATE;
-  cs4231_chip->perchip_info.play.channels = CS4231_CHANNELS;
-  cs4231_chip->perchip_info.play.precision = CS4231_PRECISION;
-  cs4231_chip->perchip_info.play.encoding = AUDIO_ENCODING_ULAW;
-  
-  cs4231_chip->perchip_info.record.sample_rate = CS4231_RATE;
-  cs4231_chip->perchip_info.record.channels = CS4231_CHANNELS;
-  cs4231_chip->perchip_info.record.precision = CS4231_PRECISION;
-  cs4231_chip->perchip_info.record.encoding = AUDIO_ENCODING_ULAW;
-  
+  /* Set the default audio parameters if not already in use. */
+  if (file->f_mode & FMODE_WRITE) {
+    if (!(drv->flags & SDF_OPEN_WRITE) && 
+	(cs4231_chip->perchip_info.play.active == 0)) {
+      cs4231_chip->perchip_info.play.open = 1;
+      cs4231_set_output_port(drv, AUDIO_SPEAKER);
+    }
+  }
+
+  if (file->f_mode & FMODE_READ) {
+    if (!(drv->flags & SDF_OPEN_READ) && 
+	(cs4231_chip->perchip_info.record.active == 0)) {
+      cs4231_chip->perchip_info.record.open = 1;
+      cs4231_set_input_port(drv, AUDIO_MICROPHONE);
+    }
+  }  
+
   cs4231_ready(drv);
   
-  cs4231_chip->status |= CS_STATUS_NEED_INIT;
-  
-  CHIP_BUG
+  CHIP_READY
     
   MOD_INC_USE_COUNT;
   
@@ -422,31 +960,48 @@
 
 static void cs4231_release(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
 {
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
   /* zero out any info about what data we have as well */
-  /* should insert init on close variable optionally calling cs4231_reset() */
+
+  if (file->f_mode & FMODE_READ) 
+      cs4231_chip->perchip_info.record.open = 0;
+
+  if (file->f_mode & FMODE_WRITE) 
+      cs4231_chip->perchip_info.play.open = 0;
+
+  if (!cs4231_chip->perchip_info.play.open && !cs4231_chip->perchip_info.record.open && (cs4231_chip->status & CS_STATUS_INIT_ON_CLOSE)) {
+    cs4231_chip_reset(drv);
+    cs4231_chip->status &= ~CS_STATUS_INIT_ON_CLOSE;
+  }
+
   MOD_DEC_USE_COUNT;
 }
 
-static int cs4231_playintr(struct sparcaudio_driver *drv)
+static void cs4231_playintr(struct sparcaudio_driver *drv)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
-  /* Send the next byte of outgoing data. */
-#if 0
-  if (cs4231_chip->output_ptr && cs4231_chip->output_count > 0) {
-    cs4231_chip->dmaregs.dmapnva = dma_handle;
-    cs4231_chip->dmaregs.dmapnc = length; 
-    cs4231_chip->output_ptr++;
-    cs4231_chip->output_count--;
-    
-    /* Done with the buffer? Notify the midlevel driver. */
-    if (cs4231_chip->output_count == 0) {
-      cs4231_chip->output_ptr = NULL;
-      cs4231_chip->output_count = 0;
-      sparcaudio_output_done(drv);
-    }
+  if (cs4231_chip->playlen == 0)
+    cs4231_chip->playlen = cs4231_chip->output_size;
+
+  if (cs4231_chip->output_ptr && cs4231_chip->output_size > 0) {
+    cs4231_chip->regs->dmapnva = (__u32) cs4231_chip->output_ptr;
+    cs4231_chip->regs->dmapnc = cs4231_chip->output_size;
+    cs4231_chip->output_size = 0;
+    cs4231_chip->output_ptr = NULL;
+    cs4231_chip->playing_count++;
+  }
+
+  /* Get two buffers into the pipe, then chain... */
+  if (cs4231_chip->playing_count < 3) 
+    sparcaudio_output_done(drv, 0);
+  else {
+    cs4231_chip->playing_count--;
+    sparcaudio_output_done(drv, 1);
   }
-#endif
+
+  return; 
 }
 
 static void cs4231_recmute(int fmt)
@@ -468,72 +1023,55 @@
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
-  cs4231_recmute(cs4231_chip->perchip_info.record.encoding);
-
   if (cs4231_chip->perchip_info.record.active == 0) {
     cs4231_pollinput(drv);
-    cs4231_chip->pioregs->iar = 0x9;
-    cs4231_chip->pioregs->idr &= CEN_DISABLE;
+    cs4231_recmute(cs4231_chip->perchip_info.record.encoding);
+    cs4231_disable_rec(drv);    
   }
-  /* Read the next byte of incoming data. */
-#if 0
-  if (cs4231_chip->input_ptr && cs4231_chip->input_count > 0) {
-    cs4231_chip->dmaregs.dmacnva = dma_handle;
-    cs4231_chip->dmaregs.dmacnc = length;
-    cs4231_chip->input_ptr++;
-    cs4231_chip->input_count--;
-    
-    /* Done with the buffer? Notify the midlevel driver. */
-    if (cs4231_chip->input_count == 0) {
-      cs4231_chip->input_ptr = NULL;
-      cs4231_chip->input_count = 0;
-      sparcaudio_input_done(drv);
-    }
+  if (cs4231_chip->input_ptr) {
+    cs4231_chip->regs->dmacnva = (__u32) cs4231_chip->input_ptr;
+    cs4231_chip->regs->dmacnc = cs4231_chip->input_size;
+    cs4231_chip->input_ptr = NULL;
+    cs4231_chip->input_size = 0;
+    sparcaudio_input_done(drv);
   }
-#endif
+  return 1;
 }
 
 static void cs4231_start_output(struct sparcaudio_driver *drv, __u8 * buffer, unsigned long count)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
+  cs4231_chip->output_ptr = buffer;
+  cs4231_chip->output_size = count;
+
   if (cs4231_chip->perchip_info.play.active || (cs4231_chip->perchip_info.play.pause))
     return;
 
   cs4231_ready(drv);
 
-  if (cs4231_chip->status & CS_STATUS_NEED_INIT)
-    {
-      cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x08;
-      cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT;
-      cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1c;
-      cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT;
-
-      CHIP_BUG
-
-      cs4231_chip->status &= ~CS_STATUS_NEED_INIT;
-    }
-
-  if (!cs4231_chip->perchip_info.play.pause) 
-    {
-      /* init dma foo here */
-      cs4231_chip->dmaregs.dmacsr &= ~CS_XINT_PLAY;
-      cs4231_chip->dmaregs.dmacsr &= ~CS_PPAUSE;
-      if (cs4231_playintr(drv)) {
-	cs4231_chip->dmaregs.dmacsr |= CS_PLAY_SETUP;
-	cs4231_chip->pioregs->iar = 0x9;
-	cs4231_chip->pioregs->idr |= PEN_ENABLE;
-      }
-    }
   cs4231_chip->perchip_info.play.active = 1;
+
+  cs4231_chip->playing_count = 0;
+  cs4231_disable_play(drv);
+  cs4231_chip->regs->dmacsr &= ~CS_XINT_PLAY;
+  cs4231_chip->regs->dmacsr &= ~CS_PPAUSE;
+  cs4231_playintr(drv);
+  cs4231_enable_play(drv);
+  cs4231_chip->regs->dmacsr |= CS_PLAY_SETUP;
+  cs4231_output_muted(drv, 0);
+  cs4231_ready(drv);
 }
 
 static void cs4231_stop_output(struct sparcaudio_driver *drv)
 {
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
+  tprintk(("in cs4231_stop_output\n"));
+  cs4231_chip->output_ptr = NULL;
+  cs4231_chip->output_size = 0;
   cs4231_chip->perchip_info.play.active = 0;
-  cs4231_chip->dmaregs.dmacsr |= (CS_PPAUSE);
+  cs4231_chip->regs->dmacsr |= (CS_PPAUSE);
 }
 
 static void cs4231_pollinput(struct sparcaudio_driver *drv)
@@ -541,10 +1079,10 @@
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
   int x = 0;
 
-  while (!(cs4231_chip->dmaregs.dmacsr & CS_XINT_COVF) && x <= CS_TIMEOUT) {
+  while (!(cs4231_chip->regs->dmacsr & CS_XINT_COVF) && x <= CS_TIMEOUT) {
     x++;
   }
-  cs4231_chip->dmaregs.dmacsr |= CS_XINT_CEMP;
+  cs4231_chip->regs->dmacsr |= CS_XINT_CEMP;
 }
 
 static void cs4231_start_input(struct sparcaudio_driver *drv, __u8 * buffer, unsigned long count)
@@ -556,29 +1094,14 @@
 
   cs4231_ready(drv);
 
-  if (cs4231_chip->status & CS_STATUS_NEED_INIT)
-    {
-      cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x08;
-      cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT;
-      cs4231_chip->pioregs->iar = IAR_AUTOCAL_BEGIN | 0x1c;
-      cs4231_chip->pioregs->idr = DEFAULT_DATA_FMAT;
-
-      CHIP_BUG
-
-      cs4231_chip->status &= ~CS_STATUS_NEED_INIT;
-    }
-
-  if (!cs4231_chip->perchip_info.record.pause)
-    {
-      /* init dma foo here */
-      cs4231_chip->dmaregs.dmacsr &= ~CS_XINT_CAPT;
-      cs4231_chip->dmaregs.dmacsr &= ~CS_CPAUSE;
-      cs4231_recintr(drv);
-      cs4231_chip->dmaregs.dmacsr |= CS_CAPT_SETUP;
-      cs4231_chip->pioregs->iar = 0x9;
-      cs4231_chip->pioregs->idr |= CEN_ENABLE;
-    }
   cs4231_chip->perchip_info.record.active = 1;
+  cs4231_chip->recording_count = 0;
+  /* init dma foo here */
+  cs4231_chip->regs->dmacsr &= ~CS_XINT_CAPT;
+  cs4231_chip->regs->dmacsr &= ~CS_CPAUSE;
+  cs4231_recintr(drv);
+  cs4231_chip->regs->dmacsr |= CS_CAPT_SETUP;
+  cs4231_enable_rec(drv);
 }
 
 static void cs4231_stop_input(struct sparcaudio_driver *drv)
@@ -586,43 +1109,166 @@
   struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
 
   cs4231_chip->perchip_info.record.active = 0;
-  cs4231_chip->dmaregs.dmacsr |= (CS_CPAUSE);
+  cs4231_chip->regs->dmacsr |= (CS_CPAUSE);
 
   cs4231_pollinput(drv);
 
   /* need adjust the end pointer, process the input, and clean up the dma */
 
-  cs4231_chip->pioregs->iar = 0x09;
-  cs4231_chip->pioregs->idr &= CEN_DISABLE;
+  cs4231_disable_rec(drv);
 }
 
 static void cs4231_audio_getdev(struct sparcaudio_driver *drv,
                                  audio_device_t * audinfo)
 {
-        strncpy(audinfo->name, "cs4231", sizeof(audinfo->name) - 1);
-        strncpy(audinfo->version, "x", sizeof(audinfo->version) - 1);
-        strncpy(audinfo->config, "audio", sizeof(audinfo->config) - 1);
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+  strncpy(audinfo->name, "SUNW,CS4231", sizeof(audinfo->name) - 1);
+  /* versions: SPARCstation 4/5=a, Ultra=b */
+  if (cs4231_chip->status & CS_STATUS_IS_ULTRA)
+    strncpy(audinfo->version, "b", sizeof(audinfo->version) - 1);
+  else
+    strncpy(audinfo->version, "a", sizeof(audinfo->version) - 1);
+  strncpy(audinfo->config, "onboard1", sizeof(audinfo->config) - 1);
+}
+
+
+static int cs4231_audio_getdev_sunos(struct sparcaudio_driver *drv)
+{
+        return AUDIO_DEV_CS4231;
 }
 
+static void cs4231_loopback(struct sparcaudio_driver *drv, unsigned int value)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+
+  cs4231_chip->regs->iar = 0x0d;
+  cs4231_chip->regs->idr = (value ? LOOPB_ON : 0);
+}
+
+static int cs4231_ioctl(struct inode * inode, struct file * file,
+			unsigned int cmd, unsigned long arg, 
+			struct sparcaudio_driver *drv)
+{
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  int retval = 0;
+  
+  switch (cmd) {
+  case AUDIO_DIAG_LOOPBACK:
+    cs4231_chip->status |= CS_STATUS_INIT_ON_CLOSE;
+    cs4231_loopback(drv, (unsigned int)arg);
+    break;
+  default:
+    retval = -EINVAL;
+  }
+
+  return retval;
+}
 
-/* The ioctl handler should be expected to identify itself and handle loopback
-   mode */
-/* There will also be a handler for getinfo and setinfo */
+
+/* Audio interrupt handler. */
+void cs4231_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+  struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id;
+  struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
+  __u32 dummy;
+  
+  tprintk(("in cs4231_interrupt\n"));
+
+  /* Clear the interrupt. */
+  dummy = cs4231_chip->regs->dmacsr;
+  cs4231_chip->regs->dmacsr = dummy;
+
+  /* now go through and figure out what gets to claim the interrupt 
+   * if anything since we may be doing shared interrupts 
+   */
+
+  if (dummy & CS_PLAY_INT) {
+    if (dummy & CS_XINT_PNVA) {
+      cs4231_chip->perchip_info.play.samples += 
+	cs4231_length_to_samplecount(&(cs4231_chip->perchip_info.play), 
+				     cs4231_chip->playlen);
+      cs4231_playintr(drv);
+    }
+  }
+
+  if (dummy & CS_CAPT_INT) {
+    if (dummy & CS_XINT_CNVA) {
+      cs4231_chip->perchip_info.record.samples += 
+	cs4231_length_to_samplecount(&(cs4231_chip->perchip_info.record), 
+				     cs4231_chip->reclen);
+      cs4231_recintr(drv);
+    }
+  }
+  if ((dummy & CS_XINT_CEMP) 
+      && (cs4231_chip->perchip_info.record.active == 0)) 
+    {
+      cs4231_chip->perchip_info.record.active = 0;
+    }
+  if ((dummy & CS_XINT_EMPT) && (cs4231_chip->perchip_info.play.active == 0)) {
+    cs4231_chip->regs->dmacsr |= (CS_PPAUSE);
+    cs4231_disable_play(drv);
+    cs4231_output_muted(drv, 1);
+    
+    cs4231_getsamplecount(drv, cs4231_chip->playlen, 0);
+  }
+
+  if (dummy & CS_GENL_INT) {
+    /* If we get here we must be sharing an interrupt, but I haven't code 
+       to handle this right now */
+  }
+
+}
 
 static struct sparcaudio_operations cs4231_ops = {
 	cs4231_open,
 	cs4231_release,
-	NULL,			/* cs4231_ioctl */
+	cs4231_ioctl,
 	cs4231_start_output,
 	cs4231_stop_output,
 	cs4231_start_input,
         cs4231_stop_input,
 	cs4231_audio_getdev,
+        cs4231_set_output_volume,
+        cs4231_get_output_volume,
+        cs4231_set_input_volume,
+        cs4231_get_input_volume,
+        cs4231_set_monitor_volume,
+        cs4231_get_monitor_volume,
+	cs4231_set_output_balance,
+	cs4231_get_output_balance,
+        cs4231_set_input_balance,
+        cs4231_get_input_balance,
+        cs4231_set_output_channels,
+        cs4231_get_output_channels,
+        cs4231_set_input_channels,
+        cs4231_get_input_channels,
+        cs4231_set_output_precision,
+        cs4231_get_output_precision,
+        cs4231_set_input_precision,
+        cs4231_get_input_precision,
+        cs4231_set_output_port,
+        cs4231_get_output_port,
+        cs4231_set_input_port,
+        cs4231_get_input_port,
+        cs4231_set_output_encoding,
+        cs4231_get_output_encoding,
+        cs4231_set_input_encoding,
+        cs4231_get_input_encoding,
+        cs4231_set_output_rate,
+        cs4231_get_output_rate,
+        cs4231_set_input_rate,
+	cs4231_get_input_rate,
+	cs4231_audio_getdev_sunos,
+	cs4231_get_output_ports,
+	cs4231_get_input_ports,
+	cs4231_output_muted,
+	cs4231_get_output_muted,
 };
 
 /* Attach to an cs4231 chip given its PROM node. */
-static inline int
-cs4231_attach(struct sparcaudio_driver *drv, struct linux_sbus_device *sdev)
+static int cs4231_attach(struct sparcaudio_driver *drv, 
+			 struct linux_sbus_device *sdev)
 {
   struct cs4231_chip *cs4231_chip;
   int err;
@@ -639,57 +1285,76 @@
   /* Point at the information structure and initialize it. */
   drv->ops = &cs4231_ops;
   cs4231_chip = (struct cs4231_chip *)drv->private;
-#if 0
-  cs4231_chip->input_ptr = NULL;
-  cs4231_chip->input_count = 0;
-  cs4231_chip->output_ptr = NULL;
-  cs4231_chip->output_count = 0;
-#endif
+  cs4231_chip->input_ptr = cs4231_chip->output_ptr = NULL;
+  cs4231_chip->input_size = cs4231_chip->output_size = 0;
+  cs4231_chip->status = 0;
 
   /* Map the registers into memory. */
   prom_apply_sbus_ranges(sbus, sdev->reg_addrs, 1, sdev);
   cs4231_chip->regs_size = sdev->reg_addrs[0].reg_size;
-  cs4231_chip->pioregs = sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0, 
-  					sdev->reg_addrs[0].reg_size,
-				      "cs4231", sdev->reg_addrs[0].which_io, 0);
-  if (!cs4231_chip->pioregs) {
+  cs4231_chip->regs = sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0,
+				     sdev->reg_addrs[0].reg_size, 
+				     "cs4231", sdev->reg_addrs[0].which_io, 
+				     0);
+
+  if (!cs4231_chip->regs) {
     printk(KERN_ERR "cs4231: could not allocate registers\n");
     kfree(drv->private);
     return -EIO;
   }
 
-  /* Reset the audio chip. */
-  cs4231_reset(drv);
-
   /* Attach the interrupt handler to the audio interrupt. */
   cs4231_chip->irq = sdev->irqs[0].pri;
 
 #ifndef __sparc_v9__
   request_irq(cs4231_chip->irq, cs4231_interrupt, SA_SHIRQ, "cs4231", drv);
 #else
-  dcookie.real_dev_id = s;
+  dcookie.real_dev_id = drv;
   dcookie.imap = dcookie.iclr = 0;
   dcookie.pil = -1;
   dcookie.bus_cookie = sdev->my_bus;
-  request_irq (cs4231_chip->irq, cs4231_interrupt, (SA_SHIRQ | SA_SBUS | SA_DCOOKIE), "cs4231", drv);
+  request_irq (cs4231_chip->irq, cs4231_interrupt, (SA_SHIRQ | SA_SBUS | SA_DCOOKIE), "cs4231", &dcookie);
   cs4231_chip->irq = dcookie.ret_ino;
 #endif
   enable_irq(cs4231_chip->irq);
 
+  cs4231_enable_interrupts(drv);
+
+  /* Reset the audio chip. */
+  cs4231_chip_reset(drv);
+
   /* Register ourselves with the midlevel audio driver. */
   err = register_sparcaudio_driver(drv);
+
   if (err < 0) {
     printk(KERN_ERR "cs4231: unable to register\n");
+    cs4231_disable_interrupts(drv);
     disable_irq(cs4231_chip->irq);
     free_irq(cs4231_chip->irq, drv);
-    sparc_free_io(cs4231_chip->pioregs, cs4231_chip->regs_size);
+    sparc_free_io(cs4231_chip->regs, cs4231_chip->regs_size);
     kfree(drv->private);
     return -EIO;
   }
 
+  cs4231_chip->perchip_info.play.active = 
+    cs4231_chip->perchip_info.play.pause = 0;
+
+  cs4231_chip->perchip_info.record.active = 
+    cs4231_chip->perchip_info.record.pause = 0;
+
+  cs4231_chip->perchip_info.play.avail_ports = (AUDIO_HEADPHONE |
+						AUDIO_SPEAKER |
+						AUDIO_LINE_OUT);
+
+  cs4231_chip->perchip_info.record.avail_ports = (AUDIO_INTERNAL_CD_IN |
+						  AUDIO_LINE_IN | 
+						  AUDIO_MICROPHONE |
+						  AUDIO_ANALOG_LOOPBACK);
+
   /* Announce the hardware to the user. */
-  printk(KERN_INFO "cs4231 at 0x%lx irq %d\n",
-	 (unsigned long)cs4231_chip->pioregs, cs4231_chip->irq);
+  printk(KERN_INFO "cs4231%c at 0x%lx irq %d\n",
+	 (cs4231_chip->status & CS_STATUS_REV_A) ? 'a' : ' ', 
+	 (unsigned long)cs4231_chip->regs, cs4231_chip->irq);
   
   /* Success! */
   return 0;
@@ -705,6 +1370,8 @@
   struct linux_sbus *bus;
   struct linux_sbus_device *sdev;
   
+  num_drivers = 0;
+  
   /* Probe each SBUS for cs4231 chips. */
   for_all_sbusdev(sdev,bus) {
     if (!strcmp(sdev->prom_name, "SUNW,CS4231")) {
@@ -727,10 +1394,11 @@
 {
         struct cs4231_chip *info = (struct cs4231_chip *)drv->private;
 
+	cs4231_disable_interrupts(drv);
         unregister_sparcaudio_driver(drv);
         disable_irq(info->irq);
         free_irq(info->irq, drv);
-        sparc_free_io(info->pioregs, info->regs_size);
+        sparc_free_io(info->regs, info->regs_size);
         kfree(drv->private);
 }
 
@@ -745,3 +1413,21 @@
 }
 #endif
 
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */

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