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

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

diff -u --recursive --new-file v2.1.128/linux/drivers/sbus/audio/amd7930.c linux/drivers/sbus/audio/amd7930.c
@@ -13,6 +13,74 @@
  *
  * Thanks to the AMD engineer who was able to get us the AMD79C30
  * databook which has all the programming information and gain tables.
+ *
+ * Advanced Micro Devices' Am79C30A is an ISDN/audio chip used in the
+ * SparcStation 1+.  The chip provides microphone and speaker interfaces
+ * which provide mono-channel audio at 8K samples per second via either
+ * 8-bit A-law or 8-bit mu-law encoding.  Also, the chip features an
+ * ISDN BRI Line Interface Unit (LIU), I.430 S/T physical interface,
+ * which performs basic D channel LAPD processing and provides raw
+ * B channel data.  The digital audio channel, the two ISDN B channels,
+ * and two 64 Kbps channels to the microprocessor are all interconnected
+ * via a multiplexer.
+ *
+ * This driver interfaces to the Linux HiSax ISDN driver, which performs
+ * all high-level Q.921 and Q.931 ISDN functions.  The file is not
+ * itself a hardware driver; rather it uses functions exported by
+ * the AMD7930 driver in the sparcaudio subsystem (drivers/sbus/audio),
+ * allowing the chip to be simultaneously used for both audio and ISDN data.
+ * The hardware driver does _no_ buffering, but provides several callbacks
+ * which are called during interrupt service and should therefore run quickly.
+ *
+ * D channel transmission is performed by passing the hardware driver the
+ * address and size of an skb's data area, then waiting for a callback
+ * to signal successful transmission of the packet.  A task is then
+ * queued to notify the HiSax driver that another packet may be transmitted.
+ *
+ * D channel reception is quite simple, mainly because of:
+ *   1) the slow speed of the D channel - 16 kbps, and
+ *   2) the presence of an 8- or 32-byte (depending on chip version) FIFO
+ *      to buffer the D channel data on the chip
+ * Worst case scenario of back-to-back packets with the 8 byte buffer
+ * at 16 kbps yields an service time of 4 ms - long enough to preclude
+ * the need for fancy buffering.  We queue a background task that copies
+ * data out of the receive buffer into an skb, and the hardware driver
+ * simply does nothing until we're done with the receive buffer and
+ * reset it for a new packet.
+ *
+ * B channel processing is more complex, because of:
+ *   1) the faster speed - 64 kbps,
+ *   2) the lack of any on-chip buffering (it interrupts for every byte), and
+ *   3) the lack of any chip support for HDLC encapsulation
+ *
+ * The HiSax driver can put each B channel into one of three modes -
+ * L1_MODE_NULL (channel disabled), L1_MODE_TRANS (transparent data relay),
+ * and L1_MODE_HDLC (HDLC encapsulation by low-level driver).
+ * L1_MODE_HDLC is the most common, used for almost all "pure" digital
+ * data sessions.  L1_MODE_TRANS is used for ISDN audio.
+ *
+ * HDLC B channel transmission is performed via a large buffer into
+ * which the skb is copied while performing HDLC bit-stuffing.  A CRC
+ * is computed and attached to the end of the buffer, which is then
+ * passed to the low-level routines for raw transmission.  Once
+ * transmission is complete, the hardware driver is set to enter HDLC
+ * idle by successive transmission of mark (all 1) bytes, waiting for
+ * the ISDN driver to prepare another packet for transmission and
+ * deliver it.
+ *
+ * HDLC B channel reception is performed via an X-byte ring buffer
+ * divided into N sections of X/N bytes each.  Defaults: X=256 bytes, N=4.
+ * As the hardware driver notifies us that each section is full, we
+ * hand it the next section and schedule a background task to peruse
+ * the received section, bit-by-bit, with an HDLC decoder.  As
+ * packets are detected, they are copied into a large buffer while
+ * decoding HDLC bit-stuffing.  The ending CRC is verified, and if
+ * it is correct, we alloc a new skb of the correct length (which we
+ * now know), copy the packet into it, and hand it to the upper layers.
+ * Optimization: for large packets, we hand the buffer (which also
+ * happens to be an skb) directly to the upper layer after an skb_trim,
+ * and alloc a new large buffer for future packets, thus avoiding a copy.
+ * Then we return to HDLC processing; state is saved between calls.
  */
 
 #include <linux/module.h>
@@ -22,6 +90,8 @@
 #include <linux/interrupt.h>
 #include <linux/malloc.h>
 #include <linux/init.h>
