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
- Lines: 613
- Date:
Wed Nov 18 09:06:05 1998
- Orig file:
v2.1.128/linux/drivers/sbus/audio/amd7930.c
- Orig date:
Mon Oct 5 13:13:40 1998
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