patch-2.1.79 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.78/linux/drivers/sbus/audio/amd7930.c linux/drivers/sbus/audio/amd7930.c
@@ -1,12 +1,18 @@
 /*
  * drivers/sbus/audio/amd7930.c
  *
- * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
+ * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu)
  *
  * This is the lowlevel driver for the AMD7930 audio chip found on all
  * sun4c machines and some sun4m machines.
  *
- * XXX Add note about the fun of getting the docs.
+ * The amd7930 is actually an ISDN chip which has a very simple
+ * integrated audio encoder/decoder. When Sun decided on what chip to
+ * use for audio, they had the brilliant idea of using the amd7930 and
+ * only connecting the audio encoder/decoder pins.
+ *
+ * Thanks to the AMD engineer who was able to get us the AMD79C30
+ * databook which has all the programming information and gain tables.
  */
 
 #include <linux/config.h>
@@ -29,26 +35,78 @@
 
 #define MAX_DRIVERS 1
 
-/* Private information we store for each amd7930 chip. */
-struct amd7930_info {
-	/* Current buffer that the driver is playing. */
+static struct sparcaudio_driver drivers[MAX_DRIVERS];
+static int num_drivers;
+
+/* Each amd7930 chip has two bi-directional B channels and a D
+ * channel available to the uproc.  This structure handles all
+ * the buffering needed to transmit and receive via a single channel.
+ */
+
+#define CHANNEL_AVAILABLE	0x00
+#define CHANNEL_INUSE_AUDIO_IN	0x01
+#define CHANNEL_INUSE_AUDIO_OUT	0x02
+#define CHANNEL_INUSE_ISDN_B1	0x04
+#define CHANNEL_INUSE_ISDN_B2	0x08
+#define CHANNEL_INUSE		0xff
+
+struct amd7930_channel {
+	/* Channel status */
+	unsigned char channel_status;
+
+	/* Current buffer that the driver is playing on channel */
 	volatile __u8 * output_ptr;
 	volatile unsigned long output_count;
+	unsigned char xmit_idle_char;
 
-	/* Current record buffer. */
+	/* Callback routine (and argument) when output is done on */
+	void (*output_callback)();
+	void * output_callback_arg;
+
+	/* Current buffer that the driver is recording on channel */
 	volatile __u8 * input_ptr;
 	volatile unsigned long input_count;
+	volatile unsigned long input_limit;
+
+	/* Callback routine (and argument) when input is done on */
+	void (*input_callback)();
+	void * input_callback_arg;
+};
+
+/* Private information we store for each amd7930 chip. */
+struct amd7930_info {
+	struct amd7930_channel D;
+	struct amd7930_channel Bb;
+	struct amd7930_channel Bc;
+
+	/* Pointers to which B channels are being used for what
+	 * These three fields (Baudio, Bisdn[0], and Bisdn[1]) will either
+	 * be NULL or point to one of the Bb/Bc structures above.
+	 */
+	struct amd7930_channel *Baudio;
+	struct amd7930_channel *Bisdn[2];
 
 	/* Device registers information. */
 	struct amd7930 *regs;
 	unsigned long regs_size;
 	struct amd7930_map map;
 
+	/* Volume information. */
+	int pgain, rgain, mgain;
+
 	/* Device interrupt information. */
 	int irq;
 	volatile int ints_on;
+
+
+	/* Someone to signal when the ISDN LIU state changes */
+	int liu_state;
+	void (*liu_callback)(void *);
+	void *liu_callback_arg;
 };
 
+
+
 /* Output a 16-bit quantity in the order that the amd7930 expects. */
 #define amd7930_out16(regs,v) ({ regs->dr = v & 0xFF; regs->dr = (v >> 8) & 0xFF; })
 
@@ -120,9 +178,8 @@
 #define NR_GER_COEFFS (sizeof(ger_coeff) / sizeof(ger_coeff[0]))
 
 /* Enable amd7930 interrupts atomically. */
-static __inline__ void amd7930_enable_ints(struct sparcaudio_driver *drv)
+static __inline__ void amd7930_enable_ints(struct amd7930_info *info)
 {
-	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 	register unsigned long flags;
 
 	if (info->ints_on)
@@ -137,9 +194,8 @@
 }
 
 /* Disable amd7930 interrupts atomically. */
-static __inline__ void amd7930_disable_ints(struct sparcaudio_driver *drv)
+static __inline__ void amd7930_disable_ints(struct amd7930_info *info)
 {
-	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 	register unsigned long flags;
 
 	if (!info->ints_on)
@@ -153,8 +209,24 @@
 	info->ints_on = 0;
 }  
 
+/* Idle amd7930 (no interrupts, no audio, no data) */
+static __inline__ void amd7930_idle(struct amd7930_info *info)
+{
+	register unsigned long flags;
+
+	if (!info->ints_on)
+		return;
+
+	save_and_cli(flags);
+	info->regs->cr = AMR_INIT;
+	info->regs->dr = 0;
+	restore_flags(flags);
+
+	info->ints_on = 0;
+}  
+
 /* Commit the local copy of the MAP registers to the amd7930. */
-static void amd7930_commit_map(struct sparcaudio_driver *drv)
+static void amd7930_write_map(struct sparcaudio_driver *drv)
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 	struct amd7930      *regs = info->regs;
@@ -184,71 +256,257 @@
 	restore_flags(flags);
 }
 