+#include <linux/version.h>
+#include <linux/soundcard.h>
 #include <asm/openprom.h>
 #include <asm/oplib.h>
 #include <asm/system.h>
@@ -32,6 +102,12 @@
 #include <asm/audioio.h>
 #include "amd7930.h"
 
+#if defined (AMD79C30_ISDN) || defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff 
+#include "../../isdn/hisax/hisax.h"
+#include "../../isdn/hisax/isdnl1.h"
+#include "../../isdn/hisax/foreign.h"
+#endif
+
 #define MAX_DRIVERS 1
 
 static struct sparcaudio_driver drivers[MAX_DRIVERS];
@@ -274,12 +350,6 @@
 		map->gr = gx_coeff[level];
 	}
 
-	/* force output to speaker for now */
-	map->mmr2 |= AM_MAP_MMR2_LS;
-
-	/* input from external microphone */
-	map->mmr2 |= AM_MAP_MMR2_AINB;
-
 	amd7930_write_map(drv);
 }
 
@@ -296,9 +366,6 @@
  * driver, since D.output_callback_arg is assumed to be a certain struct ptr
  */
 
-#include "../../isdn/hisax/hisax.h"
-#include "../../isdn/hisax/isdnl1.h"
-
 #ifdef L2FRAME_DEBUG
 
 inline void debug_info(struct amd7930_info *info, char c) {
@@ -453,7 +520,7 @@
 			channel->input_count = 0;
 			if (channel->input_callback)
 				(*channel->input_callback)
-					(channel->input_callback_arg);
+					(channel->input_callback_arg, 1);
 		}
 	}
 }
@@ -498,16 +565,7 @@
 static int amd7930_open(struct inode * inode, struct file * file,
 			struct sparcaudio_driver *drv)
 {
-	struct amd7930_info *info = (struct amd7930_info *)drv->private;
-
-	/* Set the default audio parameters. */
-	info->rgain = 128;
-	info->pgain = 200;
-	info->mgain = 0;
-	amd7930_update_map(drv);
-
 	MOD_INC_USE_COUNT;
-
 	return 0;
 }
 
@@ -640,6 +698,21 @@
 	return AUDIO_DEV_AMD;
 }
 
+static int amd7930_get_formats(struct sparcaudio_driver *drv)
+{
+      return (AFMT_MU_LAW | AFMT_A_LAW);
+}
+
+static int amd7930_get_output_ports(struct sparcaudio_driver *drv)
+{
+      return (AUDIO_SPEAKER | AUDIO_HEADPHONE);
+}
+
+static int amd7930_get_input_ports(struct sparcaudio_driver *drv)
+{
+      return (AUDIO_MICROPHONE);
+}
+
 static int amd7930_set_output_volume(struct sparcaudio_driver *drv, int vol)
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
@@ -681,6 +754,13 @@
 	return 0;
 }
 
+static int amd7930_get_monitor_volume(struct sparcaudio_driver *drv)
+{
+      struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+      return info->mgain;
+}
+
 /* Cheats. The amd has the minimum capabilities we support */
 static int amd7930_get_output_balance(struct sparcaudio_driver *drv)
 {
@@ -697,25 +777,68 @@
 	return AUDIO_MIN_PLAY_CHANNELS;
 }
 
+static int amd7930_set_output_channels(struct sparcaudio_driver *drv, 
+				       int value)
+{
+  return (value == AUDIO_MIN_PLAY_CHANNELS) ? 0 : -EINVAL;
+}
+
 static int amd7930_get_input_channels(struct sparcaudio_driver *drv)
 {
 	return AUDIO_MIN_REC_CHANNELS;
 }
 
+static int 
+amd7930_set_input_channels(struct sparcaudio_driver *drv, int value)
+{
+  return (value == AUDIO_MIN_REC_CHANNELS) ? 0 : -EINVAL;
+}
+
 static int amd7930_get_output_precision(struct sparcaudio_driver *drv)
 {
 	return AUDIO_MIN_PLAY_PRECISION;
 }
 
+static int 
+amd7930_set_output_precision(struct sparcaudio_driver *drv, int value)
+{
+  return (value == AUDIO_MIN_PLAY_PRECISION) ? 0 : -EINVAL;
+}
+
 static int amd7930_get_input_precision(struct sparcaudio_driver *drv)
 {
 	return AUDIO_MIN_REC_PRECISION;
 }
 
