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
- Lines: 1348
- Date:
Mon Jan 12 15:15:45 1998
- Orig file:
v2.1.78/linux/drivers/sbus/audio/amd7930.c
- Orig date:
Tue May 13 22:41:12 1997
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