-/* Interrupt handler (The chip takes only one byte per interrupt. Grrr!) */
-static void amd7930_interrupt(int irq, void *dev_id, struct pt_regs *intr_regs)
+/* Update the MAP registers with new settings. */
+static void amd7930_update_map(struct sparcaudio_driver *drv)
 {
-	struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id;
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
-	struct amd7930 *regs = info->regs;
+	struct amd7930_map  *map  = &info->map;
+	int level;
+
+	map->gx = gx_coeff[info->rgain];
+	map->stgr = gx_coeff[info->mgain];
+
+	level = (info->pgain * (256 + NR_GER_COEFFS)) >> 8;
+	if (level >= 256) {
+		map->ger = ger_coeff[level - 256];
+		map->gr = gx_coeff[255];
+	} else {
+		map->ger = ger_coeff[0];
+		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);
+}
+
+/* Bit of a hack here - if the HISAX ISDN driver has got INTSTAT debugging
+ * turned on, we send debugging characters to the ISDN driver:
+ *
+ *   i# - Interrupt received - number from 0 to 7 is low three bits of IR
+ *   >  - Loaded a single char into the Dchan xmit FIFO
+ *   +  - Finished loading an xmit packet into the Dchan xmit FIFO
+ *   <  - Read a single char from the Dchan recv FIFO
+ *   !  - Finished reading a packet from the Dchan recv FIFO
+ *
+ * This code needs to be removed if anything other than HISAX uses the ISDN
+ * 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) {
+	struct IsdnCardState *cs;
+
+	if (!info || !info->D.output_callback_arg) return;
+
+	cs = (struct IsdnCardState *)info->D.output_callback_arg;
+
+	if (!cs || !cs->status_write) return;
+
+	if (cs->debug & L1_DEB_INTSTAT) {
+		*(cs->status_write++) = c;
+		if (cs->status_write > cs->status_end)
+			cs->status_write = cs->status_buf;
+	}
+}
+
+#else
+
+#define debug_info(info,c)
+
+#endif
+
+
+static void fill_D_xmit_fifo(struct amd7930_info *info)
+{
+	/* Send next byte(s) of outgoing data. */
+	while (info->D.output_ptr && info->D.output_count > 0 &&
+               (info->regs->dsr2 & AMR_DSR2_TBE)) {
+
+		/* Send the next byte and advance buffer pointer. */
+		info->regs->dctb = *(info->D.output_ptr);
+		info->D.output_ptr++;
+		info->D.output_count--;
+
+		debug_info(info, '>');
+        }
+}
+
+static void transceive_Dchannel(struct amd7930_info *info)
+{
 	__u8 dummy;
+	int lbrp=0;	/* Last Byte of Received Packet (LBRP) */
 
-	/* Clear the interrupt. */
-	dummy = regs->ir;
+#define D_XMIT_ERRORS (AMR_DER_COLLISION | AMR_DER_UNRN)
+#define D_RECV_ERRORS (AMR_DER_RABRT | AMR_DER_RFRAME | AMR_DER_FCS | \
+			AMR_DER_OVFL | AMR_DER_UNFL | AMR_DER_OVRN)
+
+	/* Transmit if we can */
+	fill_D_xmit_fifo(info);
+
+	/* Done with the xmit buffer? Notify the midlevel driver. */
+	if (info->D.output_ptr != NULL && info->D.output_count == 0) {
+		info->D.output_ptr = NULL;
+		info->D.output_count = 0;
+		debug_info(info, '+');
+		if (info->D.output_callback)
+			(*info->D.output_callback)
+				(info->D.output_callback_arg,
+				 info->regs->der);
+				 /* info->regs->der & D_XMIT_ERRORS); */
+	}
+
+	/* Read the next byte(s) of incoming data. */
+
+	while (info->regs->dsr2 & AMR_DSR2_RBA) {
+
+		if (info->D.input_ptr &&
+		    (info->D.input_count < info->D.input_limit)) {
+
+			/* Get the next byte and advance buffer pointer. */
 
+			*(info->D.input_ptr) = info->regs->dcrb;
+			info->D.input_ptr++;
+			info->D.input_count++;
+
+		} else {
+
+			/* Overflow - should be detected by chip via RBLR
+			 * so we'll just consume data until we see LBRP
+			 */
+
+			dummy = info->regs->dcrb;
+
+		}
+
+		debug_info(info, '<');
+
+		if (info->regs->dsr2 & AMR_DSR2_LBRP) {
+
+			/* End of recv packet? Notify the midlevel driver. */
+
+			__u8 der;
+
+			debug_info(info, '!');
+
+			info->D.input_ptr = NULL;
+
+			der = info->regs->der & D_RECV_ERRORS;
+
+			/* Read receive byte count - advances FIFOs */
+			info->regs->cr = AMR_DLC_DRCR;
+			dummy = info->regs->dr;
+			dummy = info->regs->dr;
+
+			if (info->D.input_callback)
+				(*info->D.input_callback)
+					(info->D.input_callback_arg, der,
+					 info->D.input_count);
+		}
+
+	}
+}
+
+long amd7930_xmit_idles=0;
+
+static void transceive_Bchannel(struct amd7930_channel *channel,
+	__volatile__ __u8 *io_reg)
+{
 	/* Send the next byte of outgoing data. */
-	if (info->output_ptr && info->output_count > 0) {
+	if (channel->output_ptr && channel->output_count > 0) {
+
 		/* Send the next byte and advance buffer pointer. */
-		regs->bbtb = *(info->output_ptr);
-		info->output_ptr++;
-		info->output_count--;
+		*io_reg = *(channel->output_ptr);
+		channel->output_ptr++;
+		channel->output_count--;
 
 		/* Done with the buffer? Notify the midlevel driver. */
-		if (info->output_count == 0) {
-			info->output_ptr = NULL;
-			info->output_count = 0;
-			sparcaudio_output_done(drv);
+		if (channel->output_count == 0) {
+			channel->output_ptr = NULL;
+			channel->output_count = 0;
+			if (channel->output_callback)
+				(*channel->output_callback)
+					(channel->output_callback_arg);
 		}
-	}
+	} else {
+		*io_reg = channel->xmit_idle_char;
+		amd7930_xmit_idles++;
+        }
 
 	/* Read the next byte of incoming data. */
-	if (info->input_ptr && info->input_count > 0) {
+	if (channel->input_ptr && channel->input_count > 0) {
+
 		/* Get the next byte and advance buffer pointer. */
-		*(info->input_ptr) = regs->bbrb;
-		info->input_ptr++;
-		info->input_count--;
+		*(channel->input_ptr) = *io_reg;
+		channel->input_ptr++;
+		channel->input_count--;
 
 		/* Done with the buffer? Notify the midlevel driver. */
-		if (info->input_count == 0) {
-			info->input_ptr = NULL;
-			info->input_count = 0;
-			sparcaudio_input_done(drv);
+		if (channel->input_count == 0) {
+			channel->input_ptr = NULL;
+			channel->input_count = 0;
+			if (channel->input_callback)
+				(*channel->input_callback)
+					(channel->input_callback_arg);
 		}
 	}
 }
 
-
-static int amd7930_open(struct inode * inode, struct file * file,
-			struct sparcaudio_driver *drv)
+/* Interrupt handler (The chip takes only one byte per interrupt. Grrr!) */
+static void amd7930_interrupt(int irq, void *dev_id, struct pt_regs *intr_regs)
 {
+	struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id;
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
-	int level;
+	struct amd7930 *regs = info->regs;
+	__u8 ir;
+	__u8 lsr;
 
-	/* Set the default audio parameters. */
-	info->map.gx = gx_coeff[128];
-	info->map.stgr = gx_coeff[0];
+	/* Clear the interrupt. */
+	ir = regs->ir;
 
-	level = (128 * (256 + NR_GER_COEFFS)) >> 8;
-	if (level >= 256) {
-		info->map.ger = ger_coeff[level-256];
-		info->map.gr = gx_coeff[255];
-	} else {
-		info->map.ger = ger_coeff[0];
-		info->map.gr = gx_coeff[level];
+	if (ir & AMR_IR_BBUF) {
+		if (info->Bb.channel_status == CHANNEL_INUSE)
+			transceive_Bchannel(&info->Bb, &info->regs->bbtb);
+		if (info->Bc.channel_status == CHANNEL_INUSE)
+			transceive_Bchannel(&info->Bc, &info->regs->bctb);
+	}
+
+	if (ir & (AMR_IR_DRTHRSH | AMR_IR_DTTHRSH | AMR_IR_DSRI)) {
+		debug_info(info, 'i');
+		debug_info(info, '0' + (ir&7));
+		transceive_Dchannel(info);
 	}
 
-	info->map.mmr2 |= AM_MAP_MMR2_LS;
+	if (ir & AMR_IR_LSRI) {
+		regs->cr = AMR_LIU_LSR;
+		lsr = regs->dr;
+
+                info->liu_state = (lsr&0x7) + 2;
+
+                if (info->liu_callback)
+			(*info->liu_callback)(info->liu_callback_arg);
+        }
+}
+
+
+static int amd7930_open(struct inode * inode, struct file * file,
+			struct sparcaudio_driver *drv)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
-	amd7930_commit_map(drv);
+	/* Set the default audio parameters. */
+	info->rgain = 128;
+	info->pgain = 200;
+	info->mgain = 0;
+	amd7930_update_map(drv);
 
 	MOD_INC_USE_COUNT;
 
@@ -258,29 +516,87 @@
 static void amd7930_release(struct inode * inode, struct file * file,
 			    struct sparcaudio_driver *drv)
 {
-	amd7930_disable_ints(drv);
+	/* amd7930_disable_ints(drv->private); */
 	MOD_DEC_USE_COUNT;
 }
 
+static void request_Baudio(struct amd7930_info *info)
+{
+	if (info->Bb.channel_status == CHANNEL_AVAILABLE) {
+
+		info->Bb.channel_status = CHANNEL_INUSE;
+		info->Baudio = &info->Bb;
+
+		/* Multiplexor map - audio (Ba) to Bb */
+		info->regs->cr = AMR_MUX_MCR1;
+		info->regs->dr = AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bb << 4);
+
+		/* Enable B channel interrupts */
+		info->regs->cr = AMR_MUX_MCR4;
+		info->regs->dr = AM_MUX_MCR4_ENABLE_INTS;
+
+	} else if (info->Bc.channel_status == CHANNEL_AVAILABLE) {
+
+		info->Bc.channel_status = CHANNEL_INUSE;
+		info->Baudio = &info->Bc;
+
+		/* Multiplexor map - audio (Ba) to Bc */
+		info->regs->cr = AMR_MUX_MCR1;
+		info->regs->dr = AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bc << 4);
+
+		/* Enable B channel interrupts */
+		info->regs->cr = AMR_MUX_MCR4;
+		info->regs->dr = AM_MUX_MCR4_ENABLE_INTS;
+
+	}
+}
+
+static void release_Baudio(struct amd7930_info *info)
+{
+	if (info->Baudio) {
+		info->Baudio->channel_status = CHANNEL_AVAILABLE;
+		info->regs->cr = AMR_MUX_MCR1;
+		info->regs->dr = 0;
+		info->Baudio = NULL;
+
+		if (info->Bb.channel_status == CHANNEL_AVAILABLE &&
+		    info->Bc.channel_status == CHANNEL_AVAILABLE) {
+
+			/* Disable B channel interrupts */
+			info->regs->cr = AMR_MUX_MCR4;
+			info->regs->dr = 0;
+		}
+	}
+}
+
 static void amd7930_start_output(struct sparcaudio_driver *drv,
 				 __u8 * buffer, unsigned long count)
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
-	info->output_ptr = buffer;
-	info->output_count = count;
-	amd7930_enable_ints(drv);
+	if (! info->Baudio) {
+		request_Baudio(info);
+	}
+
+	if (info->Baudio) {
+		info->Baudio->output_ptr = buffer;
+		info->Baudio->output_count = count;
+        	info->Baudio->output_callback = (void *) &sparcaudio_output_done;
+	        info->Baudio->output_callback_arg = (void *) drv;
+		info->Baudio->xmit_idle_char = 0;
+	}
 }
 
 static void amd7930_stop_output(struct sparcaudio_driver *drv)
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
-	info->output_ptr = NULL;
-	info->output_count = 0;
-
-	if (!info->input_ptr)
-		amd7930_disable_ints(drv);
+	if (info->Baudio) {
+		info->Baudio->output_ptr = NULL;
+		info->Baudio->output_count = 0;
+		if (! info->Baudio->input_ptr)
+			release_Baudio(info);
+	}
 }
 
 static void amd7930_start_input(struct sparcaudio_driver *drv,
@@ -288,20 +604,29 @@
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
-	info->input_ptr = buffer;
-	info->input_count = count;
-	amd7930_enable_ints(drv);
+	if (! info->Baudio) {
+		request_Baudio(info);
+	}
+
+	if (info->Baudio) {
+		info->Baudio->input_ptr = buffer;
+		info->Baudio->input_count = count;
+		info->Baudio->input_callback = (void *) &sparcaudio_input_done;
+		info->Baudio->input_callback_arg = (void *) drv;
+	}
 }
 
 static void amd7930_stop_input(struct sparcaudio_driver *drv)
 {
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
-	info->input_ptr = NULL;
-	info->input_count = 0;
+	if (info->Baudio) {
+		info->Baudio->input_ptr = NULL;
+		info->Baudio->input_count = 0;
+		if (! info->Baudio->output_ptr)
+			release_Baudio(info);
+	}
 
-	if (!info->output_ptr)
-		amd7930_disable_ints(drv);
 }
 
 static void amd7930_sunaudio_getdev(struct sparcaudio_driver *drv,
@@ -312,23 +637,686 @@
 	strncpy(audinfo->config, "audio", sizeof(audinfo->config) - 1);
 }
 