-/* This should eventually be made to DTRT, whatever that ends up */
+static int 
+amd7930_set_input_precision(struct sparcaudio_driver *drv, int value)
+{
+  return (value == AUDIO_MIN_REC_PRECISION) ? 0 : -EINVAL;
+}
+
 static int amd7930_get_output_port(struct sparcaudio_driver *drv)
 {
-	return AUDIO_SPEAKER; /* some of these have only HEADPHONE */
+  struct amd7930_info *info = (struct amd7930_info *)drv->private;
+  if (info->map.mmr2 & AM_MAP_MMR2_LS)
+    return AUDIO_SPEAKER; 
+  return AUDIO_HEADPHONE;
+}
+
+static int amd7930_set_output_port(struct sparcaudio_driver *drv, int value)
+{
+  struct amd7930_info *info = (struct amd7930_info *)drv->private;
+  switch (value) {
+  case AUDIO_HEADPHONE:
+    info->map.mmr2 &= ~AM_MAP_MMR2_LS;
+    break;
+  case AUDIO_SPEAKER:
+    info->map.mmr2 |= AM_MAP_MMR2_LS;
+    break;
+  default:
+    return -EINVAL;
+  }
+  amd7930_update_map(drv);
+  return 0;
 }
 
 /* Only a microphone here, so no troubles */
@@ -724,15 +847,31 @@
 	return AUDIO_MICROPHONE;
 }
 
-/* This chip also supports AUDIO_ENCODING_ALAW, add support later */
-static int amd7930_get_output_encoding(struct sparcaudio_driver *drv)
-{
-	return AUDIO_ENCODING_ULAW;
-}
-
-static int amd7930_get_input_encoding(struct sparcaudio_driver *drv)
+static int amd7930_get_encoding(struct sparcaudio_driver *drv)
 {
-	return AUDIO_ENCODING_ULAW;
+  struct amd7930_info *info = (struct amd7930_info *)drv->private;
+  if (info->map.mmr1 & AM_MAP_MMR1_ALAW)
+    return AUDIO_ENCODING_ALAW;
+  return AUDIO_ENCODING_ULAW;
+}
+
+static int 
+amd7930_set_encoding(struct sparcaudio_driver *drv, int value)
+{
+  struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+  switch (value) {
+  case AUDIO_ENCODING_ULAW:
+    info->map.mmr1 &= ~AM_MAP_MMR1_ALAW;
+    break;
+  case AUDIO_ENCODING_ALAW:
+    info->map.mmr1 |= AM_MAP_MMR1_ALAW;
+    break;
+  default:
+    return -EINVAL;
+  }
+  amd7930_update_map(drv);
+  return 0;
 }
 
 /* This is what you get. Take it or leave it */
@@ -741,31 +880,55 @@
 	return AMD7930_RATE;
 }
 
-static int amd7930_get_input_rate(struct sparcaudio_driver *drv)
+static int 
+amd7930_set_output_rate(struct sparcaudio_driver *drv, int value)
 {
-	return AMD7930_RATE;
+  return (value == AMD7930_RATE) ? 0 : -EINVAL;
 }
 
-static int amd7930_get_output_muted(struct sparcaudio_driver *drv)
+static int amd7930_get_input_rate(struct sparcaudio_driver *drv)
 {
-      return 0;
+	return AMD7930_RATE;
 }
 
-static int amd7930_get_output_ports(struct sparcaudio_driver *drv)
+static int
+amd7930_set_input_rate(struct sparcaudio_driver *drv, int value)
 {
-      return AUDIO_SPEAKER | AUDIO_HEADPHONE;
+  return (value == AMD7930_RATE) ? 0 : -EINVAL;
 }
 
-static int amd7930_get_input_ports(struct sparcaudio_driver *drv)
+static int amd7930_get_output_muted(struct sparcaudio_driver *drv)
 {
-      return AUDIO_MICROPHONE;
+      return 0;
 }
 
-static int amd7930_get_monitor_volume(struct sparcaudio_driver *drv)
+static void amd7930_loopback(struct sparcaudio_driver *drv, unsigned int value)
 {
-	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+  struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+  if (value)
+    info->map.mmr1 |= AM_MAP_MMR1_LOOPBACK;
+  else
+    info->map.mmr1 &= ~AM_MAP_MMR1_LOOPBACK;
+  amd7930_update_map(drv);
+  return;
+}
+
+static int amd7930_ioctl(struct inode * inode, struct file * file,
+                         unsigned int cmd, unsigned long arg, 
+                         struct sparcaudio_driver *drv)
+{
+  int retval = 0;
+  
+  switch (cmd) {
+  case AUDIO_DIAG_LOOPBACK:
+    amd7930_loopback(drv, (unsigned int)arg);
+    break;
+  default:
+    retval = -EINVAL;
+  }
 
-	return info->mgain;
+  return retval;
 }
 
 
@@ -909,8 +1072,8 @@
  *
  */
 
-
-int amd7930_get_irqnum(int dev)
+#if defined (AMD79C30_ISDN) || defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE > 0x200ff 
+static int amd7930_get_irqnum(int dev)
 {
 	struct amd7930_info *info;
 
@@ -923,7 +1086,7 @@
 	return info->irq;
 }
 
-int amd7930_get_liu_state(int dev)
+static int amd7930_get_liu_state(int dev)
 {
 	struct amd7930_info *info;
 
@@ -936,7 +1099,7 @@
 	return info->liu_state;
 }
 
-void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg)
+static void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -971,7 +1134,7 @@
 	restore_flags(flags);
 }
 
-void amd7930_liu_activate(int dev, int priority)
+static void amd7930_liu_activate(int dev, int priority)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -1003,7 +1166,7 @@
 	restore_flags(flags);
 }
 
-void amd7930_liu_deactivate(int dev)
+static void amd7930_liu_deactivate(int dev)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -1024,8 +1187,8 @@
 	restore_flags(flags);
 }
 
-void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count,
-		   void (*callback)(void *, int), void *callback_arg)
+static void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count,
+			  void (*callback)(void *, int), void *callback_arg)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -1069,9 +1232,9 @@
 	restore_flags(flags);
 }
 
-void amd7930_drecv(int dev, __u8 *buffer, unsigned int size,
-		   void (*callback)(void *, int, unsigned int),
-		   void *callback_arg)
+static void amd7930_drecv(int dev, __u8 *buffer, unsigned int size,
+			  void (*callback)(void *, int, unsigned int),
+			  void *callback_arg)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -1115,7 +1278,8 @@
 	restore_flags(flags);
 }
 
-int amd7930_bopen(int dev, int chan, u_char xmit_idle_char)
+static int amd7930_bopen(int dev, unsigned int chan, 
+                         int mode, u_char xmit_idle_char)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -1124,6 +1288,10 @@
 		return -1;
 	}
 
+         if (mode == L1_MODE_HDLC) {
+                 return -1;
+         }
+ 
 	info = (struct amd7930_info *) drivers[dev].private;
 
 	save_and_cli(flags);
@@ -1167,7 +1335,7 @@
 	return 0;
 }
 
-void amd7930_bclose(int dev, int chan)
+static void amd7930_bclose(int dev, unsigned int chan)
 {
 	struct amd7930_info *info;
 	register unsigned long flags;
@@ -1202,8 +1370,9 @@
 	restore_flags(flags);
 }
 
-void amd7930_bxmit(int dev, int chan, __u8 * buffer, unsigned long count,
-		   void (*callback)(void *), void *callback_arg)
+static void amd7930_bxmit(int dev, unsigned int chan,
+                          __u8 * buffer, unsigned long count,
+			  void (*callback)(void *, int), void *callback_arg)
 {
 	struct amd7930_info *info;
 	struct amd7930_channel *Bchan;
@@ -1228,8 +1397,10 @@
 	}
 }
 
-void amd7930_brecv(int dev, int chan, __u8 * buffer, unsigned long size,
-		   void (*callback)(void *), void *callback_arg)
+static void amd7930_brecv(int dev, unsigned int chan, 
+                          __u8 * buffer, unsigned long size,
+                          void (*callback)(void *, int, unsigned int),
+                          void *callback_arg)
 {
 	struct amd7930_info *info;
 	struct amd7930_channel *Bchan;
@@ -1254,17 +1425,21 @@
 	}
 }
 
-EXPORT_SYMBOL(amd7930_get_irqnum);
-EXPORT_SYMBOL(amd7930_get_liu_state);
-EXPORT_SYMBOL(amd7930_liu_init);
-EXPORT_SYMBOL(amd7930_liu_activate);
-EXPORT_SYMBOL(amd7930_liu_deactivate);
-EXPORT_SYMBOL(amd7930_dxmit);
-EXPORT_SYMBOL(amd7930_drecv);
-EXPORT_SYMBOL(amd7930_bopen);
-EXPORT_SYMBOL(amd7930_bclose);
-EXPORT_SYMBOL(amd7930_bxmit);
-EXPORT_SYMBOL(amd7930_brecv);
+struct foreign_interface amd7930_foreign_interface = {
+        amd7930_get_irqnum,
+        amd7930_get_liu_state,
+        amd7930_liu_init,
+        amd7930_liu_activate,
+        amd7930_liu_deactivate,
+        amd7930_dxmit,
+        amd7930_drecv,
+        amd7930_bopen,
+        amd7930_bclose,
+        amd7930_bxmit,
+        amd7930_brecv
+};
+EXPORT_SYMBOL(amd7930_foreign_interface);
+#endif
 
 
 /*
@@ -1274,7 +1449,7 @@
 static struct sparcaudio_operations amd7930_ops = {
 	amd7930_open,
 	amd7930_release,
-	NULL,				/* amd7930_ioctl */
+	amd7930_ioctl,
 	amd7930_start_output,
 	amd7930_stop_output,
 	amd7930_start_input,