+static int amd7930_sunaudio_getdev_sunos(struct sparcaudio_driver *drv)
+{
+	return AUDIO_DEV_AMD;
+}
+
+static int amd7930_set_output_volume(struct sparcaudio_driver *drv, int vol)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+	info->pgain = vol;
+	amd7930_update_map(drv);
+	return 0;
+}
+
+static int amd7930_get_output_volume(struct sparcaudio_driver *drv)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+	return info->pgain;
+}
+
+static int amd7930_set_input_volume(struct sparcaudio_driver *drv, int vol)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+	info->rgain = vol;
+	amd7930_update_map(drv);
+	return 0;
+}
+
+static int amd7930_get_input_volume(struct sparcaudio_driver *drv)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+	return info->rgain;
+}
+
+static int amd7930_set_monitor_volume(struct sparcaudio_driver *drv, int vol)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+	info->mgain = vol;
+	amd7930_update_map(drv);
+	return 0;
+}
+
+/* Cheats. The amd has the minimum capabilities we support */
+static int amd7930_get_output_balance(struct sparcaudio_driver *drv)
+{
+	return AUDIO_MID_BALANCE;
+}
+
+static int amd7930_get_input_balance(struct sparcaudio_driver *drv)
+{
+	return AUDIO_MID_BALANCE;
+}
+
+static int amd7930_get_output_channels(struct sparcaudio_driver *drv)
+{
+	return AUDIO_MIN_PLAY_CHANNELS;
+}
+
+static int amd7930_get_input_channels(struct sparcaudio_driver *drv)
+{
+	return AUDIO_MIN_REC_CHANNELS;
+}
+
+static int amd7930_get_output_precision(struct sparcaudio_driver *drv)
+{
+	return AUDIO_MIN_PLAY_PRECISION;
+}
+
+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_get_output_port(struct sparcaudio_driver *drv)
+{
+	return AUDIO_SPEAKER; /* some of these have only HEADPHONE */
+}
+
+/* Only a microphone here, so no troubles */
+static int amd7930_get_input_port(struct sparcaudio_driver *drv)
+{
+	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)
+{
+	return AUDIO_ENCODING_ULAW;
+}
+
+/* This is what you get. Take it or leave it */
+static int amd7930_get_output_rate(struct sparcaudio_driver *drv)
+{
+	return AMD7930_RATE;
+}
+
+static int amd7930_get_input_rate(struct sparcaudio_driver *drv)
+{
+	return AMD7930_RATE;
+}
+
+static int amd7930_get_output_muted(struct sparcaudio_driver *drv)
+{
+      return 0;
+}
+
+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_get_monitor_volume(struct sparcaudio_driver *drv)
+{
+	struct amd7930_info *info = (struct amd7930_info *)drv->private;
+
+	return info->mgain;
+}
+
 
 /*
- *	Device detection and initialization.
+ *       ISDN operations
+ *
+ * Many of these routines take an "int dev" argument, which is simply
+ * an index into the drivers[] array.  Currently, we only support a
+ * single AMD 7930 chip, so the value should always be 0.  B channel
+ * operations require an "int chan", which should be 0 for channel B1
+ * and 1 for channel B2
+ *
+ * int amd7930_get_irqnum(int dev)
+ *
+ *   returns the interrupt number being used by the chip.  ISDN4linux
+ *   uses this number to watch the interrupt during initialization and
+ *   make sure something is happening.
+ *
+ * int amd7930_get_liu_state(int dev)
+ *
+ *   returns the current state of the ISDN Line Interface Unit (LIU)
+ *   as a number between 2 (state F2) and 7 (state F7).  0 may also be
+ *   returned if the chip doesn't exist or the LIU hasn't been
+ *   activated.  The meanings of the states are defined in I.430, ISDN
+ *   BRI Physical Layer Interface.  The most important two states are
+ *   F3 (shutdown) and F7 (syncronized).
+ *
+ * void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg)
+ *
+ *   initializes the LIU and optionally registers a callback to be
+ *   signaled upon a change of LIU state.  The callback will be called
+ *   with a single opaque callback_arg Once the callback has been
+ *   triggered, amd7930_get_liu_state can be used to determine the LIU
+ *   current state.
+ *
+ * void amd7930_liu_activate(int dev, int priority)
+ *
+ *   requests LIU activation at a given D-channel priority.
+ *   Successful activatation is achieved upon entering state F7, which
+ *   will trigger any callback previously registered with
+ *   amd7930_liu_init.
+ *
+ * void amd7930_liu_deactivate(int dev)
+ *
+ *   deactivates LIU.  Outstanding D and B channel transactions are
+ *   terminated rudely and without callback notification.  LIU change
+ *   of state callback will be triggered, however.
+ *
+ * void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count,
+ *               void (*callback)(void *, int), void *callback_arg)
+ *
+ *   transmits a packet - specified with buffer, count - over the D-channel
+ *   interface.  Buffer should begin with the LAPD address field and
+ *   end with the information field.  FCS and flag sequences should not
+ *   be included, nor is bit-stuffing required - all these functions are
+ *   performed by the chip.  The callback function will be called
+ *   DURING THE TOP HALF OF AN INTERRUPT HANDLER and will be passed
+ *   both the arbitrary callback_arg and an integer error indication:
+ *
+ *       0 - successful transmission; ready for next packet
+ *   non-0 - error value from chip's DER (D-Channel Error Register):
+ *       4 - collision detect
+ *     128 - underrun; irq routine didn't service chip fast enough
+ *
+ *   The callback routine should defer any time-consuming operations
+ *   to a bottom-half handler; however, amd7930_dxmit may be called
+ *   from within the callback to request back-to-back transmission of
+ *   a second packet (without repeating the priority/collision mechanism)
+ *
+ *   A comment about the "collision detect" error, which is signalled
+ *   whenever the echoed D-channel data didn't match the transmitted
+ *   data.  This is part of ISDN's normal multi-drop T-interface
+ *   operation, indicating that another device has attempted simultaneous
+ *   transmission, but can also result from line noise.  An immediate
+ *   requeue via amd7930_dxmit is suggested, but repeated collision
+ *   errors may indicate a more serious problem.
+ *
+ * void amd7930_drecv(int dev, __u8 *buffer, unsigned int size,
+ *               void (*callback)(void *, int, unsigned int),
+ *               void *callback_arg)
+ *
+ *   register a buffer - buffer, size - into which a D-channel packet
+ *   can be received.  The callback function will be called DURING
+ *   THE TOP HALF OF AN INTERRUPT HANDLER and will be passed an
+ *   arbitrary callback_arg, an integer error indication and the length
+ *   of the received packet, which will start with the address field,
+ *   end with the information field, and not contain flag or FCS
+ *   bytes.  Bit-stuffing will already have been corrected for.
+ *   Possible values of second callback argument "error":
+ *
+ *       0 - successful reception
+ *   non-0 - error value from chip's DER (D-Channel Error Register):
+ *       1 - recieved packet abort
+ *       2 - framing error; non-integer number of bytes received
+ *       8 - FCS error; CRC sequence indicated corrupted data
+ *      16 - overflow error; packet exceeded size of buffer
+ *      32 - underflow error; packet smaller than required five bytes
+ *      64 - overrun error; irq routine didn't service chip fast enough
+ *
+ * int amd7930_bopen(int dev, int chan, u_char xmit_idle_char)
+ *
+ *   This function should be called before any other operations on a B
+ *   channel.  In addition to arranging for interrupt handling and
+ *   channel multiplexing, it sets the xmit_idle_char which is
+ *   transmitted on the interface when no data buffer is available.
+ *   Suggested values are: 0 for ISDN audio; FF for HDLC mark idle; 7E
+ *   for HDLC flag idle.  Returns 0 on a successful open; -1 on error,
+ *   which is quite possible if audio and the other ISDN channel are
+ *   already in use, since the Am7930 can only send two of the three
+ *   channels to the processor
+ *
+ * void amd7930_bclose(int dev, int chan)
+ *
+ *   Shuts down a B channel when no longer in use.
+ *
+ * void amd7930_bxmit(int dev, int chan, __u8 *buffer, unsigned int count,
+ *               void (*callback)(void *), void *callback_arg)
+ *
+ *   transmits a raw data block - specified with buffer, count - over
+ *   the B channel interface specified by dev/chan.  The callback
+ *   function will be called DURING THE TOP HALF OF AN INTERRUPT
+ *   HANDLER and will be passed the arbitrary callback_arg
+ *
+ *   The callback routine should defer any time-consuming operations
+ *   to a bottom-half handler; however, amd7930_bxmit may be called
+ *   from within the callback to request back-to-back transmission of
+ *   another data block
+ *
+ * void amd7930_brecv(int dev, int chan, __u8 *buffer, unsigned int size,
+ *               void (*callback)(void *), void *callback_arg)
+ *
+ *   receive a raw data block - specified with buffer, size - over the
+ *   B channel interface specified by dev/chan.  The callback function
+ *   will be called DURING THE TOP HALF OF AN INTERRUPT HANDLER and
+ *   will be passed the arbitrary callback_arg
+ *
+ *   The callback routine should defer any time-consuming operations
+ *   to a bottom-half handler; however, amd7930_brecv may be called
+ *   from within the callback to register another buffer and ensure
+ *   continuous B channel reception without loss of data
+ *
  */
 