@@ -1290,31 +1465,44 @@
 	amd7930_get_output_balance,
 	NULL,			/* amd7930_set_input_balance */
 	amd7930_get_input_balance,
-	NULL,			/* amd7930_set_output_channels */
+	amd7930_set_output_channels,
 	amd7930_get_output_channels,
-	NULL,			/* amd7930_set_input_channels */
+	amd7930_set_input_channels,
 	amd7930_get_input_channels,
-	NULL,			/* amd7930_set_output_precision */
+	amd7930_set_output_precision,
 	amd7930_get_output_precision,
-	NULL,			/* amd7930_set_input_precision */
+	amd7930_set_input_precision,
 	amd7930_get_input_precision,
-	NULL,			/* amd7930_set_output_port */
+	amd7930_set_output_port,
 	amd7930_get_output_port,
 	NULL,			/* amd7930_set_input_port */
 	amd7930_get_input_port,
-	NULL,			/* amd7930_set_output_encoding */
-	amd7930_get_output_encoding,
-	NULL,			/* amd7930_set_input_encoding */
-	amd7930_get_input_encoding,
-	NULL,			/* amd7930_set_output_rate */
+	amd7930_set_encoding,
+	amd7930_get_encoding,
+	amd7930_set_encoding,
+	amd7930_get_encoding,
+	amd7930_set_output_rate,
 	amd7930_get_output_rate,
-	NULL,			/* amd7930_set_input_rate */
+	amd7930_set_input_rate,
 	amd7930_get_input_rate,
 	amd7930_sunaudio_getdev_sunos,
 	amd7930_get_output_ports,
 	amd7930_get_input_ports,
-	NULL,			/* amd7930_set_output_muted */
+	NULL,                    /* amd7930_set_output_muted */
 	amd7930_get_output_muted,
+        NULL,                   /* amd7930_set_output_pause */
+        NULL,                   /* amd7930_get_output_pause */
+        NULL,                   /* amd7930_set_input_pause */
+        NULL,                   /* amd7930_get_input_pause */
+        NULL,                   /* amd7930_set_output_samples */
+        NULL,                   /* amd7930_get_output_samples */
+        NULL,                   /* amd7930_set_input_samples */
+        NULL,                   /* amd7930_get_input_samples */
+        NULL,                   /* amd7930_set_output_error */
+        NULL,                   /* amd7930_get_output_error */
+        NULL,                   /* amd7930_set_input_error */
+        NULL,                   /* amd7930_get_input_error */
+        amd7930_get_formats,
 };
 
 /* Attach to an amd7930 chip given its PROM node. */
@@ -1373,9 +1561,17 @@
 	memset(&info->map, 0, sizeof(info->map));
 	info->map.mmr1 = AM_MAP_MMR1_GX | AM_MAP_MMR1_GER
 			 | AM_MAP_MMR1_GR | AM_MAP_MMR1_STG;
+        /* Start out with speaker, microphone */
+        info->map.mmr2 |= (AM_MAP_MMR2_LS | AM_MAP_MMR2_AINB);
+
+	/* Set the default audio parameters. */
+	info->rgain = 128;
+	info->pgain = 200;
+	info->mgain = 0;
+	amd7930_update_map(drv);
 
 	/* Register the amd7930 with the midlevel audio driver. */
-	err = register_sparcaudio_driver(drv);
+	err = register_sparcaudio_driver(drv, 1);
 	if (err < 0) {
 		printk(KERN_ERR "amd7930: unable to register\n");
 		disable_irq(info->irq);
@@ -1399,7 +1595,7 @@
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
-	unregister_sparcaudio_driver(drv);
+	unregister_sparcaudio_driver(drv, 1);
 	amd7930_idle(info);
 	disable_irq(info->irq);
 	free_irq(info->irq, drv);

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