-static struct sparcaudio_driver drivers[MAX_DRIVERS];
-static int num_drivers;
+
+int amd7930_get_irqnum(int dev)
+{
+	struct amd7930_info *info;
+
+	if (dev > num_drivers) {
+		return(0);
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	return info->irq;
+}
+
+int amd7930_get_liu_state(int dev)
+{
+	struct amd7930_info *info;
+
+	if (dev > num_drivers) {
+		return(0);
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	return info->liu_state;
+}
+
+void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg)
+{
+	struct amd7930_info *info;
+	register unsigned long flags;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+	/* Set callback for LIU state change */
+        info->liu_callback = callback;
+	info->liu_callback_arg = callback_arg;
+
+	/* De-activate the ISDN Line Interface Unit (LIU) */
+	info->regs->cr = AMR_LIU_LMR1;
+	info->regs->dr = 0;
+
+	/* Request interrupt when LIU changes state from/to F3/F7/F8 */
+	info->regs->cr = AMR_LIU_LMR2;
+	info->regs->dr = AM_LIU_LMR2_EN_F3_INT |
+          AM_LIU_LMR2_EN_F7_INT | AM_LIU_LMR2_EN_F8_INT;
+
+	/* amd7930_enable_ints(info); */
+
+	/* Activate the ISDN Line Interface Unit (LIU) */
+	info->regs->cr = AMR_LIU_LMR1;
+	info->regs->dr = AM_LIU_LMR1_LIU_ENABL;
+
+	restore_flags(flags);
+}
+
+void amd7930_liu_activate(int dev, int priority)
+{
+	struct amd7930_info *info;
+	register unsigned long flags;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+        /* Set D-channel access priority
+         *
+         * I.430 defines a priority mechanism based on counting 1s
+         * in the echo channel before transmitting
+         *
+         * Priority 0 is eight 1s; priority 1 is ten 1s; etc
+         */
+
+        info->regs->cr = AMR_LIU_LPR;
+        info->regs->dr = priority & 0x0f;
+
+	/* request LIU activation */
+
+	info->regs->cr = AMR_LIU_LMR1;
+	info->regs->dr = AM_LIU_LMR1_LIU_ENABL | AM_LIU_LMR1_REQ_ACTIV;
+
+	restore_flags(flags);
+}
+
+void amd7930_liu_deactivate(int dev)
+{
+	struct amd7930_info *info;
+	register unsigned long flags;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+	/* deactivate LIU */
+
+	info->regs->cr = AMR_LIU_LMR1;
+	info->regs->dr = 0;
+
+	restore_flags(flags);
+}
+
+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;
+	__u8 dmr1;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+	if (info->D.output_ptr) {
+		restore_flags(flags);
+		printk("amd7930_dxmit: transmitter in use\n");
+		return;
+	}
+
+	info->D.output_ptr = buffer;
+	info->D.output_count = count;
+	info->D.output_callback = callback;
+	info->D.output_callback_arg = callback_arg;
+
+	/* Enable D-channel Transmit Threshold interrupt; disable addressing */
+	info->regs->cr = AMR_DLC_DMR1;
+	dmr1 = info->regs->dr;
+	dmr1 |= AMR_DLC_DMR1_DTTHRSH_INT;
+	dmr1 &= ~AMR_DLC_DMR1_EN_ADDRS;
+	info->regs->dr = dmr1;
+
+	/* Begin xmit by setting D-channel Transmit Byte Count Reg (DTCR) */
+	info->regs->cr = AMR_DLC_DTCR;
+	info->regs->dr = count & 0xff;
+	info->regs->dr = (count >> 8) & 0xff;
+
+	/* Prime xmit FIFO */
+	/* fill_D_xmit_fifo(info); */
+	transceive_Dchannel(info);
+
+	restore_flags(flags);
+}
+
+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;
+	__u8 dmr1;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+	if (info->D.input_ptr) {
+		restore_flags(flags);
+		printk("amd7930_drecv: receiver already has buffer!\n");
+		return;
+	}
+
+	info->D.input_ptr = buffer;
+	info->D.input_count = 0;
+	info->D.input_limit = size;
+	info->D.input_callback = callback;
+	info->D.input_callback_arg = callback_arg;
+
+	/* Enable D-channel Receive Threshold interrupt;
+	 * Enable D-channel End of Receive Packet interrupt;
+	 * Disable address recognition
+	 */
+	info->regs->cr = AMR_DLC_DMR1;
+	dmr1 = info->regs->dr;
+	dmr1 |= AMR_DLC_DMR1_DRTHRSH_INT | AMR_DLC_DMR1_EORP_INT;
+	dmr1 &= ~AMR_DLC_DMR1_EN_ADDRS;
+	info->regs->dr = dmr1;
+
+	/* Set D-channel Receive Byte Count Limit Register */
+	info->regs->cr = AMR_DLC_DRCR;
+	info->regs->dr = size & 0xff;
+	info->regs->dr = (size >> 8) & 0xff;
+
+	restore_flags(flags);
+}
+
+int amd7930_bopen(int dev, int chan, u_char xmit_idle_char)
+{
+	struct amd7930_info *info;
+	register unsigned long flags;
+
+	if (dev > num_drivers || chan<0 || chan>1) {
+		return -1;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+	if (info->Bb.channel_status == CHANNEL_AVAILABLE) {
+
+		info->Bb.channel_status = CHANNEL_INUSE;
+		info->Bb.xmit_idle_char = xmit_idle_char;
+		info->Bisdn[chan] = &info->Bb;
+
+		/* Multiplexor map - isdn (B1/2) to Bb */
+		info->regs->cr = AMR_MUX_MCR2 + chan;
+		info->regs->dr = (AM_MUX_CHANNEL_B1 + chan) |
+				 (AM_MUX_CHANNEL_Bb << 4);
+
+	} else if (info->Bc.channel_status == CHANNEL_AVAILABLE) {
+
+		info->Bc.channel_status = CHANNEL_INUSE;
+		info->Bc.xmit_idle_char = xmit_idle_char;
+		info->Bisdn[chan] = &info->Bc;
+
+		/* Multiplexor map - isdn (B1/2) to Bc */
+		info->regs->cr = AMR_MUX_MCR2 + chan;
+		info->regs->dr = (AM_MUX_CHANNEL_B1 + chan) |
+				 (AM_MUX_CHANNEL_Bc << 4);
+
+	} else {
+		restore_flags(flags);
+		return (-1);
+	}
+
+	/* Enable B channel transmit */
+	info->regs->cr = AMR_LIU_LMR1;
+	info->regs->dr |= AM_LIU_LMR1_B1_ENABL + chan;
+
+	/* Enable B channel interrupts */
+	info->regs->cr = AMR_MUX_MCR4;
+	info->regs->dr = AM_MUX_MCR4_ENABLE_INTS | AM_MUX_MCR4_REVERSE_Bb | AM_MUX_MCR4_REVERSE_Bc;
+
+	restore_flags(flags);
+	return 0;
+}
+
+void amd7930_bclose(int dev, int chan)
+{
+	struct amd7930_info *info;
+	register unsigned long flags;
+
+	if (dev > num_drivers || chan<0 || chan>1) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+
+	save_and_cli(flags);
+
+	if (info->Bisdn[chan]) {
+		info->Bisdn[chan]->channel_status = CHANNEL_AVAILABLE;
+		info->regs->cr = AMR_MUX_MCR2 + chan;
+		info->regs->dr = 0;
+		info->Bisdn[chan] = NULL;
+
+		/* Disable B channel transmit */
+		info->regs->cr = AMR_LIU_LMR1;
+		info->regs->dr &= ~(AM_LIU_LMR1_B1_ENABL + chan);
+
+		if (info->Bb.channel_status == CHANNEL_AVAILABLE &&
+		    info->Bc.channel_status == CHANNEL_AVAILABLE) {
+
+			/* Disable B channel interrupts */
+			info->regs->cr = AMR_MUX_MCR4;
+			info->regs->dr = 0;
+		}
+	}
+
+	restore_flags(flags);
+}
+
+void amd7930_bxmit(int dev, int chan, __u8 * buffer, unsigned long count,
+		   void (*callback)(void *), void *callback_arg)
+{
+	struct amd7930_info *info;
+	struct amd7930_channel *Bchan;
+	register unsigned long flags;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+	Bchan = info->Bisdn[chan];
+
+	if (Bchan) {
+		save_and_cli(flags);
+
+		Bchan->output_ptr = buffer;
+		Bchan->output_count = count;
+	        Bchan->output_callback = (void *) callback;
+        	Bchan->output_callback_arg = callback_arg;
+
+		restore_flags(flags);
+	}
+}
+
+void amd7930_brecv(int dev, int chan, __u8 * buffer, unsigned long size,
+		   void (*callback)(void *), void *callback_arg)
+{
+	struct amd7930_info *info;
+	struct amd7930_channel *Bchan;
+	register unsigned long flags;
+
+	if (dev > num_drivers) {
+		return;
+	}
+
+	info = (struct amd7930_info *) drivers[dev].private;
+	Bchan = info->Bisdn[chan];
+
+	if (Bchan) {
+		save_and_cli(flags);
+
+		Bchan->input_ptr = buffer;
+		Bchan->input_count = size;
+		Bchan->input_callback = (void *) callback;
+		Bchan->input_callback_arg = callback_arg;
+
+		restore_flags(flags);
+	}
+}
+
+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);
+
+
+/*
+ *	Device detection and initialization.
+ */
 
 static struct sparcaudio_operations amd7930_ops = {
 	amd7930_open,
 	amd7930_release,
-	NULL,			/* amd7930_ioctl */
+	NULL,				/* amd7930_ioctl */
 	amd7930_start_output,
 	amd7930_stop_output,
 	amd7930_start_input,
 	amd7930_stop_input,
 	amd7930_sunaudio_getdev,
+	amd7930_set_output_volume,
+	amd7930_get_output_volume,
+	amd7930_set_input_volume,
+	amd7930_get_input_volume,
+	amd7930_set_monitor_volume,
+	amd7930_get_monitor_volume,
+	NULL,			/* amd7930_set_output_balance */
+	amd7930_get_output_balance,
+	NULL,			/* amd7930_set_input_balance */
+	amd7930_get_input_balance,
+	NULL,			/* amd7930_set_output_channels */
+	amd7930_get_output_channels,
+	NULL,			/* amd7930_set_input_channels */
+	amd7930_get_input_channels,
+	NULL,			/* amd7930_set_output_precision */
+	amd7930_get_output_precision,
+	NULL,			/* amd7930_set_input_precision */
+	amd7930_get_input_precision,
+	NULL,			/* 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_get_output_rate,
+	NULL,			/* 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 */
+	amd7930_get_output_muted,
 };
 
 /* Attach to an amd7930 chip given its PROM node. */
@@ -348,8 +1336,10 @@
 	/* Point at the information structure and initialize it. */
 	drv->ops = &amd7930_ops;
 	info = (struct amd7930_info *)drv->private;
-	info->output_ptr = info->input_ptr = NULL;
-	info->output_count = info->input_count = 0;
+	info->Bb.output_ptr = info->Bb.input_ptr = NULL;
+	info->Bb.output_count = info->Bb.input_count = 0;
+	info->Bc.output_ptr = info->Bc.input_ptr = NULL;
+	info->Bc.output_count = info->Bc.input_count = 0;
 	info->ints_on = 1; /* force disable below */
 
 	/* Map the registers into memory. */
@@ -365,15 +1355,14 @@
 		return -EIO;
 	}
 
-	/* Disable amd7930 interrupt generation. */
-	amd7930_disable_ints(drv);
+	/* Put amd7930 in idle mode (interrupts disabled) */
+	amd7930_idle(info);
 
-	/* Initialize the MUX unit to connect the MAP to the CPU. */
-	info->regs->cr = AMR_MUX_1_4;
-	info->regs->dr = (AM_MUX_CHANNEL_Bb << 4) | AM_MUX_CHANNEL_Ba;
-	info->regs->dr = 0;
-	info->regs->dr = 0;
-	info->regs->dr = AM_MUX_MCR4_ENABLE_INTS;
+	/* Enable extended FIFO operation on D-channel */
+	info->regs->cr = AMR_DLC_EFCR;
+	info->regs->dr = AMR_DLC_EFCR_EXTEND_FIFO;
+	info->regs->cr = AMR_DLC_DMR4;
+	info->regs->dr = /* AMR_DLC_DMR4_RCV_30 | */ AMR_DLC_DMR4_XMT_14;
 
 	/* Attach the interrupt handler to the audio interrupt. */
 	prom_getproperty(node, "intr", (char *)&irq, sizeof(irq));
@@ -381,6 +1370,7 @@
 	request_irq(info->irq, amd7930_interrupt,
 		    SA_INTERRUPT, "amd7930", drv);
 	enable_irq(info->irq);
+	amd7930_enable_ints(info);
 
 	/* Initalize the local copy of the MAP registers. */
 	memset(&info->map, 0, sizeof(info->map));
@@ -413,7 +1403,7 @@
 	struct amd7930_info *info = (struct amd7930_info *)drv->private;
 
 	unregister_sparcaudio_driver(drv);
-	amd7930_disable_ints(drv);
+	amd7930_idle(info);
 	disable_irq(info->irq);
 	free_irq(info->irq, drv);
 	sparc_free_io(info->regs, info->regs_size);
@@ -421,7 +1411,6 @@
 }
 #endif
 
-
 /* Probe for the amd7930 chip and then attach the driver. */
 #ifdef MODULE
 int init_module(void)
@@ -432,12 +1421,6 @@
 	struct linux_sbus *bus;
 	struct linux_sbus_device *sdev;
 	int node;
-
-#if 0
-#ifdef MODULE
-	register_symtab(0);
-#endif
-#endif
 
 	/* Try to find the sun4c "audio" node first. */
 	node = prom_getchild(prom_root_node);

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