Commit 9d036904 authored by David S. Miller's avatar David S. Miller

Merge nuts.ninka.net:/home/davem/src/BK/sparc-sound-2.5

into nuts.ninka.net:/home/davem/src/BK/sparc-2.5
parents 94eda096 581c1c1a
......@@ -70,7 +70,6 @@ source drivers/mtd/Config.in
source drivers/serial/Config.in
if [ "$CONFIG_SUN4" != "y" ]; then
source drivers/sbus/char/Config.in
source drivers/sbus/audio/Config.in
fi
mainmenu_option next_comment
......@@ -223,9 +222,15 @@ source drivers/input/Config.in
source fs/Config.in
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
source net/bluetooth/Config.in
tristate 'Sound card support' CONFIG_SOUND
if [ "$CONFIG_SOUND" != "n" ]; then
source sound/Config.in
fi
endmenu
source drivers/usb/Config.in
source net/bluetooth/Config.in
mainmenu_option next_comment
comment 'Watchdog'
......
......@@ -78,7 +78,6 @@ endmenu
source drivers/serial/Config.in
source drivers/sbus/char/Config.in
source drivers/sbus/audio/Config.in
source drivers/mtd/Config.in
mainmenu_option next_comment
comment 'Block devices'
......
......@@ -159,13 +159,6 @@ CONFIG_OBP_FLASH=m
# CONFIG_SUN_VIDEOPIX is not set
CONFIG_SUN_AURORA=m
#
# Linux/SPARC audio subsystem (EXPERIMENTAL)
#
CONFIG_SPARCAUDIO=m
CONFIG_SPARCAUDIO_CS4231=m
# CONFIG_SPARCAUDIO_DUMMY is not set
#
# Memory Technology Devices (MTD)
#
......@@ -497,6 +490,7 @@ CONFIG_8139TOO=m
CONFIG_SIS900=m
CONFIG_EPIC100=m
CONFIG_SUNDANCE=m
CONFIG_SUNDANCE_MMIO=y
# CONFIG_TLAN is not set
CONFIG_VIA_RHINE=m
# CONFIG_VIA_RHINE_MMIO is not set
......@@ -842,7 +836,64 @@ CONFIG_SOUND=m
#
# Advanced Linux Sound Architecture
#
# CONFIG_SND is not set
CONFIG_SND=m
CONFIG_SND_BIT32_EMUL=m
CONFIG_SND_SEQUENCER=m
CONFIG_SND_SEQ_DUMMY=m
CONFIG_SND_OSSEMUL=y
CONFIG_SND_MIXER_OSS=m
CONFIG_SND_PCM_OSS=m
CONFIG_SND_SEQUENCER_OSS=y
# CONFIG_SND_RTCTIMER is not set
# CONFIG_SND_VERBOSE_PRINTK is not set
# CONFIG_SND_DEBUG is not set
# CONFIG_SND_DEBUG_MEMORY is not set
# CONFIG_SND_DEBUG_DETECT is not set
#
# Generic devices
#
CONFIG_SND_DUMMY=m
CONFIG_SND_VIRMIDI=m
# CONFIG_SND_MTPAV is not set
# CONFIG_SND_SERIAL_U16550 is not set
# CONFIG_SND_MPU401 is not set
#
# PCI devices
#
CONFIG_SND_ALI5451=m
CONFIG_SND_CS46XX=m
CONFIG_SND_CS46XX_ACCEPT_VALID=y
CONFIG_SND_CS4281=m
CONFIG_SND_EMU10K1=m
CONFIG_SND_KORG1212=m
CONFIG_SND_NM256=m
# CONFIG_SND_RME32 is not set
# CONFIG_SND_RME96 is not set
# CONFIG_SND_RME9652 is not set
# CONFIG_SND_HDSP is not set
CONFIG_SND_TRIDENT=m
CONFIG_SND_YMFPCI=m
CONFIG_SND_ALS4000=m
CONFIG_SND_CMIPCI=m
CONFIG_SND_ENS1370=m
CONFIG_SND_ENS1371=m
CONFIG_SND_ES1938=m
CONFIG_SND_ES1968=m
CONFIG_SND_MAESTRO3=m
CONFIG_SND_FM801=m
CONFIG_SND_ICE1712=m
CONFIG_SND_INTEL8X0=m
CONFIG_SND_SONICVIBES=m
CONFIG_SND_VIA686=m
CONFIG_SND_VIA8233=m
#
# ALSA Sparc devices
#
CONFIG_SND_SUN_AMD7930=m
CONFIG_SND_SUN_CS4231=m
#
# USB support
......
CONFIG_SPARCAUDIO
This driver provides support for the build-in sound devices on most
Sun machines. If you want to be able to use this, select this option
and one or more of the lowlevel drivers below. See
<http://www.dementia.org/~shadow/sparcaudio.html> for more
information.
CONFIG_SPARCAUDIO_AMD7930
This driver supports the AMD 7930 chip found on sun4c, 4/6xx, and
SparcClassic systems.
CONFIG_SPARCAUDIO_CS4231
This driver supports the Crystal Semiconductor CS4231 chip found on
the SS4, SS5, and Ultras.
CONFIG_SPARCAUDIO_DBRI
This driver supports the DBRI audio interface found on the SS10,
SS20, Sparcbook 3, and Voyager systems.
CONFIG_SPARCAUDIO_DUMMY
This is a pseudo-driver used for debugging and testing the
sparcaudio subsystem. Say N unless you want to work on this
subsystem.
#
# Configuration script for sparcaudio subsystem
#
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
mainmenu_option next_comment
comment 'Linux/SPARC audio subsystem (EXPERIMENTAL)'
tristate 'Audio support (EXPERIMENTAL)' CONFIG_SPARCAUDIO
if [ "$CONFIG_SPARC64" != "y" ]; then
dep_tristate ' AMD7930 Lowlevel Driver' CONFIG_SPARCAUDIO_AMD7930 $CONFIG_SPARCAUDIO
dep_tristate ' DBRI Lowlevel Driver' CONFIG_SPARCAUDIO_DBRI $CONFIG_SPARCAUDIO
fi
dep_tristate ' CS4231 Lowlevel Driver' CONFIG_SPARCAUDIO_CS4231 $CONFIG_SPARCAUDIO
dep_tristate ' Dummy Lowlevel Driver' CONFIG_SPARCAUDIO_DUMMY $CONFIG_SPARCAUDIO
endmenu
fi
#
# Makefile for the kernel SPARC audio drivers.
#
# 7 October 2000, Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
# Rewritten to use lists instead of if-statements.
#
export-objs := audio.o amd7930.o dbri.o
obj-$(CONFIG_SPARCAUDIO) += audio.o
obj-$(CONFIG_SPARCAUDIO_AMD7930) += amd7930.o
obj-$(CONFIG_SPARCAUDIO_DBRI) += dbri.o
obj-$(CONFIG_SPARCAUDIO_CS4231) += cs4231.o
obj-$(CONFIG_SPARCAUDIO_DUMMY) += dmy.o
include $(TOPDIR)/Rules.make
/* $Id: amd7930.c,v 1.28 2001/10/13 01:47:29 davem Exp $
* drivers/sbus/audio/amd7930.c
*
* 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.
*
* 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.
*
* 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>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.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>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/sbus.h>
#include <asm/audioio.h>
#include "amd7930.h"
static __u8 bilinear2mulaw(__u8 data);
static __u8 mulaw2bilinear(__u8 data);
static __u8 linear2mulaw(__u16 data);
static __u16 mulaw2linear(__u8 data);
#if defined (AMD79C30_ISDN)
#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];
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 */
u8 channel_status;
/* Current buffer that the driver is playing on channel */
volatile __u8 * output_ptr;
volatile u32 output_count;
u8 xmit_idle_char;
/* Callback routine (and argument) when output is done on */
void (*output_callback)(void *, unsigned char);
void * output_callback_arg;
/* Current buffer that the driver is recording on channel */
volatile __u8 * input_ptr;
volatile u32 input_count;
volatile u32 input_limit;
/* Callback routine (and argument) when input is done on */
void (*input_callback)(void *, unsigned char, unsigned long);
void * input_callback_arg;
int input_format;
int output_format;
};
/* 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. */
unsigned long regs;
unsigned long regs_size;
struct amd7930_map map;
/* Volume information. */
int pgain, rgain, mgain;
/* Device interrupt information. */
int irq;
volatile int ints_on;
/* Format type */
int format_type;
/* 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. */
static __inline__ void amd7930_out16(unsigned long regs, u16 val)
{
sbus_writeb(val & 0xff, regs + DR);
sbus_writeb(val >> 8, regs + DR);
}
/* gx, gr & stg gains. this table must contain 256 elements with
* the 0th being "infinity" (the magic value 9008). The remaining
* elements match sun's gain curve (but with higher resolution):
* -18 to 0dB in .16dB steps then 0 to 12dB in .08dB steps.
*/
static __const__ __u16 gx_coeff[256] = {
0x9008, 0x8b7c, 0x8b51, 0x8b45, 0x8b42, 0x8b3b, 0x8b36, 0x8b33,
0x8b32, 0x8b2a, 0x8b2b, 0x8b2c, 0x8b25, 0x8b23, 0x8b22, 0x8b22,
0x9122, 0x8b1a, 0x8aa3, 0x8aa3, 0x8b1c, 0x8aa6, 0x912d, 0x912b,
0x8aab, 0x8b12, 0x8aaa, 0x8ab2, 0x9132, 0x8ab4, 0x913c, 0x8abb,
0x9142, 0x9144, 0x9151, 0x8ad5, 0x8aeb, 0x8a79, 0x8a5a, 0x8a4a,
0x8b03, 0x91c2, 0x91bb, 0x8a3f, 0x8a33, 0x91b2, 0x9212, 0x9213,
0x8a2c, 0x921d, 0x8a23, 0x921a, 0x9222, 0x9223, 0x922d, 0x9231,
0x9234, 0x9242, 0x925b, 0x92dd, 0x92c1, 0x92b3, 0x92ab, 0x92a4,
0x92a2, 0x932b, 0x9341, 0x93d3, 0x93b2, 0x93a2, 0x943c, 0x94b2,
0x953a, 0x9653, 0x9782, 0x9e21, 0x9d23, 0x9cd2, 0x9c23, 0x9baa,
0x9bde, 0x9b33, 0x9b22, 0x9b1d, 0x9ab2, 0xa142, 0xa1e5, 0x9a3b,
0xa213, 0xa1a2, 0xa231, 0xa2eb, 0xa313, 0xa334, 0xa421, 0xa54b,
0xada4, 0xac23, 0xab3b, 0xaaab, 0xaa5c, 0xb1a3, 0xb2ca, 0xb3bd,
0xbe24, 0xbb2b, 0xba33, 0xc32b, 0xcb5a, 0xd2a2, 0xe31d, 0x0808,
0x72ba, 0x62c2, 0x5c32, 0x52db, 0x513e, 0x4cce, 0x43b2, 0x4243,
0x41b4, 0x3b12, 0x3bc3, 0x3df2, 0x34bd, 0x3334, 0x32c2, 0x3224,
0x31aa, 0x2a7b, 0x2aaa, 0x2b23, 0x2bba, 0x2c42, 0x2e23, 0x25bb,
0x242b, 0x240f, 0x231a, 0x22bb, 0x2241, 0x2223, 0x221f, 0x1a33,
0x1a4a, 0x1acd, 0x2132, 0x1b1b, 0x1b2c, 0x1b62, 0x1c12, 0x1c32,
0x1d1b, 0x1e71, 0x16b1, 0x1522, 0x1434, 0x1412, 0x1352, 0x1323,
0x1315, 0x12bc, 0x127a, 0x1235, 0x1226, 0x11a2, 0x1216, 0x0a2a,
0x11bc, 0x11d1, 0x1163, 0x0ac2, 0x0ab2, 0x0aab, 0x0b1b, 0x0b23,
0x0b33, 0x0c0f, 0x0bb3, 0x0c1b, 0x0c3e, 0x0cb1, 0x0d4c, 0x0ec1,
0x079a, 0x0614, 0x0521, 0x047c, 0x0422, 0x03b1, 0x03e3, 0x0333,
0x0322, 0x031c, 0x02aa, 0x02ba, 0x02f2, 0x0242, 0x0232, 0x0227,
0x0222, 0x021b, 0x01ad, 0x0212, 0x01b2, 0x01bb, 0x01cb, 0x01f6,
0x0152, 0x013a, 0x0133, 0x0131, 0x012c, 0x0123, 0x0122, 0x00a2,
0x011b, 0x011e, 0x0114, 0x00b1, 0x00aa, 0x00b3, 0x00bd, 0x00ba,
0x00c5, 0x00d3, 0x00f3, 0x0062, 0x0051, 0x0042, 0x003b, 0x0033,
0x0032, 0x002a, 0x002c, 0x0025, 0x0023, 0x0022, 0x001a, 0x0021,
0x001b, 0x001b, 0x001d, 0x0015, 0x0013, 0x0013, 0x0012, 0x0012,
0x000a, 0x000a, 0x0011, 0x0011, 0x000b, 0x000b, 0x000c, 0x000e,
};
static __const__ __u16 ger_coeff[] = {
0x431f, /* 5. dB */
0x331f, /* 5.5 dB */
0x40dd, /* 6. dB */
0x11dd, /* 6.5 dB */
0x440f, /* 7. dB */
0x411f, /* 7.5 dB */
0x311f, /* 8. dB */
0x5520, /* 8.5 dB */
0x10dd, /* 9. dB */
0x4211, /* 9.5 dB */
0x410f, /* 10. dB */
0x111f, /* 10.5 dB */
0x600b, /* 11. dB */
0x00dd, /* 11.5 dB */
0x4210, /* 12. dB */
0x110f, /* 13. dB */
0x7200, /* 14. dB */
0x2110, /* 15. dB */
0x2200, /* 15.9 dB */
0x000b, /* 16.9 dB */
0x000f /* 18. dB */
};
#define NR_GER_COEFFS (sizeof(ger_coeff) / sizeof(ger_coeff[0]))
/* Enable amd7930 interrupts atomically. */
static void amd7930_enable_ints(struct amd7930_info *info)
{
unsigned long flags;
save_and_cli(flags);
if (!info->ints_on) {
sbus_writeb(AMR_INIT, info->regs + CR);
sbus_writeb(AM_INIT_ACTIVE, info->regs + DR);
info->ints_on = 1;
}
restore_flags(flags);
}
/* Disable amd7930 interrupts atomically. */
static __inline__ void amd7930_disable_ints(struct amd7930_info *info)
{
unsigned long flags;
save_and_cli(flags);
if (info->ints_on) {
sbus_writeb(AMR_INIT, info->regs + CR);
sbus_writeb(AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS,
info->regs + DR);
info->ints_on = 0;
}
restore_flags(flags);
}
/* Idle amd7930 (no interrupts, no audio, no data) */
static __inline__ void amd7930_idle(struct amd7930_info *info)
{
unsigned long flags;
save_and_cli(flags);
if (info->ints_on) {
sbus_writeb(AMR_INIT, info->regs + CR);
sbus_writeb(0, info->regs + DR);
info->ints_on = 0;
}
restore_flags(flags);
}
/* Commit the local copy of the MAP registers to the amd7930. */
static void amd7930_write_map(struct sparcaudio_driver *drv)
{
struct amd7930_info *info = (struct amd7930_info *) drv->private;
unsigned long regs = info->regs;
struct amd7930_map *map = &info->map;
unsigned long flags;
save_and_cli(flags);
sbus_writeb(AMR_MAP_GX, regs + CR);
amd7930_out16(regs, map->gx);
sbus_writeb(AMR_MAP_GR, regs + CR);
amd7930_out16(regs, map->gr);
sbus_writeb(AMR_MAP_STGR, regs + CR);
amd7930_out16(regs, map->stgr);
sbus_writeb(AMR_MAP_GER, regs + CR);
amd7930_out16(regs, map->ger);
sbus_writeb(AMR_MAP_MMR1, regs + CR);
sbus_writeb(map->mmr1, regs + DR);
sbus_writeb(AMR_MAP_MMR2, regs + CR);
sbus_writeb(map->mmr2, regs + DR);
restore_flags(flags);
}
/* Update the MAP registers with new settings. */
static void amd7930_update_map(struct sparcaudio_driver *drv)
{
struct amd7930_info *info = (struct amd7930_info *) drv->private;
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];
}
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
*/
#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 &&
(sbus_readb(info->regs + DSR2) & AMR_DSR2_TBE)) {
u8 byte = *(info->D.output_ptr);
/* Send the next byte and advance buffer pointer. */
sbus_writeb(byte, info->regs + DCTB);
info->D.output_ptr++;
info->D.output_count--;
debug_info(info, '>');
}
}
static void transceive_Dchannel(struct amd7930_info *info)
{
__u8 dummy;
#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,
sbus_readb(info->regs + DER));
/* sbus_readb(info->regs + DER) & D_XMIT_ERRORS); */
}
/* Read the next byte(s) of incoming data. */
while (sbus_readb(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) = sbus_readb(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 = sbus_readb(info->regs + DCRB);
}
debug_info(info, '<');
if (sbus_readb(info->regs + DSR2) & AMR_DSR2_LBRP) {
__u8 der;
/* End of recv packet? Notify the midlevel driver. */
debug_info(info, '!');
info->D.input_ptr = NULL;
der = sbus_readb(info->regs + DER) & D_RECV_ERRORS;
/* Read receive byte count - advances FIFOs */
sbus_writeb(AMR_DLC_DRCR, info->regs + CR);
dummy = sbus_readb(info->regs + DR);
dummy = sbus_readb(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,
unsigned long reg)
{
/* Send the next byte of outgoing data. */
if (channel->output_ptr && channel->output_count > 0) {
u8 byte;
/* Send the next byte and advance buffer pointer. */
switch(channel->output_format) {
case AUDIO_ENCODING_ULAW:
case AUDIO_ENCODING_ALAW:
byte = *(channel->output_ptr);
sbus_writeb(byte, reg);
break;
case AUDIO_ENCODING_LINEAR8:
byte = bilinear2mulaw(*(channel->output_ptr));
sbus_writeb(byte, reg);
break;
case AUDIO_ENCODING_LINEAR:
if (channel->output_count >= 2) {
u16 val = channel->output_ptr[0] << 8;
val |= channel->output_ptr[1];
byte = linear2mulaw(val);
sbus_writeb(byte, reg);
channel->output_ptr++;
channel->output_count--;
};
};
channel->output_ptr++;
channel->output_count--;
/* Done with the buffer? Notify the midlevel driver. */
if (channel->output_count == 0) {
channel->output_ptr = NULL;
channel->output_count = 0;
if (channel->output_callback)
(*channel->output_callback)
(channel->output_callback_arg,1);
}
} else {
sbus_writeb(channel->xmit_idle_char, reg);
amd7930_xmit_idles++;
}
/* Read the next byte of incoming data. */
if (channel->input_ptr && channel->input_count > 0) {
/* Get the next byte and advance buffer pointer. */
switch(channel->input_format) {
case AUDIO_ENCODING_ULAW:
case AUDIO_ENCODING_ALAW:
*(channel->input_ptr) = sbus_readb(reg);
break;
case AUDIO_ENCODING_LINEAR8:
*(channel->input_ptr) = mulaw2bilinear(sbus_readb(reg));
break;
case AUDIO_ENCODING_LINEAR:
if (channel->input_count >= 2) {
u16 val = mulaw2linear(sbus_readb(reg));
channel->input_ptr[0] = val >> 8;
channel->input_ptr[1] = val & 0xff;
channel->input_ptr++;
channel->input_count--;
} else {
*(channel->input_ptr) = 0;
}
};
channel->input_ptr++;
channel->input_count--;
/* Done with the buffer? Notify the midlevel driver. */
if (channel->input_count == 0) {
channel->input_ptr = NULL;
channel->input_count = 0;
if (channel->input_callback)
(*channel->input_callback)
(channel->input_callback_arg, 1, 0);
}
}
}
/* 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;
unsigned long regs = info->regs;
__u8 ir;
/* Clear the interrupt. */
ir = sbus_readb(regs + IR);
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);
}
if (ir & AMR_IR_LSRI) {
__u8 lsr;
sbus_writeb(AMR_LIU_LSR, regs + CR);
lsr = sbus_readb(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;
switch(MINOR(inode->i_rdev) & 0xf) {
case SPARCAUDIO_AUDIO_MINOR:
info->format_type = AUDIO_ENCODING_ULAW;
break;
case SPARCAUDIO_DSP_MINOR:
info->format_type = AUDIO_ENCODING_LINEAR8;
break;
case SPARCAUDIO_DSP16_MINOR:
info->format_type = AUDIO_ENCODING_LINEAR;
break;
};
MOD_INC_USE_COUNT;
return 0;
}
static void amd7930_release(struct inode * inode, struct file * file,
struct sparcaudio_driver *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 */
sbus_writeb(AMR_MUX_MCR1, info->regs + CR);
sbus_writeb(AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bb << 4),
info->regs + DR);
/* Enable B channel interrupts */
sbus_writeb(AMR_MUX_MCR4, info->regs + CR);
sbus_writeb(AM_MUX_MCR4_ENABLE_INTS, info->regs + DR);
} else if (info->Bc.channel_status == CHANNEL_AVAILABLE) {
info->Bc.channel_status = CHANNEL_INUSE;
info->Baudio = &info->Bc;
/* Multiplexor map - audio (Ba) to Bc */
sbus_writeb(AMR_MUX_MCR1, info->regs + CR);
sbus_writeb(AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bc << 4),
info->regs + DR);
/* Enable B channel interrupts */
sbus_writeb(AMR_MUX_MCR4, info->regs + CR);
sbus_writeb(AM_MUX_MCR4_ENABLE_INTS, info->regs + DR);
}
}
static void release_Baudio(struct amd7930_info *info)
{
if (info->Baudio) {
info->Baudio->channel_status = CHANNEL_AVAILABLE;
sbus_writeb(AMR_MUX_MCR1, info->regs + CR);
sbus_writeb(0, info->regs + DR);
info->Baudio = NULL;
if (info->Bb.channel_status == CHANNEL_AVAILABLE &&
info->Bc.channel_status == CHANNEL_AVAILABLE) {
/* Disable B channel interrupts */
sbus_writeb(AMR_MUX_MCR4, info->regs + CR);
sbus_writeb(0, info->regs + DR);
}
}
}
static void amd7930_start_output(struct sparcaudio_driver *drv,
__u8 * buffer, unsigned long count)
{
struct amd7930_info *info = (struct amd7930_info *) drv->private;
if (! info->Baudio)
request_Baudio(info);
if (info->Baudio) {
info->Baudio->output_ptr = buffer;
info->Baudio->output_count = count;
info->Baudio->output_format = info->format_type;
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;
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,
__u8 * buffer, unsigned long count)
{
struct amd7930_info *info = (struct amd7930_info *) drv->private;
if (! info->Baudio)
request_Baudio(info);
if (info->Baudio) {
info->Baudio->input_ptr = buffer;
info->Baudio->input_count = count;
info->Baudio->input_format = info->format_type;
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;
if (info->Baudio) {
info->Baudio->input_ptr = NULL;
info->Baudio->input_count = 0;
if (! info->Baudio->output_ptr)
release_Baudio(info);
}
}
static void amd7930_sunaudio_getdev(struct sparcaudio_driver *drv,
audio_device_t * audinfo)
{
strncpy(audinfo->name, "SUNW,am79c30", sizeof(audinfo->name) - 1);
strncpy(audinfo->version, "a", sizeof(audinfo->version) - 1);
strncpy(audinfo->config, "onboard1", sizeof(audinfo->config) - 1);
}
static int amd7930_sunaudio_getdev_sunos(struct sparcaudio_driver *drv)
{
return AUDIO_DEV_AMD;
}
static int amd7930_get_formats(struct sparcaudio_driver *drv)
{
return (AFMT_MU_LAW | AFMT_A_LAW | AFMT_U8 | AFMT_S16_BE);
}
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;
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;
}
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)
{
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_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;
}
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)
{
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 */
static int amd7930_get_input_port(struct sparcaudio_driver *drv)
{
return AUDIO_MICROPHONE;
}
static int amd7930_get_encoding(struct sparcaudio_driver *drv)
{
struct amd7930_info *info = (struct amd7930_info *) drv->private;
if ((info->map.mmr1 & AM_MAP_MMR1_ALAW) &&
(info->format_type == AUDIO_ENCODING_ALAW))
return AUDIO_ENCODING_ALAW;
return info->format_type;
}
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_ALAW:
info->map.mmr1 |= AM_MAP_MMR1_ALAW;
break;
case AUDIO_ENCODING_LINEAR8:
case AUDIO_ENCODING_LINEAR:
case AUDIO_ENCODING_ULAW:
info->map.mmr1 &= ~AM_MAP_MMR1_ALAW;
break;
default:
return -EINVAL;
};
info->format_type = value;
amd7930_update_map(drv);
return 0;
}
/* 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_set_output_rate(struct sparcaudio_driver *drv, int value)
{
return (value == AMD7930_RATE) ? 0 : -EINVAL;
}
static int amd7930_get_input_rate(struct sparcaudio_driver *drv)
{
return AMD7930_RATE;
}
static int
amd7930_set_input_rate(struct sparcaudio_driver *drv, int value)
{
return (value == AMD7930_RATE) ? 0 : -EINVAL;
}
static int amd7930_get_output_muted(struct sparcaudio_driver *drv)
{
return 0;
}
static void amd7930_loopback(struct sparcaudio_driver *drv, unsigned int value)
{
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);
}
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 retval;
}
/*
* 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 - received 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
*
*/
#if defined (AMD79C30_ISDN)
static 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;
}
static 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;
}
static void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg)
{
struct amd7930_info *info;
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) */
sbus_writeb(AMR_LIU_LMR1, info->regs + CR);
sbus_writeb(0, info->regs + DR);
/* Request interrupt when LIU changes state from/to F3/F7/F8 */
sbus_writeb(AMR_LIU_LMR2, info->regs + CR);
sbus_writeb(AM_LIU_LMR2_EN_F3_INT |
AM_LIU_LMR2_EN_F7_INT |
AM_LIU_LMR2_EN_F8_INT,
info->regs + DR);
/* amd7930_enable_ints(info); */
/* Activate the ISDN Line Interface Unit (LIU) */
sbus_writeb(AMR_LIU_LMR1, info->regs + CR);
sbus_writeb(AM_LIU_LMR1_LIU_ENABL, info->regs + DR);
restore_flags(flags);
}
static void amd7930_liu_activate(int dev, int priority)
{
struct amd7930_info *info;
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
*/
sbus_writeb(AMR_LIU_LPR, info->regs + CR);
sbus_writeb(priority & 0x0f, info->regs + DR);
/* request LIU activation */
sbus_writeb(AMR_LIU_LMR1, info->regs + CR);
sbus_writeb(AM_LIU_LMR1_LIU_ENABL | AM_LIU_LMR1_REQ_ACTIV,
info->regs + DR);
restore_flags(flags);
}
static void amd7930_liu_deactivate(int dev)
{
struct amd7930_info *info;
unsigned long flags;
if (dev > num_drivers)
return;
info = (struct amd7930_info *) drivers[dev].private;
save_and_cli(flags);
/* deactivate LIU */
sbus_writeb(AMR_LIU_LMR1, info->regs + CR);
sbus_writeb(0, info->regs + DR);
restore_flags(flags);
}
static void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count,
void (*callback)(void *, int), void *callback_arg)
{
struct amd7930_info *info;
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 */
sbus_writeb(AMR_DLC_DMR1, info->regs + CR);
dmr1 = sbus_readb(info->regs + DR);
dmr1 |= AMR_DLC_DMR1_DTTHRSH_INT;
dmr1 &= ~AMR_DLC_DMR1_EN_ADDRS;
sbus_writeb(dmr1, info->regs + DR);
/* Begin xmit by setting D-channel Transmit Byte Count Reg (DTCR) */
sbus_writeb(AMR_DLC_DTCR, info->regs + CR);
sbus_writeb(count & 0xff, info->regs + DR);
sbus_writeb((count >> 8) & 0xff, info->regs + DR);
/* Prime xmit FIFO */
/* fill_D_xmit_fifo(info); */
transceive_Dchannel(info);
restore_flags(flags);
}
static void amd7930_drecv(int dev, __u8 *buffer, unsigned int size,
void (*callback)(void *, int, unsigned int),
void *callback_arg)
{
struct amd7930_info *info;
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
*/
sbus_writeb(AMR_DLC_DMR1, info->regs + CR);
dmr1 = sbus_readb(info->regs + DR);
dmr1 |= AMR_DLC_DMR1_DRTHRSH_INT | AMR_DLC_DMR1_EORP_INT;
dmr1 &= ~AMR_DLC_DMR1_EN_ADDRS;
sbus_writeb(dmr1, info->regs + DR);
/* Set D-channel Receive Byte Count Limit Register */
sbus_writeb(AMR_DLC_DRCR, info->regs + CR);
sbus_writeb(size & 0xff, info->regs + DR);
sbus_writeb((size >> 8) & 0xff, info->regs + DR);
restore_flags(flags);
}
static int amd7930_bopen(int dev, unsigned int chan,
int mode, u_char xmit_idle_char)
{
struct amd7930_info *info;
unsigned long flags;
u8 tmp;
if (dev > num_drivers || chan<0 || chan>1)
return -1;
if (mode == L1_MODE_HDLC)
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 */
sbus_writeb(AMR_MUX_MCR2 + chan, info->regs + CR);
sbus_writeb((AM_MUX_CHANNEL_B1 + chan) |
(AM_MUX_CHANNEL_Bb << 4),
info->regs + DR);
} 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 */
sbus_writeb(AMR_MUX_MCR2 + chan, info->regs + CR);
sbus_writeb((AM_MUX_CHANNEL_B1 + chan) |
(AM_MUX_CHANNEL_Bc << 4),
info->regs + DR);
} else {
restore_flags(flags);
return (-1);
}
/* Enable B channel transmit */
sbus_writeb(AMR_LIU_LMR1, info->regs + CR);
tmp = sbus_readb(info->regs + DR);
tmp |= AM_LIU_LMR1_B1_ENABL + chan;
sbus_writeb(tmp, info->regs + DR);
/* Enable B channel interrupts */
sbus_writeb(AMR_MUX_MCR4, info->regs + CR);
sbus_writeb(AM_MUX_MCR4_ENABLE_INTS |
AM_MUX_MCR4_REVERSE_Bb |
AM_MUX_MCR4_REVERSE_Bc,
info->regs + DR);
restore_flags(flags);
return 0;
}
static void amd7930_bclose(int dev, unsigned int chan)
{
struct amd7930_info *info;
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]) {
u8 tmp;
info->Bisdn[chan]->channel_status = CHANNEL_AVAILABLE;
sbus_writeb(AMR_MUX_MCR2 + chan, info->regs + CR);
sbus_writeb(0, info->regs + DR);
info->Bisdn[chan] = NULL;
/* Disable B channel transmit */
sbus_writeb(AMR_LIU_LMR1, info->regs + CR);
tmp = sbus_readb(info->regs + DR);
tmp &= ~(AM_LIU_LMR1_B1_ENABL + chan);
sbus_writeb(tmp, info->regs + DR);
if (info->Bb.channel_status == CHANNEL_AVAILABLE &&
info->Bc.channel_status == CHANNEL_AVAILABLE) {
/* Disable B channel interrupts */
sbus_writeb(AMR_MUX_MCR4, info->regs + CR);
sbus_writeb(0, info->regs + DR);
}
}
restore_flags(flags);
}
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;
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_format = AUDIO_ENCODING_ULAW;
Bchan->output_callback = (void *) callback;
Bchan->output_callback_arg = callback_arg;
restore_flags(flags);
}
}
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;
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_format = AUDIO_ENCODING_ULAW;
Bchan->input_callback = (void *) callback;
Bchan->input_callback_arg = callback_arg;
restore_flags(flags);
}
}
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
/*
* Device detection and initialization.
*/
static struct sparcaudio_operations amd7930_ops = {
amd7930_open,
amd7930_release,
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,
amd7930_set_output_channels,
amd7930_get_output_channels,
amd7930_set_input_channels,
amd7930_get_input_channels,
amd7930_set_output_precision,
amd7930_get_output_precision,
amd7930_set_input_precision,
amd7930_get_input_precision,
amd7930_set_output_port,
amd7930_get_output_port,
NULL, /* amd7930_set_input_port */
amd7930_get_input_port,
amd7930_set_encoding,
amd7930_get_encoding,
amd7930_set_encoding,
amd7930_get_encoding,
amd7930_set_output_rate,
amd7930_get_output_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 */
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. */
static int amd7930_attach(struct sparcaudio_driver *drv, int node,
struct sbus_bus *sbus, struct sbus_dev *sdev)
{
struct linux_prom_registers regs;
struct linux_prom_irqs irq;
struct resource res, *resp;
struct amd7930_info *info;
int err;
/* Allocate our private information structure. */
drv->private = kmalloc(sizeof(struct amd7930_info), GFP_KERNEL);
if (drv->private == NULL)
return -ENOMEM;
/* Point at the information structure and initialize it. */
drv->ops = &amd7930_ops;
info = (struct amd7930_info *)drv->private;
memset(info, 0, sizeof(*info));
info->ints_on = 1; /* force disable below */
drv->dev = sdev;
/* Map the registers into memory. */
prom_getproperty(node, "reg", (char *)&regs, sizeof(regs));
if (sbus && sdev) {
resp = &sdev->resource[0];
} else {
resp = &res;
res.start = regs.phys_addr;
res.end = res.start + regs.reg_size - 1;
res.flags = IORESOURCE_IO | (regs.which_io & 0xff);
}
info->regs_size = regs.reg_size;
info->regs = sbus_ioremap(resp, 0, regs.reg_size, "amd7930");
if (!info->regs) {
printk(KERN_ERR "amd7930: could not remap registers\n");
kfree(drv->private);
return -EIO;
}
/* Put amd7930 in idle mode (interrupts disabled) */
amd7930_idle(info);
/* Enable extended FIFO operation on D-channel */
sbus_writeb(AMR_DLC_EFCR, info->regs + CR);
sbus_writeb(AMR_DLC_EFCR_EXTEND_FIFO, info->regs + DR);
sbus_writeb(AMR_DLC_DMR4, info->regs + CR);
sbus_writeb(/* AMR_DLC_DMR4_RCV_30 | */ AMR_DLC_DMR4_XMT_14,
info->regs + DR);
/* Attach the interrupt handler to the audio interrupt. */
prom_getproperty(node, "intr", (char *)&irq, sizeof(irq));
info->irq = irq.pri;
request_irq(info->irq, amd7930_interrupt,
SA_INTERRUPT, "amd7930", drv);
amd7930_enable_ints(info);
/* Initalize the local copy of the MAP registers. */
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;
info->format_type = AUDIO_ENCODING_ULAW;
info->Bb.input_format = AUDIO_ENCODING_ULAW;
info->Bb.output_format = AUDIO_ENCODING_ULAW;
info->Bc.input_format = AUDIO_ENCODING_ULAW;
info->Bc.output_format = AUDIO_ENCODING_ULAW;
amd7930_update_map(drv);
/* Register the amd7930 with the midlevel audio driver. */
err = register_sparcaudio_driver(drv, 1);
if (err < 0) {
printk(KERN_ERR "amd7930: unable to register\n");
free_irq(info->irq, drv);
sbus_iounmap(info->regs, info->regs_size);
kfree(drv->private);
return -EIO;
}
/* Announce the hardware to the user. */
printk(KERN_INFO "amd7930 at %lx irq %d\n",
info->regs, info->irq);
/* Success! */
return 0;
}
/* Detach from an amd7930 chip given the device structure. */
static void __exit amd7930_detach(struct sparcaudio_driver *drv)
{
struct amd7930_info *info = (struct amd7930_info *)drv->private;
unregister_sparcaudio_driver(drv, 1);
amd7930_idle(info);
free_irq(info->irq, drv);
sbus_iounmap(info->regs, info->regs_size);
kfree(drv->private);
}
/* Probe for the amd7930 chip and then attach the driver. */
static int __init amd7930_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
int node;
/* Try to find the sun4c "audio" node first. */
node = prom_getchild(prom_root_node);
node = prom_searchsiblings(node, "audio");
if (node && amd7930_attach(&drivers[0], node, NULL, NULL) == 0)
num_drivers = 1;
else
num_drivers = 0;
/* Probe each SBUS for amd7930 chips. */
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "audio")) {
/* Don't go over the max number of drivers. */
if (num_drivers >= MAX_DRIVERS)
continue;
if (amd7930_attach(&drivers[num_drivers],
sdev->prom_node, sdev->bus, sdev) == 0)
num_drivers++;
}
}
/* Only return success if we found some amd7930 chips. */
return (num_drivers > 0) ? 0 : -EIO;
}
static void __exit amd7930_exit(void)
{
register int i;
for (i = 0; i < num_drivers; i++) {
amd7930_detach(&drivers[i]);
num_drivers--;
}
}
module_init(amd7930_init);
module_exit(amd7930_exit);
MODULE_LICENSE("GPL");
/*************************************************************/
/* Audio format conversion */
/*************************************************************/
/* Translation tables */
static unsigned char ulaw[] = {
3, 7, 11, 15, 19, 23, 27, 31,
35, 39, 43, 47, 51, 55, 59, 63,
66, 68, 70, 72, 74, 76, 78, 80,
82, 84, 86, 88, 90, 92, 94, 96,
98, 99, 100, 101, 102, 103, 104, 105,
106, 107, 108, 109, 110, 111, 112, 113,
113, 114, 114, 115, 115, 116, 116, 117,
117, 118, 118, 119, 119, 120, 120, 121,
121, 121, 122, 122, 122, 122, 123, 123,
123, 123, 124, 124, 124, 124, 125, 125,
125, 125, 125, 125, 126, 126, 126, 126,
126, 126, 126, 126, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
253, 249, 245, 241, 237, 233, 229, 225,
221, 217, 213, 209, 205, 201, 197, 193,
190, 188, 186, 184, 182, 180, 178, 176,
174, 172, 170, 168, 166, 164, 162, 160,
158, 157, 156, 155, 154, 153, 152, 151,
150, 149, 148, 147, 146, 145, 144, 143,
143, 142, 142, 141, 141, 140, 140, 139,
139, 138, 138, 137, 137, 136, 136, 135,
135, 135, 134, 134, 134, 134, 133, 133,
133, 133, 132, 132, 132, 132, 131, 131,
131, 131, 131, 131, 130, 130, 130, 130,
130, 130, 130, 130, 129, 129, 129, 129,
129, 129, 129, 129, 129, 129, 129, 129,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128
};
static __u8 mulaw2bilinear(__u8 data)
{
return ulaw[data];
}
static unsigned char linear[] = {
0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 2, 0, 0, 0, 3,
0, 0, 0, 4, 0, 0, 0, 5,
0, 0, 0, 6, 0, 0, 0, 7,
0, 0, 0, 8, 0, 0, 0, 9,
0, 0, 0, 10, 0, 0, 0, 11,
0, 0, 0, 12, 0, 0, 0, 13,
0, 0, 0, 14, 0, 0, 0, 15,
0, 0, 16, 0, 17, 0, 18, 0,
19, 0, 20, 0, 21, 0, 22, 0,
23, 0, 24, 0, 25, 0, 26, 0,
27, 0, 28, 0, 29, 0, 30, 0,
31, 0, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45,
46, 48, 50, 52, 54, 56, 58, 60,
62, 65, 69, 73, 77, 83, 91, 103,
255, 231, 219, 211, 205, 201, 197, 193,
190, 188, 186, 184, 182, 180, 178, 176,
174, 173, 172, 171, 170, 169, 168, 167,
166, 165, 164, 163, 162, 161, 160, 0,
159, 0, 158, 0, 157, 0, 156, 0,
155, 0, 154, 0, 153, 0, 152, 0,
151, 0, 150, 0, 149, 0, 148, 0,
147, 0, 146, 0, 145, 0, 144, 0,
0, 143, 0, 0, 0, 142, 0, 0,
0, 141, 0, 0, 0, 140, 0, 0,
0, 139, 0, 0, 0, 138, 0, 0,
0, 137, 0, 0, 0, 136, 0, 0,
0, 135, 0, 0, 0, 134, 0, 0,
0, 133, 0, 0, 0, 132, 0, 0,
0, 131, 0, 0, 0, 130, 0, 0,
0, 129, 0, 0, 0, 128, 0, 0
};
static __u8 bilinear2mulaw(__u8 data)
{
return linear[data];
}
static int exp_lut[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};
#define BIAS 0x84
#define CLIP 32635
#define SWAP_ENDIAN(x) ((x >> 8) | ((x & 0xff) << 8))
static __u8 linear2mulaw(__u16 data)
{
static int sign, exponent, mantissa;
/* not really sure, if swapping is ok - comment next line to disable it */
data = SWAP_ENDIAN(data);
sign = (data >> 8) & 0x80;
if (sign != 0) data = -data;
if (data > CLIP) data = CLIP;
data += BIAS;
exponent = exp_lut[(data >> 7) & 0xFF];
mantissa = (data >> (exponent + 3)) & 0x0F;
return (~(sign | (exponent << 4) | mantissa));
}
static __u16 mulaw2linear(__u8 data)
{
/* this conversion is not working */
return data;
}
#if 0
#define INOUT(x,y) (((x) << 16) | (y))
static int convert_audio(int in_format, int out_format, __u8* buffer, int count)
{
static int i,sign,exponent;
static __u16 data;
if (in_format == out_format) return count;
switch(INOUT(in_format, out_format)) {
case INOUT(AUDIO_ENCODING_ULAW, AUDIO_ENCODING_LINEAR8):
for (i = 0;i < count; i++) {
buffer[i] = ulaw[buffer[i]];
};
break;
case INOUT(AUDIO_ENCODING_ULAW, AUDIO_ENCODING_LINEAR):
break;
case INOUT(AUDIO_ENCODING_LINEAR, AUDIO_ENCODING_ULAW):
/* buffer is two-byte => convert to first */
for (i = 0; i < count/2; i++) {
data = ((__u16*)buffer)[i];
sign = (data >> 8) & 0x80;
if (data > CLIP) data = CLIP;
data += BIAS;
exponent = exp_lut[(data >> 7) & 0xFF];
buffer[i] = ~(sign | (exponent << 4) |
((data >> (exponent + 3)) & 0x0F));
};
break;
case INOUT(AUDIO_ENCODING_LINEAR8, AUDIO_ENCODING_ULAW):
for (i = 0; i < count; i++) {
buffer[i] = linear[buffer[i]];
};
break;
default:
return 0;
};
return count;
}
#undef INOUT
#endif
#undef BIAS
#undef CLIP
#undef SWAP_ENDIAN
/* $Id: amd7930.h,v 1.8 1999/09/21 14:37:10 davem Exp $
* drivers/sbus/audio/amd7930.h
*
* Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
*
* Definitions for the AMD79C30 Digital Subscriber Controller which is
* used as an audio chip in sun4c architecture machines. The
* information in this file is based on Advanced Micro Devices
* Publication 09893, Rev G, Amendment /0, Final (a.k.a. the data
* sheet).
*/
#ifndef _AMD7930_H_
#define _AMD7930_H_
#include <linux/types.h>
#include <linux/version.h>
/* Register interface presented to the CPU by the amd7930. */
#define CR 0x00UL /* Command Register (W) */
#define IR CR /* Interrupt Register (R) */
#define DR 0x01UL /* Data Register (R/W) */
#define DSR1 0x02UL /* D-channel Status Register 1 (R) */
#define DER 0x03UL /* D-channel Error Register (R) */
#define DCTB 0x04UL /* D-channel Transmit Buffer (W) */
#define DCRB DCTB /* D-channel Receive Buffer (R) */
#define BBTB 0x05UL /* Bb-channel Transmit Buffer (W) */
#define BBRB BBTB /* Bb-channel Receive Buffer (R) */
#define BCTB 0x06UL /* Bc-channel Transmit Buffer (W) */
#define BCRB BCTB /* Bc-channel Receive Buffer (R) */
#define DSR2 0x07UL /* D-channel Status Register 2 (R) */
/* Indirect registers in the Main Audio Processor. */
struct amd7930_map {
__u16 x[8];
__u16 r[8];
__u16 gx;
__u16 gr;
__u16 ger;
__u16 stgr;
__u16 ftgr;
__u16 atgr;
__u8 mmr1;
__u8 mmr2;
};
/* After an amd7930 interrupt, reading the Interrupt Register (ir)
* clears the interrupt and returns a bitmask indicated which
* interrupt source(s) require service
*/
#define AMR_IR_DTTHRSH 0x01 /* D-channel xmit threshold */
#define AMR_IR_DRTHRSH 0x02 /* D-channel recv threshold */
#define AMR_IR_DSRI 0x04 /* D-channel packet status */
#define AMR_IR_DERI 0x08 /* D-channel error */
#define AMR_IR_BBUF 0x10 /* B-channel data xfer */
#define AMR_IR_LSRI 0x20 /* LIU status */
#define AMR_IR_DSR2I 0x40 /* D-channel buffer status */
#define AMR_IR_MLTFRMI 0x80 /* multiframe or PP */
/* The amd7930 has "indirect registers" which are accessed by writing
* the register number into the Command Register and then reading or
* writing values from the Data Register as appropriate. We define the
* AMR_* macros to be the indirect register numbers and AM_* macros to
* be bits in whatever register is referred to.
*/
/* Initialization */
#define AMR_INIT 0x21
#define AM_INIT_ACTIVE 0x01
#define AM_INIT_DATAONLY 0x02
#define AM_INIT_POWERDOWN 0x03
#define AM_INIT_DISABLE_INTS 0x04
#define AMR_INIT2 0x20
#define AM_INIT2_ENABLE_POWERDOWN 0x20
#define AM_INIT2_ENABLE_MULTIFRAME 0x10
/* Line Interface Unit */
#define AMR_LIU_LSR 0xA1
#define AM_LIU_LSR_STATE 0x07
#define AM_LIU_LSR_F3 0x08
#define AM_LIU_LSR_F7 0x10
#define AM_LIU_LSR_F8 0x20
#define AM_LIU_LSR_HSW 0x40
#define AM_LIU_LSR_HSW_CHG 0x80
#define AMR_LIU_LPR 0xA2
#define AMR_LIU_LMR1 0xA3
#define AM_LIU_LMR1_B1_ENABL 0x01
#define AM_LIU_LMR1_B2_ENABL 0x02
#define AM_LIU_LMR1_F_DISABL 0x04
#define AM_LIU_LMR1_FA_DISABL 0x08
#define AM_LIU_LMR1_REQ_ACTIV 0x10
#define AM_LIU_LMR1_F8_F3 0x20
#define AM_LIU_LMR1_LIU_ENABL 0x40
#define AMR_LIU_LMR2 0xA4
#define AM_LIU_LMR2_DECHO 0x01
#define AM_LIU_LMR2_DLOOP 0x02
#define AM_LIU_LMR2_DBACKOFF 0x04
#define AM_LIU_LMR2_EN_F3_INT 0x08
#define AM_LIU_LMR2_EN_F8_INT 0x10
#define AM_LIU_LMR2_EN_HSW_INT 0x20
#define AM_LIU_LMR2_EN_F7_INT 0x40
#define AMR_LIU_2_4 0xA5
#define AMR_LIU_MF 0xA6
#define AMR_LIU_MFSB 0xA7
#define AMR_LIU_MFQB 0xA8
/* Multiplexor */
#define AMR_MUX_MCR1 0x41
#define AMR_MUX_MCR2 0x42
#define AMR_MUX_MCR3 0x43
#define AM_MUX_CHANNEL_B1 0x01
#define AM_MUX_CHANNEL_B2 0x02
#define AM_MUX_CHANNEL_Ba 0x03
#define AM_MUX_CHANNEL_Bb 0x04
#define AM_MUX_CHANNEL_Bc 0x05
#define AM_MUX_CHANNEL_Bd 0x06
#define AM_MUX_CHANNEL_Be 0x07
#define AM_MUX_CHANNEL_Bf 0x08
#define AMR_MUX_MCR4 0x44
#define AM_MUX_MCR4_ENABLE_INTS 0x08
#define AM_MUX_MCR4_REVERSE_Bb 0x10
#define AM_MUX_MCR4_REVERSE_Bc 0x20
#define AMR_MUX_1_4 0x45
/* Main Audio Processor */
#define AMR_MAP_X 0x61
#define AMR_MAP_R 0x62
#define AMR_MAP_GX 0x63
#define AMR_MAP_GR 0x64
#define AMR_MAP_GER 0x65
#define AMR_MAP_STGR 0x66
#define AMR_MAP_FTGR_1_2 0x67
#define AMR_MAP_ATGR_1_2 0x68
#define AMR_MAP_MMR1 0x69
#define AM_MAP_MMR1_ALAW 0x01
#define AM_MAP_MMR1_GX 0x02
#define AM_MAP_MMR1_GR 0x04
#define AM_MAP_MMR1_GER 0x08
#define AM_MAP_MMR1_X 0x10
#define AM_MAP_MMR1_R 0x20
#define AM_MAP_MMR1_STG 0x40
#define AM_MAP_MMR1_LOOPBACK 0x80
#define AMR_MAP_MMR2 0x6A
#define AM_MAP_MMR2_AINB 0x01
#define AM_MAP_MMR2_LS 0x02
#define AM_MAP_MMR2_ENABLE_DTMF 0x04
#define AM_MAP_MMR2_ENABLE_TONEGEN 0x08
#define AM_MAP_MMR2_ENABLE_TONERING 0x10
#define AM_MAP_MMR2_DISABLE_HIGHPASS 0x20
#define AM_MAP_MMR2_DISABLE_AUTOZERO 0x40
#define AMR_MAP_1_10 0x6B
#define AMR_MAP_MMR3 0x6C
#define AMR_MAP_STRA 0x6D
#define AMR_MAP_STRF 0x6E
#define AMR_MAP_PEAKX 0x70
#define AMR_MAP_PEAKR 0x71
#define AMR_MAP_15_16 0x72
/* Data Link Controller */
#define AMR_DLC_FRAR_1_2_3 0x81
#define AMR_DLC_SRAR_1_2_3 0x82
#define AMR_DLC_TAR 0x83
#define AMR_DLC_DRLR 0x84
#define AMR_DLC_DTCR 0x85
#define AMR_DLC_DMR1 0x86
#define AMR_DLC_DMR1_DTTHRSH_INT 0x01
#define AMR_DLC_DMR1_DRTHRSH_INT 0x02
#define AMR_DLC_DMR1_TAR_ENABL 0x04
#define AMR_DLC_DMR1_EORP_INT 0x08
#define AMR_DLC_DMR1_EN_ADDR1 0x10
#define AMR_DLC_DMR1_EN_ADDR2 0x20
#define AMR_DLC_DMR1_EN_ADDR3 0x40
#define AMR_DLC_DMR1_EN_ADDR4 0x80
#define AMR_DLC_DMR1_EN_ADDRS 0xf0
#define AMR_DLC_DMR2 0x87
#define AMR_DLC_DMR2_RABRT_INT 0x01
#define AMR_DLC_DMR2_RESID_INT 0x02
#define AMR_DLC_DMR2_COLL_INT 0x04
#define AMR_DLC_DMR2_FCS_INT 0x08
#define AMR_DLC_DMR2_OVFL_INT 0x10
#define AMR_DLC_DMR2_UNFL_INT 0x20
#define AMR_DLC_DMR2_OVRN_INT 0x40
#define AMR_DLC_DMR2_UNRN_INT 0x80
#define AMR_DLC_1_7 0x88
#define AMR_DLC_DRCR 0x89
#define AMR_DLC_RNGR1 0x8A
#define AMR_DLC_RNGR2 0x8B
#define AMR_DLC_FRAR4 0x8C
#define AMR_DLC_SRAR4 0x8D
#define AMR_DLC_DMR3 0x8E
#define AMR_DLC_DMR3_VA_INT 0x01
#define AMR_DLC_DMR3_EOTP_INT 0x02
#define AMR_DLC_DMR3_LBRP_INT 0x04
#define AMR_DLC_DMR3_RBA_INT 0x08
#define AMR_DLC_DMR3_LBT_INT 0x10
#define AMR_DLC_DMR3_TBE_INT 0x20
#define AMR_DLC_DMR3_RPLOST_INT 0x40
#define AMR_DLC_DMR3_KEEP_FCS 0x80
#define AMR_DLC_DMR4 0x8F
#define AMR_DLC_DMR4_RCV_1 0x00
#define AMR_DLC_DMR4_RCV_2 0x01
#define AMR_DLC_DMR4_RCV_4 0x02
#define AMR_DLC_DMR4_RCV_8 0x03
#define AMR_DLC_DMR4_RCV_16 0x01
#define AMR_DLC_DMR4_RCV_24 0x02
#define AMR_DLC_DMR4_RCV_30 0x03
#define AMR_DLC_DMR4_XMT_1 0x00
#define AMR_DLC_DMR4_XMT_2 0x04
#define AMR_DLC_DMR4_XMT_4 0x08
#define AMR_DLC_DMR4_XMT_8 0x0c
#define AMR_DLC_DMR4_XMT_10 0x08
#define AMR_DLC_DMR4_XMT_14 0x0c
#define AMR_DLC_DMR4_IDLE_MARK 0x00
#define AMR_DLC_DMR4_IDLE_FLAG 0x10
#define AMR_DLC_DMR4_ADDR_BOTH 0x00
#define AMR_DLC_DMR4_ADDR_1ST 0x20
#define AMR_DLC_DMR4_ADDR_2ND 0xa0
#define AMR_DLC_DMR4_CR_ENABLE 0x40
#define AMR_DLC_12_15 0x90
#define AMR_DLC_ASR 0x91
#define AMR_DLC_EFCR 0x92
#define AMR_DLC_EFCR_EXTEND_FIFO 0x01
#define AMR_DLC_EFCR_SEC_PKT_INT 0x02
#define AMR_DSR1_VADDR 0x01
#define AMR_DSR1_EORP 0x02
#define AMR_DSR1_PKT_IP 0x04
#define AMR_DSR1_DECHO_ON 0x08
#define AMR_DSR1_DLOOP_ON 0x10
#define AMR_DSR1_DBACK_OFF 0x20
#define AMR_DSR1_EOTP 0x40
#define AMR_DSR1_CXMT_ABRT 0x80
#define AMR_DSR2_LBRP 0x01
#define AMR_DSR2_RBA 0x02
#define AMR_DSR2_RPLOST 0x04
#define AMR_DSR2_LAST_BYTE 0x08
#define AMR_DSR2_TBE 0x10
#define AMR_DSR2_MARK_IDLE 0x20
#define AMR_DSR2_FLAG_IDLE 0x40
#define AMR_DSR2_SECOND_PKT 0x80
#define AMR_DER_RABRT 0x01
#define AMR_DER_RFRAME 0x02
#define AMR_DER_COLLISION 0x04
#define AMR_DER_FCS 0x08
#define AMR_DER_OVFL 0x10
#define AMR_DER_UNFL 0x20
#define AMR_DER_OVRN 0x40
#define AMR_DER_UNRN 0x80
/* Peripheral Port */
#define AMR_PP_PPCR1 0xC0
#define AMR_PP_PPSR 0xC1
#define AMR_PP_PPIER 0xC2
#define AMR_PP_MTDR 0xC3
#define AMR_PP_MRDR 0xC3
#define AMR_PP_CITDR0 0xC4
#define AMR_PP_CIRDR0 0xC4
#define AMR_PP_CITDR1 0xC5
#define AMR_PP_CIRDR1 0xC5
#define AMR_PP_PPCR2 0xC8
#define AMR_PP_PPCR3 0xC9
/* Give this chip a "default" sample rate */
#define AMD7930_RATE (8000)
#endif /* _AMD7930_H_ */
This source diff could not be displayed because it is too large. You can view the blob instead.
/* $Id: cs4215.h,v 1.8 2000/10/27 07:01:38 uzi Exp $
* drivers/sbus/audio/cs4215.h
*
* Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de)
* Used with dbri.h
*/
#ifndef _CS4215_H_
#define _CS4215_H_
struct cs4215 {
__u8 data[4]; /* Data mode: Time slots 5-8 */
__u8 ctrl[4]; /* Ctrl mode: Time slots 1-4 */
__u8 onboard;
__u8 offset; /* Bit offset from frame sync to time slot 1 */
volatile __u32 status;
volatile __u32 version;
};
/*
* Control mode first
*/
/* Time Slot 1, Status register */
#define CS4215_CLB (1<<2) /* Control Latch Bit */
#define CS4215_OLB (1<<3) /* 1: line: 2.0V, speaker 4V */
/* 0: line: 2.8V, speaker 8V */
#define CS4215_MLB (1<<4) /* 1: Microphone: 20dB gain disabled */
#define CS4215_RSRVD_1 (1<<5)
/* Time Slot 2, Data Format Register */
#define CS4215_DFR_LINEAR16 0
#define CS4215_DFR_ULAW 1
#define CS4215_DFR_ALAW 2
#define CS4215_DFR_LINEAR8 3
#define CS4215_DFR_STEREO (1<<2)
static struct {
unsigned short freq;
unsigned char xtal;
unsigned char csval;
} CS4215_FREQ[] = {
{ 8000, (1<<4), (0<<3) },
{ 16000, (1<<4), (1<<3) },
{ 27429, (1<<4), (2<<3) }, /* Actually 24428.57 */
{ 32000, (1<<4), (3<<3) },
/* { NA, (1<<4), (4<<3) }, */
/* { NA, (1<<4), (5<<3) }, */
{ 48000, (1<<4), (6<<3) },
{ 9600, (1<<4), (7<<3) },
{ 5513, (2<<4), (0<<3) }, /* Actually 5512.5 */
{ 11025, (2<<4), (1<<3) },
{ 18900, (2<<4), (2<<3) },
{ 22050, (2<<4), (3<<3) },
{ 37800, (2<<4), (4<<3) },
{ 44100, (2<<4), (5<<3) },
{ 33075, (2<<4), (6<<3) },
{ 6615, (2<<4), (7<<3) },
{ 0, 0, 0 }
};
#define CS4215_HPF (1<<7) /* High Pass Filter, 1: Enabled */
#define CS4215_12_MASK 0xfcbf /* Mask off reserved bits in slot 1 & 2 */
/* Time Slot 3, Serial Port Control register */
#define CS4215_XEN (1<<0) /* 0: Enable serial output */
#define CS4215_XCLK (1<<1) /* 1: Master mode: Generate SCLK */
#define CS4215_BSEL_64 (0<<2) /* Bitrate: 64 bits per frame */
#define CS4215_BSEL_128 (1<<2)
#define CS4215_BSEL_256 (2<<2)
#define CS4215_MCK_MAST (0<<4) /* Master clock */
#define CS4215_MCK_XTL1 (1<<4) /* 24.576 MHz clock source */
#define CS4215_MCK_XTL2 (2<<4) /* 16.9344 MHz clock source */
#define CS4215_MCK_CLK1 (3<<4) /* Clockin, 256 x Fs */
#define CS4215_MCK_CLK2 (4<<4) /* Clockin, see DFR */
/* Time Slot 4, Test Register */
#define CS4215_DAD (1<<0) /* 0:Digital-Dig loop, 1:Dig-Analog-Dig loop */
#define CS4215_ENL (1<<1) /* Enable Loopback Testing */
/* Time Slot 5, Parallel Port Register */
/* Read only here and the same as the in data mode */
/* Time Slot 6, Reserved */
/* Time Slot 7, Version Register */
#define CS4215_VERSION_MASK 0xf /* Known versions 0/C, 1/D, 2/E */
/* Time Slot 8, Reserved */
/*
* Data mode
*/
/* Time Slot 1-2: Left Channel Data, 2-3: Right Channel Data */
/* Time Slot 5, Output Setting */
#define CS4215_LO(v) v /* Left Output Attenuation 0x3f: -94.5 dB */
#define CS4215_LE (1<<6) /* Line Out Enable */
#define CS4215_HE (1<<7) /* Headphone Enable */
/* Time Slot 6, Output Setting */
#define CS4215_RO(v) v /* Right Output Attenuation 0x3f: -94.5 dB */
#define CS4215_SE (1<<6) /* Speaker Enable */
#define CS4215_ADI (1<<7) /* A/D Data Invalid: Busy in calibration */
/* Time Slot 7, Input Setting */
#define CS4215_LG(v) v /* Left Gain Setting 0xf: 22.5 dB */
#define CS4215_IS (1<<4) /* Input Select: 1=Microphone, 0=Line */
#define CS4215_OVR (1<<5) /* 1: Overrange condition occurred */
#define CS4215_PIO0 (1<<6) /* Parallel I/O 0 */
#define CS4215_PIO1 (1<<7)
/* Time Slot 8, Input Setting */
#define CS4215_RG(v) v /* Right Gain Setting 0xf: 22.5 dB */
#define CS4215_MA(v) (v<<4) /* Monitor Path Attenuation 0xf: mute */
#endif /* _CS4215_H_ */
/* $Id: cs4231.c,v 1.47 2001/10/08 22:19:50 davem Exp $
* drivers/sbus/audio/cs4231.c
*
* Copyright 1996, 1997, 1998, 1999 Derrick J Brashear (shadow@andrew.cmu.edu)
* The 4231/ebus support was written by David Miller, who didn't bother
* crediting himself here, so I will.
*
* Based on the AMD7930 driver:
* Copyright 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
*
* This is the lowlevel driver for the CS4231 audio chip found on some
* sun4m and sun4u machines.
*
* This was culled from the Crystal docs on the 4231a, and the addendum they
* faxed me on the 4231.
* The APC DMA controller support unfortunately is not documented. Thanks, Sun.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/soundcard.h>
#include <linux/version.h>
#include <linux/ioport.h>
#include <asm/openprom.h>
#include <asm/oplib.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/sbus.h>
#ifdef CONFIG_PCI
#define EB4231_SUPPORT
#include <asm/ebus.h>
#include <asm/pbm.h>
#endif
#include <asm/audioio.h>
#include "cs4231.h"
#undef __CS4231_DEBUG
#undef __CS4231_TRACE
#define __CS4231_ERROR
#ifdef __CS4231_ERROR
#define eprintk(x) printk x
#else
#define eprintk(x)
#endif
#ifdef __CS4231_TRACE
#define tprintk(x) printk x
#else
#define tprintk(x)
#endif
#ifdef __CS4231_DEBUG
#define dprintk(x) printk x
#else
#define dprintk(x)
#endif
#define MAX_DRIVERS 1
static struct sparcaudio_driver drivers[MAX_DRIVERS];
static int num_drivers;
static int cs4231_record_gain(struct sparcaudio_driver *drv, int value,
unsigned char balance);
static int cs4231_play_gain(struct sparcaudio_driver *drv, int value,
unsigned char balance);
static void cs4231_ready(struct sparcaudio_driver *drv);
static void cs4231_playintr(struct sparcaudio_driver *drv, int);
static int cs4231_recintr(struct sparcaudio_driver *drv);
static int cs4231_output_muted(struct sparcaudio_driver *drv, int value);
static void cs4231_pollinput(struct sparcaudio_driver *drv);
static int cs4231_length_to_samplecount(struct audio_prinfo *thisdir,
unsigned int length);
static void cs4231_getsamplecount(struct sparcaudio_driver *drv,
unsigned int length, unsigned int value);
#ifdef EB4231_SUPPORT
static void eb4231_pollinput(struct sparcaudio_driver *drv);
#endif
/* Serveral shorthands save typing... */
#define CHIP_READY() \
do { udelay(100); cs4231_ready(drv); udelay(1000); } while(0)
#define WRITE_IAR(__VAL) \
CS4231_WRITE8(cs4231_chip, cs4231_chip->regs + IAR, __VAL)
#define WRITE_IDR(__VAL) \
CS4231_WRITE8(cs4231_chip, cs4231_chip->regs + IDR, __VAL)
#define READ_IAR() \
CS4231_READ8(cs4231_chip, cs4231_chip->regs + IAR)
#define READ_IDR() \
CS4231_READ8(cs4231_chip, cs4231_chip->regs + IDR)
/* Enable cs4231 interrupts atomically. */
static void cs4231_enable_interrupts(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned long flags;
tprintk(("enabling interrupts\n"));
save_flags(flags);
cli();
if ((cs4231_chip->status & CS_STATUS_INTS_ON) == 0) {
WRITE_IAR(0xa);
WRITE_IDR(INTR_ON);
cs4231_chip->status |= CS_STATUS_INTS_ON;
}
restore_flags(flags);
}
/* Disable cs4231 interrupts atomically. */
static void cs4231_disable_interrupts(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned long flags;
tprintk(("disabling interrupts\n"));
save_flags(flags);
cli();
if ((cs4231_chip->status & CS_STATUS_INTS_ON) != 0) {
WRITE_IAR(0xa);
WRITE_IDR(INTR_OFF);
cs4231_chip->status &= ~CS_STATUS_INTS_ON;
}
restore_flags(flags);
}
static void cs4231_enable_play(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned long flags;
tprintk(("enabling play\n"));
save_flags(flags);
cli();
WRITE_IAR(0x9);
WRITE_IDR(READ_IDR() | PEN_ENABLE);
restore_flags(flags);
}
static void cs4231_disable_play(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned long flags;
tprintk(("disabling play\n"));
save_flags(flags);
cli();
WRITE_IAR(0x9);
WRITE_IDR(READ_IDR() & PEN_DISABLE);
restore_flags(flags);
}
static void cs4231_enable_rec(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned long flags;
tprintk(("enabling rec\n"));
save_flags(flags);
cli();
WRITE_IAR(0x9);
WRITE_IDR(READ_IDR() | CEN_ENABLE);
restore_flags(flags);
}
static void cs4231_disable_rec(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned long flags;
tprintk(("disabling rec\n"));
save_flags(flags);
cli();
WRITE_IAR(0x9);
WRITE_IDR(READ_IDR() & CEN_DISABLE);
restore_flags(flags);
}
static struct cs4231_rates {
int speed, bits;
} cs4231_rate_table[] = {
{ 5512, CS4231_DFR_5512 },
{ 6615, CS4231_DFR_6615 },
{ 8000, CS4231_DFR_8000 },
{ 9600, CS4231_DFR_9600 },
{ 11025, CS4231_DFR_11025 },
{ 16000, CS4231_DFR_16000 },
{ 18900, CS4231_DFR_18900 },
{ 22050, CS4231_DFR_22050 },
{ 27429, CS4231_DFR_27429 },
{ 32000, CS4231_DFR_32000 },
{ 33075, CS4231_DFR_33075 },
{ 37800, CS4231_DFR_37800 },
{ 44100, CS4231_DFR_44100 },
{ 48000, CS4231_DFR_48000 }
};
#define NUM_RATES (sizeof(cs4231_rate_table) / sizeof(struct cs4231_rates))
static int cs4231_rate_to_bits(struct sparcaudio_driver *drv, int *value)
{
struct cs4231_rates *p = &cs4231_rate_table[0];
int i, wanted = *value;
/* We try to be nice and approximate what the user asks for. */
if (wanted < 5512)
wanted = 5512;
if (wanted > 48000)
wanted = 48000;
for (i = 0; i < NUM_RATES; i++, p++) {
/* Exact match? */
if (wanted == p->speed)
break;
/* If we're inbetween two entries, and neither is exact,
* pick the closest one.
*/
if (wanted == p[1].speed)
continue;
if (wanted > p->speed && wanted < p[1].speed) {
int diff1, diff2;
diff1 = wanted - p->speed;
diff2 = p[1].speed - wanted;
if (diff2 < diff1)
p++;
break;
}
}
*value = p->speed;
return p->bits;
}
static int cs4231_encoding_to_bits(struct sparcaudio_driver *drv, int value)
{
int set_bits;
switch (value) {
case AUDIO_ENCODING_ULAW:
set_bits = CS4231_DFR_ULAW;
break;
case AUDIO_ENCODING_ALAW:
set_bits = CS4231_DFR_ALAW;
break;
case AUDIO_ENCODING_DVI:
set_bits = CS4231_DFR_ADPCM;
break;
case AUDIO_ENCODING_LINEARLE:
set_bits = CS4231_DFR_LINEARLE;
break;
case AUDIO_ENCODING_LINEAR:
set_bits = CS4231_DFR_LINEARBE;
break;
case AUDIO_ENCODING_LINEAR8:
set_bits = CS4231_DFR_LINEAR8;
break;
default:
set_bits = -EINVAL;
break;
};
return set_bits;
}
static int cs4231_set_output_encoding(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp_bits, set_bits;
tprintk(("output encoding %d\n", value));
if (value != 0) {
set_bits = cs4231_encoding_to_bits(drv, value);
if (set_bits >= 0) {
READ_IDR();
READ_IDR();
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x8);
tmp_bits = READ_IDR();
WRITE_IDR(CHANGE_ENCODING(tmp_bits, set_bits));
READ_IDR();
READ_IDR();
CHIP_READY();
cs4231_chip->perchip_info.play.encoding = value;
return 0;
}
}
dprintk(("output enc failed\n"));
return -EINVAL;
}
static int cs4231_get_output_encoding(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.play.encoding;
}
static int cs4231_set_input_encoding(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp_bits, set_bits;
tprintk(("input encoding %d\n", value));
if (value != 0) {
set_bits = cs4231_encoding_to_bits(drv, value);
if (set_bits >= 0) {
READ_IDR();
READ_IDR();
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x1c);
tmp_bits = READ_IDR();
WRITE_IDR(CHANGE_ENCODING(tmp_bits, set_bits));
READ_IDR();
READ_IDR();
CHIP_READY();
cs4231_chip->perchip_info.record.encoding = value;
return 0;
}
}
dprintk(("input enc failed\n"));
return -EINVAL;
}
static int cs4231_get_input_encoding(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.record.encoding;
}
static int cs4231_set_output_rate(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp_bits, set_bits;
tprintk(("output rate %d\n", value));
if (value != 0) {
set_bits = cs4231_rate_to_bits(drv, &value);
if (set_bits >= 0) {
READ_IDR();
READ_IDR();
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x8);
tmp_bits = READ_IDR();
WRITE_IDR(CHANGE_DFR(tmp_bits, set_bits));
READ_IDR();
READ_IDR();
CHIP_READY();
cs4231_chip->perchip_info.play.sample_rate = value;
tprintk(("tmp_bits[%02x] set_bits[%02x] CHANGE_DFR[%02x]\n",
tmp_bits, set_bits, CHANGE_DFR(tmp_bits, set_bits)));
return 0;
}
}
dprintk(("output rate failed\n"));
return -EINVAL;
}
static int cs4231_get_output_rate(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.play.sample_rate;
}
static int cs4231_set_input_rate(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp_bits, set_bits;
tprintk(("input rate %d\n", value));
if (value != 0) {
set_bits = cs4231_rate_to_bits(drv, &value);
if (set_bits >= 0) {
READ_IDR();
READ_IDR();
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x1c);
tmp_bits = READ_IDR();
WRITE_IDR(CHANGE_DFR(tmp_bits, set_bits));
READ_IDR();
READ_IDR();
CHIP_READY();
cs4231_chip->perchip_info.record.sample_rate = value;
return 0;
}
}
dprintk(("input rate failed\n"));
return -EINVAL;
}
static int cs4231_get_input_rate(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.record.sample_rate;
}
/* Generically we support 4 channels. This hardware does 2 */
static int cs4231_set_input_channels(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp_bits;
tprintk(("input channels %d\n", value));
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x1c);
tmp_bits = READ_IDR();
switch (value) {
case 1:
WRITE_IDR(CS4231_MONO_ON(tmp_bits));
break;
case 2:
WRITE_IDR(CS4231_STEREO_ON(tmp_bits));
break;
default:
dprintk(("input chan failed\n"));
return -EINVAL;
};
CHIP_READY();
cs4231_chip->perchip_info.record.channels = value;
return 0;
}
static int cs4231_get_input_channels(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.record.channels;
}
/* Generically we support 4 channels. This hardware does 2 */
static int cs4231_set_output_channels(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp_bits;
tprintk(("output channels %d\n", value));
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x8);
tmp_bits = READ_IDR();
switch (value) {
case 1:
WRITE_IDR(CS4231_MONO_ON(tmp_bits));
break;
case 2:
WRITE_IDR(CS4231_STEREO_ON(tmp_bits));
break;
default:
dprintk(("output chan failed\n"));
return -EINVAL;
};
CHIP_READY();
cs4231_chip->perchip_info.play.channels = value;
return 0;
}
static int cs4231_get_output_channels(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.play.channels;
}
static int cs4231_get_input_precision(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.record.precision;
}
static int cs4231_get_output_precision(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.play.precision;
}
static int cs4231_set_input_precision(struct sparcaudio_driver *drv, int val)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->perchip_info.record.precision = val;
return cs4231_chip->perchip_info.record.precision;
}
static int cs4231_set_output_precision(struct sparcaudio_driver *drv, int val)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->perchip_info.play.precision = val;
return cs4231_chip->perchip_info.play.precision;
}
/* Wait until the auto calibration process has finished */
static void cs4231_ready(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned int x;
WRITE_IAR(IAR_AUTOCAL_END);
x = 0;
do {
if (READ_IDR() != IAR_NOT_READY)
break;
x++;
} while (x <= CS_TIMEOUT);
WRITE_IAR(0x0b);
x = 0;
do {
if (READ_IDR() != AUTOCAL_IN_PROGRESS)
break;
x++;
} while (x <= CS_TIMEOUT);
}
/* Set output mute */
static int cs4231_output_muted(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
tprintk(("in cs4231_output_muted: %d\n", value));
if (!value) {
WRITE_IAR(0x7);
WRITE_IDR(READ_IDR() & OUTCR_UNMUTE);
WRITE_IAR(0x6);
WRITE_IDR(READ_IDR() & OUTCR_UNMUTE);
cs4231_chip->perchip_info.output_muted = 0;
} else {
WRITE_IAR(0x7);
WRITE_IDR(READ_IDR() | OUTCR_MUTE);
WRITE_IAR(0x6);
WRITE_IDR(READ_IDR() | OUTCR_MUTE);
cs4231_chip->perchip_info.output_muted = 1;
}
return 0;
}
static int cs4231_get_output_muted(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.output_muted;
}
static int cs4231_get_formats(struct sparcaudio_driver *drv)
{
return (AFMT_MU_LAW | AFMT_A_LAW |
AFMT_U8 | AFMT_IMA_ADPCM |
AFMT_S16_LE | AFMT_S16_BE);
}
static int cs4231_get_output_ports(struct sparcaudio_driver *drv)
{
return (AUDIO_LINE_OUT | AUDIO_SPEAKER | AUDIO_HEADPHONE);
}
static int cs4231_get_input_ports(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
/* This apparently applies only to APC ultras, not ebus ultras */
if (cs4231_chip->status & CS_STATUS_IS_ULTRA)
return (AUDIO_LINE_IN | AUDIO_MICROPHONE | AUDIO_ANALOG_LOOPBACK);
else
return (AUDIO_INTERNAL_CD_IN | AUDIO_LINE_IN |
AUDIO_MICROPHONE | AUDIO_ANALOG_LOOPBACK);
}
/* Set chip "output" port */
static int cs4231_set_output_port(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int retval = 0;
tprintk(("output port: %d\n", value));
/* Aaaaaah! It's all coming so fast! Turn it all off, then selectively
* enable things.
*/
WRITE_IAR(0x1a);
WRITE_IDR(READ_IDR() | MONO_IOCR_MUTE);
WRITE_IAR(0x0a);
WRITE_IDR(READ_IDR() | PINCR_LINE_MUTE);
WRITE_IDR(READ_IDR() | PINCR_HDPH_MUTE);
if (value & AUDIO_SPEAKER) {
WRITE_IAR(0x1a);
WRITE_IDR(READ_IDR() & ~MONO_IOCR_MUTE);
retval |= AUDIO_SPEAKER;
}
if (value & AUDIO_HEADPHONE) {
WRITE_IAR(0x0a);
WRITE_IDR(READ_IDR() & ~PINCR_HDPH_MUTE);
retval |= AUDIO_HEADPHONE;
}
if (value & AUDIO_LINE_OUT) {
WRITE_IAR(0x0a);
WRITE_IDR(READ_IDR() & ~PINCR_LINE_MUTE);
retval |= AUDIO_LINE_OUT;
}
cs4231_chip->perchip_info.play.port = retval;
return (retval);
}
static int cs4231_get_output_port(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.play.port;
}
/* Set chip "input" port */
static int cs4231_set_input_port(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int retval = 0;
tprintk(("input port: %d\n", value));
/* You can have one and only one. This is probably wrong, but
* appears to be how SunOS is doing it. Should be able to mix.
* More work to be done. CD input mixable, analog loopback may be.
*/
/* Ultra systems do not support AUDIO_INTERNAL_CD_IN */
/* This apparently applies only to APC ultras, not ebus ultras */
if (!(cs4231_chip->status & CS_STATUS_IS_ULTRA)) {
if (value & AUDIO_INTERNAL_CD_IN) {
WRITE_IAR(0x1);
WRITE_IDR(CDROM_ENABLE(READ_IDR()));
WRITE_IAR(0x0);
WRITE_IDR(CDROM_ENABLE(READ_IDR()));
retval = AUDIO_INTERNAL_CD_IN;
}
}
if ((value & AUDIO_LINE_IN)) {
WRITE_IAR(0x1);
WRITE_IDR(LINE_ENABLE(READ_IDR()));
WRITE_IAR(0x0);
WRITE_IDR(LINE_ENABLE(READ_IDR()));
retval = AUDIO_LINE_IN;
} else if (value & AUDIO_MICROPHONE) {
WRITE_IAR(0x1);
WRITE_IDR(MIC_ENABLE(READ_IDR()));
WRITE_IAR(0x0);
WRITE_IDR(MIC_ENABLE(READ_IDR()));
retval = AUDIO_MICROPHONE;
} else if (value & AUDIO_ANALOG_LOOPBACK) {
WRITE_IAR(0x1);
WRITE_IDR(OUTPUTLOOP_ENABLE(READ_IDR()));
WRITE_IAR(0x0);
WRITE_IDR(OUTPUTLOOP_ENABLE(READ_IDR()));
retval = AUDIO_ANALOG_LOOPBACK;
}
cs4231_chip->perchip_info.record.port = retval;
return retval;
}
static int cs4231_get_input_port(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.record.port;
}
/* Set chip "monitor" gain */
static int cs4231_set_monitor_volume(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int a = 0;
tprintk(("monitor gain: %d\n", value));
/* This interpolation really sucks. The question is, be compatible
* with ScumOS/Sloaris or not?
*/
a = CS4231_MON_MAX_ATEN - (value * (CS4231_MON_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1));
WRITE_IAR(0x0d);
if (a >= CS4231_MON_MAX_ATEN)
WRITE_IDR(LOOPB_OFF);
else
WRITE_IDR((a << 2) | LOOPB_ON);
if (value == AUDIO_MAX_GAIN)
cs4231_chip->perchip_info.monitor_gain = AUDIO_MAX_GAIN;
else
cs4231_chip->perchip_info.monitor_gain =
((CS4231_MAX_DEV_ATEN - a) *
(AUDIO_MAX_GAIN + 1) /
(CS4231_MAX_DEV_ATEN + 1));
return 0;
}
static int cs4231_get_monitor_volume(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.monitor_gain;
}
static int cs4231_get_output_error(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.play.error;
}
static int cs4231_get_input_error(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.record.error;
}
#ifdef EB4231_SUPPORT
static int eb4231_get_output_samples(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dbcr = readl(cs4231_chip->eb2p + EBDMA_COUNT);
int count =
cs4231_length_to_samplecount(&cs4231_chip->perchip_info.play, dbcr);
return (cs4231_chip->perchip_info.play.samples -
((count > cs4231_chip->perchip_info.play.samples)
? 0 : count));
}
static int eb4231_get_input_samples(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dbcr = readl(cs4231_chip->eb2c + EBDMA_COUNT);
int count =
cs4231_length_to_samplecount(&cs4231_chip->perchip_info.record, dbcr);
return (cs4231_chip->perchip_info.record.samples -
((count > cs4231_chip->perchip_info.record.samples) ?
0 : count));
}
#endif
static int cs4231_get_output_samples(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dmapc = sbus_readl(cs4231_chip->regs + APCPC);
int count =
cs4231_length_to_samplecount(&cs4231_chip->perchip_info.play, dmapc);
return (cs4231_chip->perchip_info.play.samples -
((count > cs4231_chip->perchip_info.play.samples)
? 0 : count));
}
static int cs4231_get_input_samples(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dmacc = sbus_readl(cs4231_chip->regs + APCCC);
int count =
cs4231_length_to_samplecount(&cs4231_chip->perchip_info.record, dmacc);
return (cs4231_chip->perchip_info.record.samples -
((count > cs4231_chip->perchip_info.record.samples) ?
0 : count));
}
static int cs4231_get_output_pause(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.play.pause;
}
static int cs4231_get_input_pause(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.record.pause;
}
/* But for play/record we have these cheesy jacket routines because of
* how this crap gets set.
*/
static int cs4231_set_input_volume(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_record_gain(drv, value,
cs4231_chip->perchip_info.record.balance);
return 0;
}
static int cs4231_get_input_volume(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.record.gain;
}
static int cs4231_set_output_volume(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_play_gain(drv, value, cs4231_chip->perchip_info.play.balance);
return 0;
}
static int cs4231_get_output_volume(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return cs4231_chip->perchip_info.play.gain;
}
/* Likewise for balance */
static int cs4231_set_input_balance(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->perchip_info.record.balance = value;
cs4231_record_gain(drv, cs4231_chip->perchip_info.record.gain,
cs4231_chip->perchip_info.record.balance);
return 0;
}
static int cs4231_get_input_balance(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.record.balance;
}
static int cs4231_set_output_balance(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->perchip_info.play.balance = value;
cs4231_play_gain(drv, cs4231_chip->perchip_info.play.gain,
cs4231_chip->perchip_info.play.balance);
return 0;
}
static int cs4231_get_output_balance(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
return (int) cs4231_chip->perchip_info.play.balance;
}
/* Set chip record gain */
static int cs4231_record_gain(struct sparcaudio_driver *drv, int value,
unsigned char balance)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp = 0, r, l, r_adj, l_adj;
unsigned char old_gain;
r = l = value;
if (balance < AUDIO_MID_BALANCE) {
r = (int) (value -
((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT));
if (r < 0)
r = 0;
} else if (balance > AUDIO_MID_BALANCE) {
l = (int) (value -
((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT));
if (l < 0)
l = 0;
}
l_adj = l * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
r_adj = r * (CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
WRITE_IAR(0x0);
old_gain = READ_IDR();
WRITE_IDR(RECGAIN_SET(old_gain, l_adj));
WRITE_IAR(0x1);
old_gain = READ_IDR();
WRITE_IDR(RECGAIN_SET(old_gain, r_adj));
if (l == value) {
(l == 0) ? (tmp = 0) : (tmp = ((l_adj + 1) * AUDIO_MAX_GAIN) /
(CS4231_MAX_GAIN + 1));
} else if (r == value) {
(r == 0) ? (tmp = 0) : (tmp = ((r_adj + 1) * AUDIO_MAX_GAIN) /
(CS4231_MAX_GAIN + 1));
}
cs4231_chip->perchip_info.record.gain = tmp;
return 0;
}
/* Set chip play gain */
static int cs4231_play_gain(struct sparcaudio_driver *drv, int value,
unsigned char balance)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int tmp = 0, r, l, r_adj, l_adj;
unsigned char old_gain;
tprintk(("in play_gain: %d %c\n", value, balance));
r = l = value;
if (balance < AUDIO_MID_BALANCE) {
r = (int) (value -
((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT));
if (r < 0)
r = 0;
} else if (balance > AUDIO_MID_BALANCE) {
l = (int) (value -
((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT));
if (l < 0)
l = 0;
}
(l == 0) ? (l_adj = CS4231_MAX_DEV_ATEN) : (l_adj = CS4231_MAX_ATEN -
(l * (CS4231_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1)));
(r == 0) ? (r_adj = CS4231_MAX_DEV_ATEN) : (r_adj = CS4231_MAX_ATEN -
(r * (CS4231_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1)));
WRITE_IAR(0x6);
old_gain = READ_IDR();
WRITE_IDR(GAIN_SET(old_gain, l_adj));
WRITE_IAR(0x7);
old_gain = READ_IDR();
WRITE_IDR(GAIN_SET(old_gain, r_adj));
if ((value == 0) || (value == AUDIO_MAX_GAIN)) {
tmp = value;
} else {
if (value == l) {
tmp = ((CS4231_MAX_ATEN - l_adj) * (AUDIO_MAX_GAIN + 1) /
(CS4231_MAX_ATEN + 1));
} else if (value == r) {
tmp = ((CS4231_MAX_ATEN - r_adj) * (AUDIO_MAX_GAIN + 1) /
(CS4231_MAX_ATEN + 1));
}
}
cs4231_chip->perchip_info.play.gain = tmp;
return 0;
}
/* Reset the audio chip to a sane state. */
static void cs4231_chip_reset(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
unsigned char vers;
tprintk(("in cs4231_chip_reset\n"));
if (cs4231_chip->status & CS_STATUS_IS_EBUS) {
#ifdef EB4231_SUPPORT
writel(EBUS_DCSR_RESET, cs4231_chip->eb2p + EBDMA_CSR);
writel(EBUS_DCSR_RESET, cs4231_chip->eb2c + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16, cs4231_chip->eb2p + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16, cs4231_chip->eb2c + EBDMA_CSR);
#endif
} else {
u32 tmp;
sbus_writel(APC_CHIP_RESET, cs4231_chip->regs + APCCSR);
sbus_writel(0x00, cs4231_chip->regs + APCCSR);
tmp = sbus_readl(cs4231_chip->regs + APCCSR);
tmp |= APC_CDC_RESET;
sbus_writel(tmp, cs4231_chip->regs + APCCSR);
udelay(20);
tmp = sbus_readl(cs4231_chip->regs + APCCSR);
tmp &= ~(APC_CDC_RESET);
sbus_writel(tmp, cs4231_chip->regs + APCCSR);
}
WRITE_IAR(READ_IAR() | IAR_AUTOCAL_BEGIN);
CHIP_READY();
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x0c);
WRITE_IDR(MISC_IR_MODE2);
/* This is the equivalent of DEFAULT_DATA_FMAT */
cs4231_set_input_encoding(drv, AUDIO_ENCODING_ULAW);
cs4231_set_input_rate(drv, CS4231_RATE);
cs4231_set_input_channels(drv, CS4231_CHANNELS);
cs4231_set_input_precision(drv, CS4231_PRECISION);
cs4231_set_output_encoding(drv, AUDIO_ENCODING_ULAW);
cs4231_set_output_rate(drv, CS4231_RATE);
cs4231_set_output_channels(drv, CS4231_CHANNELS);
cs4231_set_output_precision(drv, CS4231_PRECISION);
WRITE_IAR(0x19);
/* see what we can turn on */
vers = READ_IDR();
if (vers & CS4231A) {
tprintk(("This is a CS4231A\n"));
cs4231_chip->status |= CS_STATUS_REV_A;
} else {
cs4231_chip->status &= ~CS_STATUS_REV_A;
}
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x10);
WRITE_IDR(OLB_ENABLE);
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x11);
if (cs4231_chip->status & CS_STATUS_REV_A)
WRITE_IDR(HPF_ON | XTALE_ON);
else
WRITE_IDR(HPF_ON);
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x1a);
WRITE_IDR(0x00);
/* Now set things up for defaults */
cs4231_set_input_balance(drv, AUDIO_MID_BALANCE);
cs4231_set_output_balance(drv, AUDIO_MID_BALANCE);
cs4231_set_input_volume(drv, CS4231_DEFAULT_RECGAIN);
cs4231_set_output_volume(drv, CS4231_DEFAULT_PLAYGAIN);
cs4231_set_input_port(drv, AUDIO_MICROPHONE);
cs4231_set_output_port(drv, AUDIO_SPEAKER);
cs4231_set_monitor_volume(drv, LOOPB_OFF);
WRITE_IAR(IAR_AUTOCAL_END);
cs4231_ready(drv);
WRITE_IAR(IAR_AUTOCAL_BEGIN | 0x09);
WRITE_IDR(READ_IDR() & ACAL_DISABLE);
WRITE_IAR(IAR_AUTOCAL_END);
cs4231_ready(drv);
cs4231_output_muted(drv, 0);
cs4231_chip->recording_count = 0;
cs4231_chip->input_next_dma_handle = 0;
cs4231_chip->input_dma_handle = 0;
cs4231_chip->input_next_dma_size = 0;
cs4231_chip->input_dma_size = 0;
cs4231_chip->playing_count = 0;
cs4231_chip->output_next_dma_handle = 0;
cs4231_chip->output_dma_handle = 0;
cs4231_chip->output_next_dma_size = 0;
cs4231_chip->output_dma_size = 0;
}
static int
cs4231_length_to_samplecount(struct audio_prinfo *thisdir, unsigned int length)
{
unsigned int count;
if (thisdir->channels == 2)
count = (length / 2);
else
count = length;
if (thisdir->encoding == AUDIO_ENCODING_LINEAR)
count = (count / 2);
else if (thisdir->encoding == AUDIO_ENCODING_DVI)
count = (count / 4);
return count;
}
#ifdef EB4231_SUPPORT
static void eb4231_getsamplecount(struct sparcaudio_driver *drv,
unsigned int length,
unsigned int direction)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
struct audio_prinfo *thisdir;
unsigned int count, curcount, nextcount, dbcr;
if(direction == 1) {
thisdir = &cs4231_chip->perchip_info.record;
dbcr = readl(cs4231_chip->eb2c + EBDMA_COUNT);
nextcount = cs4231_chip->input_next_dma_size;
} else {
thisdir = &cs4231_chip->perchip_info.play;
dbcr = readl(cs4231_chip->eb2p + EBDMA_COUNT);
nextcount = cs4231_chip->output_next_dma_size;
}
curcount = cs4231_length_to_samplecount(thisdir, dbcr);
count = thisdir->samples;
length = cs4231_length_to_samplecount(thisdir, length);
/* normalize for where we are. */
thisdir->samples = ((count - nextcount) + (length - curcount));
}
#endif
static void cs4231_getsamplecount(struct sparcaudio_driver *drv,
unsigned int length,
unsigned int direction)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
struct audio_prinfo *thisdir;
unsigned int count, nextcount, curcount;
u32 tmp;
if (direction == 1) {
/* record */
thisdir = &cs4231_chip->perchip_info.record;
tmp = sbus_readl(cs4231_chip->regs + APCCC);
curcount = cs4231_length_to_samplecount(thisdir, tmp);
tmp = sbus_readl(cs4231_chip->regs + APCCNC);
nextcount = cs4231_length_to_samplecount(thisdir, tmp);
} else {
/* play */
thisdir = &cs4231_chip->perchip_info.play;
tmp = sbus_readl(cs4231_chip->regs + APCPC);
curcount = cs4231_length_to_samplecount(thisdir, tmp);
tmp = sbus_readl(cs4231_chip->regs + APCPNC);
nextcount = cs4231_length_to_samplecount(thisdir, tmp);
}
count = thisdir->samples;
length = cs4231_length_to_samplecount(thisdir, length);
/* normalize for where we are. */
thisdir->samples = ((count - nextcount) + (length - curcount));
}
static int cs4231_open(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
/* Set the default audio parameters if not already in use. */
if (file->f_mode & FMODE_WRITE) {
if (!(drv->flags & SDF_OPEN_WRITE) &&
(cs4231_chip->perchip_info.play.active == 0)) {
cs4231_chip->perchip_info.play.open = 1;
cs4231_chip->perchip_info.play.samples =
cs4231_chip->perchip_info.play.error = 0;
}
}
if (file->f_mode & FMODE_READ) {
if (!(drv->flags & SDF_OPEN_READ) &&
(cs4231_chip->perchip_info.record.active == 0)) {
cs4231_chip->perchip_info.record.open = 1;
cs4231_chip->perchip_info.record.samples =
cs4231_chip->perchip_info.record.error = 0;
}
}
cs4231_ready(drv);
CHIP_READY();
MOD_INC_USE_COUNT;
return 0;
}
static void cs4231_release(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
void (*dma_unmap_single)(struct sbus_dev *, dma_addr_t, size_t, int) = sbus_unmap_single;
#ifdef EB4231_SUPPORT
if (cs4231_chip->status & CS_STATUS_IS_EBUS)
dma_unmap_single = (void (*)(struct sbus_dev *, dma_addr_t, size_t, int)) pci_unmap_single;
#endif
/* zero out any info about what data we have as well */
if (file->f_mode & FMODE_READ) {
/* stop capture here or midlevel? */
cs4231_chip->perchip_info.record.open = 0;
if (cs4231_chip->input_dma_handle) {
dma_unmap_single(drv->dev,
cs4231_chip->input_dma_handle,
cs4231_chip->input_dma_size,
SBUS_DMA_FROMDEVICE);
cs4231_chip->input_dma_handle = 0;
cs4231_chip->input_dma_size = 0;
}
if (cs4231_chip->input_next_dma_handle) {
dma_unmap_single(drv->dev,
cs4231_chip->input_next_dma_handle,
cs4231_chip->input_next_dma_size,
SBUS_DMA_FROMDEVICE);
cs4231_chip->input_next_dma_handle = 0;
cs4231_chip->input_next_dma_size = 0;
}
}
if (file->f_mode & FMODE_WRITE) {
cs4231_chip->perchip_info.play.active =
cs4231_chip->perchip_info.play.open = 0;
if (cs4231_chip->output_dma_handle) {
dma_unmap_single(drv->dev,
cs4231_chip->output_dma_handle,
cs4231_chip->output_dma_size,
SBUS_DMA_TODEVICE);
cs4231_chip->output_dma_handle = 0;
cs4231_chip->output_dma_size = 0;
}
if (cs4231_chip->output_next_dma_handle) {
dma_unmap_single(drv->dev,
cs4231_chip->output_next_dma_handle,
cs4231_chip->output_next_dma_size,
SBUS_DMA_TODEVICE);
cs4231_chip->output_next_dma_handle = 0;
cs4231_chip->output_next_dma_size = 0;
}
}
if (!cs4231_chip->perchip_info.play.open &&
!cs4231_chip->perchip_info.record.open &&
(cs4231_chip->status & CS_STATUS_INIT_ON_CLOSE)) {
cs4231_chip_reset(drv);
cs4231_chip->status &= ~CS_STATUS_INIT_ON_CLOSE;
}
MOD_DEC_USE_COUNT;
}
static void cs4231_playintr(struct sparcaudio_driver *drv, int push)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int status = 0;
if (!push) {
if (!cs4231_chip->perchip_info.play.active) {
sbus_writel(cs4231_chip->output_next_dma_handle,
cs4231_chip->regs + APCPNVA);
sbus_writel(cs4231_chip->output_next_dma_size,
cs4231_chip->regs + APCPNC);
}
sparcaudio_output_done(drv, 0);
return;
}
if (cs4231_chip->playlen == 0 && cs4231_chip->output_size > 0)
cs4231_chip->playlen = cs4231_chip->output_size;
if (cs4231_chip->output_dma_handle) {
sbus_unmap_single(drv->dev,
cs4231_chip->output_dma_handle,
cs4231_chip->output_dma_size,
SBUS_DMA_TODEVICE);
cs4231_chip->output_dma_handle = 0;
cs4231_chip->output_dma_size = 0;
cs4231_chip->playing_count--;
status++;
}
if (cs4231_chip->output_next_dma_handle) {
cs4231_chip->output_dma_handle = cs4231_chip->output_next_dma_handle;
cs4231_chip->output_dma_size = cs4231_chip->output_next_dma_size;
cs4231_chip->output_next_dma_size = 0;
cs4231_chip->output_next_dma_handle = 0;
}
if ((cs4231_chip->output_ptr && cs4231_chip->output_size > 0) &&
!(cs4231_chip->perchip_info.play.pause)) {
cs4231_chip->output_next_dma_handle =
sbus_map_single(drv->dev,
(char *)cs4231_chip->output_ptr,
cs4231_chip->output_size,
SBUS_DMA_TODEVICE);
cs4231_chip->output_next_dma_size = cs4231_chip->output_size;
sbus_writel(cs4231_chip->output_next_dma_handle,
cs4231_chip->regs + APCPNVA);
sbus_writel(cs4231_chip->output_next_dma_size,
cs4231_chip->regs + APCPNC);
cs4231_chip->output_size = 0;
cs4231_chip->output_ptr = NULL;
cs4231_chip->playing_count++;
status += 2;
} else {
sbus_writel(0, cs4231_chip->regs + APCPNVA);
sbus_writel(0, cs4231_chip->regs + APCPNC);
}
sparcaudio_output_done(drv, status);
}
#ifdef EB4231_SUPPORT
static void eb4231_playintr(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int status = 0;
if (cs4231_chip->playlen == 0 && cs4231_chip->output_size > 0)
cs4231_chip->playlen = cs4231_chip->output_size;
if (cs4231_chip->output_dma_handle) {
pci_unmap_single((struct pci_dev *)drv->dev,
cs4231_chip->output_dma_handle,
cs4231_chip->output_dma_size,
PCI_DMA_TODEVICE);
cs4231_chip->output_dma_handle = 0;
cs4231_chip->output_dma_size = 0;
cs4231_chip->playing_count--;
status++;
}
if(cs4231_chip->output_next_dma_handle) {
cs4231_chip->output_dma_handle = cs4231_chip->output_next_dma_handle;
cs4231_chip->output_dma_size = cs4231_chip->output_next_dma_size;
cs4231_chip->output_next_dma_handle = 0;
cs4231_chip->output_next_dma_size = 0;
}
if ((cs4231_chip->output_ptr && cs4231_chip->output_size > 0) &&
!(cs4231_chip->perchip_info.play.pause)) {
cs4231_chip->output_next_dma_handle =
pci_map_single((struct pci_dev *)drv->dev,
(char *)cs4231_chip->output_ptr,
cs4231_chip->output_size,
PCI_DMA_TODEVICE);
cs4231_chip->output_next_dma_size = cs4231_chip->output_size;
writel(cs4231_chip->output_next_dma_size,
cs4231_chip->eb2p + EBDMA_COUNT);
writel(cs4231_chip->output_next_dma_handle,
cs4231_chip->eb2p + EBDMA_ADDR);
cs4231_chip->output_size = 0;
cs4231_chip->output_ptr = NULL;
cs4231_chip->playing_count++;
status += 2;
}
sparcaudio_output_done(drv, status);
}
#endif
static void cs4231_recclear(int fmt, char *dmabuf, int length)
{
switch (fmt) {
case AUDIO_ENCODING_LINEAR:
memset(dmabuf, 0x00, length);
break;
case AUDIO_ENCODING_ALAW:
memset(dmabuf, 0xd5, length);
break;
case AUDIO_ENCODING_ULAW:
memset(dmabuf, 0xff, length);
break;
}
}
static int cs4231_recintr(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int status = 0;
if (cs4231_chip->perchip_info.record.active == 0) {
dprintk(("going inactive\n"));
cs4231_pollinput(drv);
cs4231_disable_rec(drv);
}
if (cs4231_chip->input_dma_handle) {
sbus_unmap_single(drv->dev,
cs4231_chip->input_dma_handle,
cs4231_chip->input_dma_size,
SBUS_DMA_FROMDEVICE);
cs4231_chip->input_dma_handle = 0;
cs4231_chip->input_dma_size = 0;
cs4231_chip->recording_count--;
status++;
}
if (cs4231_chip->input_next_dma_handle) {
cs4231_chip->input_dma_handle = cs4231_chip->input_next_dma_handle;
cs4231_chip->input_dma_size = cs4231_chip->input_next_dma_size;
cs4231_chip->input_next_dma_size = 0;
cs4231_chip->input_next_dma_handle = 0;
}
if ((cs4231_chip->input_ptr && cs4231_chip->input_size > 0) &&
!(cs4231_chip->perchip_info.record.pause)) {
cs4231_recclear(cs4231_chip->perchip_info.record.encoding,
(char *)cs4231_chip->input_ptr,
cs4231_chip->input_size);
cs4231_chip->input_next_dma_handle =
sbus_map_single(drv->dev,
(char *)cs4231_chip->input_ptr,
cs4231_chip->input_size,
SBUS_DMA_FROMDEVICE);
cs4231_chip->input_next_dma_size = cs4231_chip->input_size;
sbus_writel(cs4231_chip->input_next_dma_handle,
cs4231_chip->regs + APCCNVA);
sbus_writel(cs4231_chip->input_next_dma_size,
cs4231_chip->regs + APCCNC);
cs4231_chip->input_size = 0;
cs4231_chip->input_ptr = NULL;
cs4231_chip->recording_count++;
status += 2;
} else {
sbus_writel(0, cs4231_chip->regs + APCCNVA);
sbus_writel(0, cs4231_chip->regs + APCCNC);
}
sparcaudio_input_done(drv, status);
return 1;
}
#ifdef EB4231_SUPPORT
static int eb4231_recintr(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int status = 0;
if (cs4231_chip->perchip_info.record.active == 0) {
dprintk(("going inactive\n"));
eb4231_pollinput(drv);
cs4231_disable_rec(drv);
}
if (cs4231_chip->input_dma_handle) {
pci_unmap_single((struct pci_dev *)drv->dev,
cs4231_chip->input_dma_handle,
cs4231_chip->input_dma_size,
PCI_DMA_FROMDEVICE);
cs4231_chip->input_dma_handle = 0;
cs4231_chip->input_dma_size = 0;
cs4231_chip->recording_count--;
status++;
}
if (cs4231_chip->input_next_dma_handle) {
cs4231_chip->input_dma_handle = cs4231_chip->input_next_dma_handle;
cs4231_chip->input_dma_size = cs4231_chip->input_next_dma_size;
cs4231_chip->input_next_dma_size = 0;
cs4231_chip->input_next_dma_handle = 0;
}
if ((cs4231_chip->input_ptr && cs4231_chip->input_size > 0) &&
!(cs4231_chip->perchip_info.record.pause)) {
cs4231_recclear(cs4231_chip->perchip_info.record.encoding,
(char *)cs4231_chip->input_ptr,
cs4231_chip->input_size);
cs4231_chip->input_next_dma_handle =
pci_map_single((struct pci_dev *)drv->dev,
(char *)cs4231_chip->input_ptr,
cs4231_chip->input_size,
PCI_DMA_FROMDEVICE);
cs4231_chip->input_next_dma_size = cs4231_chip->input_size;
writel(cs4231_chip->input_next_dma_size,
cs4231_chip->eb2c + EBDMA_COUNT);
writel(cs4231_chip->input_next_dma_handle,
cs4231_chip->eb2c + EBDMA_ADDR);
cs4231_chip->input_size = 0;
cs4231_chip->input_ptr = NULL;
cs4231_chip->recording_count++;
status += 2;
}
sparcaudio_input_done(drv, status);
return 1;
}
#endif
#ifdef EB4231_SUPPORT
static void eb4231_start_output(struct sparcaudio_driver *drv, __u8 * buffer,
unsigned long count)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dcsr;
cs4231_chip->output_ptr = buffer;
cs4231_chip->output_size = count;
if (cs4231_chip->perchip_info.play.active ||
(cs4231_chip->perchip_info.play.pause))
return;
cs4231_ready(drv);
cs4231_chip->perchip_info.play.active = 1;
cs4231_chip->playing_count = 0;
dcsr = readl(cs4231_chip->eb2p + EBDMA_CSR);
if (!(dcsr & EBUS_DCSR_EN_DMA)) {
writel(EBUS_DCSR_RESET, cs4231_chip->eb2p + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16, cs4231_chip->eb2p + EBDMA_CSR);
eb4231_playintr(drv);
writel(EBUS_DCSR_BURST_SZ_16 |
(EBUS_DCSR_EN_DMA | EBUS_DCSR_INT_EN |
EBUS_DCSR_EN_CNT | EBUS_DCSR_EN_NEXT),
cs4231_chip->eb2p + EBDMA_CSR);
cs4231_enable_play(drv);
cs4231_ready(drv);
} else {
eb4231_playintr(drv);
}
}
#endif
static void cs4231_start_output(struct sparcaudio_driver *drv, __u8 * buffer,
unsigned long count)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 csr;
tprintk(("in 4231 start output\n"));
cs4231_chip->output_ptr = buffer;
cs4231_chip->output_size = count;
if (cs4231_chip->perchip_info.play.active ||
(cs4231_chip->perchip_info.play.pause))
return;
cs4231_ready(drv);
cs4231_chip->perchip_info.play.active = 1;
cs4231_chip->playing_count = 0;
csr = sbus_readl(cs4231_chip->regs + APCCSR);
if ((csr & APC_PPAUSE) || !(csr & APC_PDMA_READY)) {
u32 pnva;
csr &= ~APC_XINT_PLAY;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
csr &= ~APC_PPAUSE;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
pnva = sbus_readl(cs4231_chip->regs + APCPNVA);
cs4231_playintr(drv, (pnva == 0) ? 1 : 0);
csr |= APC_PLAY_SETUP;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
cs4231_enable_play(drv);
cs4231_ready(drv);
}
}
#ifdef EB4231_SUPPORT
static void eb4231_stop_output(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dcsr;
dprintk(("eb4231_stop_output: dcsr 0x%x dacr 0x%x dbcr %d\n",
readl(cs4231_chip->eb2p + EBDMA_CSR),
readl(cs4231_chip->eb2p + EBDMA_ADDR),
readl(cs4231_chip->eb2p + EBDMA_COUNT)));
cs4231_chip->output_ptr = NULL;
cs4231_chip->output_size = 0;
if (cs4231_chip->output_dma_handle) {
pci_unmap_single((struct pci_dev *)drv->dev,
cs4231_chip->output_dma_handle,
cs4231_chip->output_dma_size,
PCI_DMA_TODEVICE);
cs4231_chip->output_dma_handle = 0;
cs4231_chip->output_dma_size = 0;
}
if (cs4231_chip->output_next_dma_handle) {
pci_unmap_single((struct pci_dev *)drv->dev,
cs4231_chip->output_next_dma_handle,
cs4231_chip->output_next_dma_size,
PCI_DMA_TODEVICE);
cs4231_chip->output_next_dma_handle = 0;
cs4231_chip->output_next_dma_size = 0;
}
dcsr = readl(cs4231_chip->eb2p + EBDMA_CSR);
if(dcsr & EBUS_DCSR_EN_DMA)
writel(dcsr & ~EBUS_DCSR_EN_DMA,
cs4231_chip->eb2p + EBDMA_CSR);
/* Else subsequent speed setting changes are ignored by the chip. */
cs4231_disable_play(drv);
}
#endif
static void cs4231_stop_output(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
tprintk(("in cs4231_stop_output\n"));
cs4231_chip->output_ptr = NULL;
cs4231_chip->output_size = 0;
if (cs4231_chip->output_dma_handle) {
sbus_unmap_single(drv->dev,
cs4231_chip->output_dma_handle,
cs4231_chip->output_dma_size,
SBUS_DMA_TODEVICE);
cs4231_chip->output_dma_handle = 0;
cs4231_chip->output_dma_size = 0;
}
if (cs4231_chip->output_next_dma_handle) {
sbus_unmap_single(drv->dev,
cs4231_chip->output_next_dma_handle,
cs4231_chip->output_next_dma_size,
SBUS_DMA_TODEVICE);
cs4231_chip->output_next_dma_handle = 0;
cs4231_chip->output_next_dma_size = 0;
}
#if 0 /* Not safe without shutting off the DMA controller as well. -DaveM */
/* Else subsequent speed setting changes are ignored by the chip. */
cs4231_disable_play(drv);
#endif
}
#ifdef EB4231_SUPPORT
static void eb4231_pollinput(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int x;
u32 dcsr;
x = 0;
do {
dcsr = readl(cs4231_chip->eb2c + EBDMA_CSR);
if (dcsr & EBUS_DCSR_TC)
break;
x++;
} while (x <= CS_TIMEOUT);
writel(dcsr | EBUS_DCSR_TC,
cs4231_chip->eb2c + EBDMA_CSR);
}
#endif
static void cs4231_pollinput(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int x;
u32 csr;
x = 0;
do {
csr = sbus_readl(cs4231_chip->regs + APCCSR);
if (csr & APC_XINT_COVF)
break;
x++;
} while (x <= CS_TIMEOUT);
sbus_writel(csr | APC_XINT_CEMP,
cs4231_chip->regs + APCCSR);
}
static void cs4231_start_input(struct sparcaudio_driver *drv, __u8 * buffer,
unsigned long count)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 csr;
cs4231_chip->input_ptr = buffer;
cs4231_chip->input_size = count;
if (cs4231_chip->perchip_info.record.active ||
(cs4231_chip->perchip_info.record.pause))
return;
cs4231_ready(drv);
cs4231_chip->perchip_info.record.active = 1;
cs4231_chip->recording_count = 0;
csr = sbus_readl(cs4231_chip->regs + APCCSR);
if ((csr & APC_CPAUSE) || !(csr & APC_CDMA_READY)) {
csr &= ~APC_XINT_CAPT;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
csr &= ~APC_CPAUSE;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
cs4231_recintr(drv);
csr |= APC_CAPT_SETUP;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
cs4231_enable_rec(drv);
cs4231_ready(drv);
} else {
cs4231_recintr(drv);
}
}
static void cs4231_stop_input(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 csr;
cs4231_chip->perchip_info.record.active = 0;
csr = sbus_readl(cs4231_chip->regs + APCCSR);
csr |= APC_CPAUSE;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
cs4231_chip->input_ptr = NULL;
cs4231_chip->input_size = 0;
if (cs4231_chip->input_dma_handle) {
sbus_unmap_single(drv->dev,
cs4231_chip->input_dma_handle,
cs4231_chip->input_dma_size,
SBUS_DMA_FROMDEVICE);
cs4231_chip->input_dma_handle = 0;
cs4231_chip->input_dma_size = 0;
}
if (cs4231_chip->input_next_dma_handle) {
sbus_unmap_single(drv->dev,
cs4231_chip->input_next_dma_handle,
cs4231_chip->input_next_dma_size,
SBUS_DMA_FROMDEVICE);
cs4231_chip->input_next_dma_handle = 0;
cs4231_chip->input_next_dma_size = 0;
}
cs4231_pollinput(drv);
}
#ifdef EB4231_SUPPORT
static void eb4231_start_input(struct sparcaudio_driver *drv, __u8 * buffer,
unsigned long count)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dcsr;
cs4231_chip->input_ptr = buffer;
cs4231_chip->input_size = count;
if (cs4231_chip->perchip_info.record.active ||
(cs4231_chip->perchip_info.record.pause))
return;
cs4231_ready(drv);
cs4231_chip->perchip_info.record.active = 1;
cs4231_chip->recording_count = 0;
dcsr = readl(cs4231_chip->eb2c + EBDMA_CSR);
if (!(dcsr & EBUS_DCSR_EN_DMA)) {
writel(EBUS_DCSR_RESET, cs4231_chip->eb2c + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16, cs4231_chip->eb2c + EBDMA_CSR);
eb4231_recintr(drv);
writel(EBUS_DCSR_BURST_SZ_16 |
(EBUS_DCSR_EN_DMA | EBUS_DCSR_INT_EN |
EBUS_DCSR_EN_CNT | EBUS_DCSR_EN_NEXT),
cs4231_chip->eb2c + EBDMA_CSR);
cs4231_enable_rec(drv);
cs4231_ready(drv);
} else {
eb4231_recintr(drv);
}
}
static void eb4231_stop_input(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dcsr;
cs4231_chip->perchip_info.record.active = 0;
cs4231_chip->input_ptr = NULL;
cs4231_chip->input_size = 0;
if (cs4231_chip->input_dma_handle) {
pci_unmap_single((struct pci_dev *)drv->dev,
cs4231_chip->input_dma_handle,
cs4231_chip->input_dma_size,
PCI_DMA_FROMDEVICE);
cs4231_chip->input_dma_handle = 0;
cs4231_chip->input_dma_size = 0;
}
if (cs4231_chip->input_next_dma_handle) {
pci_unmap_single((struct pci_dev *)drv->dev,
cs4231_chip->input_next_dma_handle,
cs4231_chip->input_next_dma_size,
PCI_DMA_FROMDEVICE);
cs4231_chip->input_next_dma_handle = 0;
cs4231_chip->input_next_dma_size = 0;
}
dcsr = readl(cs4231_chip->eb2c + EBDMA_CSR);
if (dcsr & EBUS_DCSR_EN_DMA)
writel(dcsr & ~EBUS_DCSR_EN_DMA, cs4231_chip->eb2c + EBDMA_CSR);
cs4231_disable_rec(drv);
}
#endif
static int cs4231_set_output_pause(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->perchip_info.play.pause = value;
if (!value)
sparcaudio_output_done(drv, 0);
return value;
}
static int cs4231_set_output_error(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int i;
i = cs4231_chip->perchip_info.play.error;
cs4231_chip->perchip_info.play.error = value;
return i;
}
static int cs4231_set_input_error(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int i;
i = cs4231_chip->perchip_info.record.error;
cs4231_chip->perchip_info.record.error = value;
return i;
}
static int cs4231_set_output_samples(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *)drv->private;
int i;
i = cs4231_chip->perchip_info.play.samples;
cs4231_chip->perchip_info.play.samples = value;
return i;
}
static int cs4231_set_input_samples(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int i;
i = cs4231_chip->perchip_info.record.samples;
cs4231_chip->perchip_info.record.samples = value;
return i;
}
static int cs4231_set_input_pause(struct sparcaudio_driver *drv, int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->perchip_info.record.pause = value;
if (value)
cs4231_stop_input(drv);
return value;
}
static void cs4231_audio_getdev(struct sparcaudio_driver *drv,
audio_device_t * audinfo)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
strncpy(audinfo->name, "SUNW,CS4231", sizeof(audinfo->name) - 1);
/* versions */
/* a: SPARCstation 4/5 b: Ultra 1/2 (electron) */
/* c: Ultra 1/2 PCI? (positron) d: ppc */
/* e: x86 f: Ultra Enterprise? (tazmo) */
/* g: Ultra 30? (quark) h: Ultra 5/10? (darwin) */
/* apparently Ultra 1, Ultra 2 don't have internal CD input */
if (cs4231_chip->status & CS_STATUS_IS_ULTRA)
strncpy(audinfo->version, "b", sizeof(audinfo->version) - 1);
else
strncpy(audinfo->version, "a", sizeof(audinfo->version) - 1);
strncpy(audinfo->config, "onboard1", sizeof(audinfo->config) - 1);
}
static int cs4231_audio_getdev_sunos(struct sparcaudio_driver *drv)
{
return AUDIO_DEV_CS4231;
}
static void cs4231_loopback(struct sparcaudio_driver *drv, unsigned int value)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
WRITE_IAR(0x0d);
WRITE_IDR(value ? LOOPB_ON : 0);
}
static int cs4231_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg,
struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
int retval = 0;
switch (cmd) {
case AUDIO_DIAG_LOOPBACK:
cs4231_chip->status |= CS_STATUS_INIT_ON_CLOSE;
cs4231_loopback(drv, (unsigned int)arg);
break;
default:
retval = -EINVAL;
};
return retval;
}
#ifdef EB4231_SUPPORT
/* ebus audio capture interrupt handler. */
void eb4231_cinterrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct sparcaudio_driver *drv = (struct sparcaudio_driver *) dev_id;
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dummy;
/* Clear the interrupt. */
dummy = readl(cs4231_chip->eb2c + EBDMA_CSR);
writel(dummy, cs4231_chip->eb2c + EBDMA_CSR);
if ((dummy & EBUS_DCSR_TC) != 0
/*&& (dummy & EBUS_DCSR_A_LOADED) != 0*/) {
cs4231_chip->perchip_info.record.samples +=
cs4231_length_to_samplecount(&(cs4231_chip->perchip_info.record),
cs4231_chip->reclen);
eb4231_recintr(drv);
}
if ((dummy & EBUS_DCSR_A_LOADED) == 0) {
cs4231_chip->perchip_info.record.active = 0;
eb4231_recintr(drv);
eb4231_getsamplecount(drv, cs4231_chip->reclen, 1);
}
}
/* ebus audio play interrupt handler. */
void eb4231_pinterrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct sparcaudio_driver *drv = (struct sparcaudio_driver *) dev_id;
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
u32 dummy;
/* Clear the interrupt. Bleh, when not using the next-address
* feature, TC can only be cleared by a reset.
*/
dummy = readl(cs4231_chip->eb2p + EBDMA_CSR);
writel(dummy, cs4231_chip->eb2p + EBDMA_CSR);
/* If we get a terminal count and address loaded condition,
* this means the DNAR was copied into DACR.
*/
if((dummy & EBUS_DCSR_TC) != 0
/*&& (dummy & EBUS_DCSR_A_LOADED) != 0*/) {
cs4231_chip->perchip_info.play.samples +=
cs4231_length_to_samplecount(&(cs4231_chip->perchip_info.play),
cs4231_chip->playlen);
eb4231_playintr(drv);
}
if((dummy & EBUS_DCSR_A_LOADED) == 0) {
cs4231_chip->perchip_info.play.active = 0;
eb4231_playintr(drv);
eb4231_getsamplecount(drv, cs4231_chip->playlen, 0);
}
}
#endif
/* Audio interrupt handler. */
void cs4231_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct sparcaudio_driver *drv = (struct sparcaudio_driver *) dev_id;
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
__u32 dummy;
dprintk(("in cs4231_interrupt\n"));
/* Clear the interrupt. */
dummy = sbus_readl(cs4231_chip->regs + APCCSR);
sbus_writel(dummy, cs4231_chip->regs + APCCSR);
/* now go through and figure out what gets to claim the interrupt
* if anything since we may be doing shared interrupts
*/
if (dummy & APC_PLAY_INT) {
if (dummy & APC_XINT_PNVA) {
cs4231_chip->perchip_info.play.samples +=
cs4231_length_to_samplecount(&(cs4231_chip->perchip_info.play),
cs4231_chip->playlen);
if (!(dummy & APC_XINT_EMPT))
cs4231_playintr(drv, 1);
}
/* Any other conditions we need worry about? */
}
if (dummy & APC_CAPT_INT) {
if (dummy & APC_XINT_CNVA) {
cs4231_chip->perchip_info.record.samples +=
cs4231_length_to_samplecount(&(cs4231_chip->perchip_info.record),
cs4231_chip->reclen);
cs4231_recintr(drv);
}
/* Any other conditions we need worry about? */
}
if (dummy & APC_XINT_CEMP) {
if (cs4231_chip->perchip_info.record.active == 0) {
/* Fix me */
cs4231_chip->perchip_info.record.active = 0;
cs4231_chip->perchip_info.record.error = 1;
cs4231_recintr(drv);
}
}
if (dummy & APC_XINT_EMPT) {
if (!cs4231_chip->output_next_dma_handle) {
u32 csr = sbus_readl(cs4231_chip->regs + APCCSR);
csr |= APC_PPAUSE;
sbus_writel(csr, cs4231_chip->regs + APCCSR);
cs4231_disable_play(drv);
cs4231_chip->perchip_info.play.error = 1;
}
cs4231_chip->perchip_info.play.active = 0;
cs4231_playintr(drv, 0);
cs4231_getsamplecount(drv, cs4231_chip->playlen, 0);
}
if (dummy & APC_GENL_INT) {
/* If we get here we must be sharing an interrupt, but I haven't code
* to handle this right now.
*/
}
}
static struct sparcaudio_operations cs4231_ops = {
cs4231_open,
cs4231_release,
cs4231_ioctl,
cs4231_start_output,
cs4231_stop_output,
cs4231_start_input,
cs4231_stop_input,
cs4231_audio_getdev,
cs4231_set_output_volume,
cs4231_get_output_volume,
cs4231_set_input_volume,
cs4231_get_input_volume,
cs4231_set_monitor_volume,
cs4231_get_monitor_volume,
cs4231_set_output_balance,
cs4231_get_output_balance,
cs4231_set_input_balance,
cs4231_get_input_balance,
cs4231_set_output_channels,
cs4231_get_output_channels,
cs4231_set_input_channels,
cs4231_get_input_channels,
cs4231_set_output_precision,
cs4231_get_output_precision,
cs4231_set_input_precision,
cs4231_get_input_precision,
cs4231_set_output_port,
cs4231_get_output_port,
cs4231_set_input_port,
cs4231_get_input_port,
cs4231_set_output_encoding,
cs4231_get_output_encoding,
cs4231_set_input_encoding,
cs4231_get_input_encoding,
cs4231_set_output_rate,
cs4231_get_output_rate,
cs4231_set_input_rate,
cs4231_get_input_rate,
cs4231_audio_getdev_sunos,
cs4231_get_output_ports,
cs4231_get_input_ports,
cs4231_output_muted,
cs4231_get_output_muted,
cs4231_set_output_pause,
cs4231_get_output_pause,
cs4231_set_input_pause,
cs4231_get_input_pause,
cs4231_set_output_samples,
cs4231_get_output_samples,
cs4231_set_input_samples,
cs4231_get_input_samples,
cs4231_set_output_error,
cs4231_get_output_error,
cs4231_set_input_error,
cs4231_get_input_error,
cs4231_get_formats,
};
#ifdef EB4231_SUPPORT
static struct sparcaudio_operations eb4231_ops = {
cs4231_open,
cs4231_release,
cs4231_ioctl,
eb4231_start_output,
eb4231_stop_output,
eb4231_start_input,
eb4231_stop_input,
cs4231_audio_getdev,
cs4231_set_output_volume,
cs4231_get_output_volume,
cs4231_set_input_volume,
cs4231_get_input_volume,
cs4231_set_monitor_volume,
cs4231_get_monitor_volume,
cs4231_set_output_balance,
cs4231_get_output_balance,
cs4231_set_input_balance,
cs4231_get_input_balance,
cs4231_set_output_channels,
cs4231_get_output_channels,
cs4231_set_input_channels,
cs4231_get_input_channels,
cs4231_set_output_precision,
cs4231_get_output_precision,
cs4231_set_input_precision,
cs4231_get_input_precision,
cs4231_set_output_port,
cs4231_get_output_port,
cs4231_set_input_port,
cs4231_get_input_port,
cs4231_set_output_encoding,
cs4231_get_output_encoding,
cs4231_set_input_encoding,
cs4231_get_input_encoding,
cs4231_set_output_rate,
cs4231_get_output_rate,
cs4231_set_input_rate,
cs4231_get_input_rate,
cs4231_audio_getdev_sunos,
cs4231_get_output_ports,
cs4231_get_input_ports,
cs4231_output_muted,
cs4231_get_output_muted,
cs4231_set_output_pause,
cs4231_get_output_pause,
cs4231_set_input_pause,
cs4231_get_input_pause,
cs4231_set_output_samples,
eb4231_get_output_samples,
cs4231_set_input_samples,
eb4231_get_input_samples,
cs4231_set_output_error,
cs4231_get_output_error,
cs4231_set_input_error,
cs4231_get_input_error,
cs4231_get_formats,
};
#endif
/* Attach to an cs4231 chip given its PROM node. */
static int cs4231_attach(struct sparcaudio_driver *drv,
struct sbus_dev *sdev)
{
struct cs4231_chip *cs4231_chip;
int err;
/* Allocate our private information structure. */
drv->private = kmalloc(sizeof(struct cs4231_chip), GFP_KERNEL);
if (drv->private == NULL)
return -ENOMEM;
/* Point at the information structure and initialize it. */
drv->ops = &cs4231_ops;
cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->input_ptr = cs4231_chip->output_ptr = NULL;
cs4231_chip->input_size = cs4231_chip->output_size = 0;
cs4231_chip->status = 0;
drv->dev = sdev;
/* Map the registers into memory. */
cs4231_chip->regs_size = sdev->reg_addrs[0].reg_size;
cs4231_chip->regs = sbus_ioremap(&sdev->resource[0], 0,
sdev->reg_addrs[0].reg_size,
"cs4231");
if (!cs4231_chip->regs) {
printk(KERN_ERR "cs4231: could not remap registers\n");
kfree(drv->private);
return -EIO;
}
/* Attach the interrupt handler to the audio interrupt. */
cs4231_chip->irq = sdev->irqs[0];
request_irq(cs4231_chip->irq, cs4231_interrupt, SA_SHIRQ, "cs4231", drv);
cs4231_chip->nirqs = 1;
cs4231_enable_interrupts(drv);
/* Reset the audio chip. */
cs4231_chip_reset(drv);
/* Register ourselves with the midlevel audio driver. */
err = register_sparcaudio_driver(drv, 1);
if (err < 0) {
printk(KERN_ERR "cs4231: unable to register\n");
cs4231_disable_interrupts(drv);
free_irq(cs4231_chip->irq, drv);
sbus_iounmap(cs4231_chip->regs, cs4231_chip->regs_size);
kfree(drv->private);
return -EIO;
}
cs4231_chip->perchip_info.play.active =
cs4231_chip->perchip_info.play.pause = 0;
cs4231_chip->perchip_info.record.active =
cs4231_chip->perchip_info.record.pause = 0;
cs4231_chip->perchip_info.play.avail_ports = (AUDIO_HEADPHONE |
AUDIO_SPEAKER |
AUDIO_LINE_OUT);
cs4231_chip->perchip_info.record.avail_ports = (AUDIO_INTERNAL_CD_IN |
AUDIO_LINE_IN |
AUDIO_MICROPHONE |
AUDIO_ANALOG_LOOPBACK);
/* Announce the hardware to the user. */
printk(KERN_INFO "audio%d: cs4231%c at %lx irq %s\n",
drv->index, (cs4231_chip->status & CS_STATUS_REV_A) ? 'a' : ' ',
cs4231_chip->regs, __irq_itoa(cs4231_chip->irq));
/* Success! */
return 0;
}
#ifdef EB4231_SUPPORT
/* Attach to an cs4231 chip given its PROM node. */
static int eb4231_attach(struct sparcaudio_driver *drv,
struct linux_ebus_device *edev)
{
struct cs4231_chip *cs4231_chip;
int len, err, nregs;
struct linux_prom_registers regs[4];
/* Allocate our private information structure. */
drv->private = kmalloc(sizeof(struct cs4231_chip), GFP_KERNEL);
if (drv->private == NULL)
return -ENOMEM;
/* Point at the information structure and initialize it. */
drv->ops = &eb4231_ops;
cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_chip->input_ptr = cs4231_chip->output_ptr = NULL;
cs4231_chip->input_size = cs4231_chip->output_size = 0;
cs4231_chip->status = 0;
drv->dev = (struct sbus_dev *)edev->bus->self;
len = prom_getproperty(edev->prom_node, "reg", (void *)regs, sizeof(regs));
if ((len % sizeof(regs[0])) != 0) {
printk("eb4231: Strange reg property size %d\n", len);
return -ENODEV;
}
nregs = len / sizeof(regs[0]);
cs4231_chip->regs = (unsigned long)ioremap(edev->resource[0].start, 0x10);
cs4231_chip->eb2p = (unsigned long)ioremap(edev->resource[1].start, 0x10);
cs4231_chip->eb2c = (unsigned long)ioremap(edev->resource[2].start, 0x10);
cs4231_chip->status |= CS_STATUS_IS_EBUS;
/* Attach the interrupt handler to the audio interrupt. */
cs4231_chip->irq = edev->irqs[0];
cs4231_chip->irq2 = edev->irqs[1];
if(request_irq(cs4231_chip->irq, eb4231_cinterrupt, SA_SHIRQ, "cs4231", drv) ||
request_irq(cs4231_chip->irq2, eb4231_pinterrupt, SA_SHIRQ, "cs4231", drv))
goto bail;
cs4231_chip->nirqs = 2;
cs4231_enable_interrupts(drv);
/* Reset the audio chip. */
cs4231_chip_reset(drv);
/* Register ourselves with the midlevel audio driver. */
err = register_sparcaudio_driver(drv, 1);
if (err < 0) {
bail:
printk(KERN_ERR "cs4231: unable to register\n");
cs4231_disable_interrupts(drv);
free_irq(cs4231_chip->irq, drv);
free_irq(cs4231_chip->irq2, drv);
kfree(drv->private);
return -EIO;
}
cs4231_chip->perchip_info.play.active =
cs4231_chip->perchip_info.play.pause = 0;
cs4231_chip->perchip_info.record.active =
cs4231_chip->perchip_info.record.pause = 0;
cs4231_chip->perchip_info.play.avail_ports = (AUDIO_HEADPHONE |
AUDIO_SPEAKER |
AUDIO_LINE_OUT);
cs4231_chip->perchip_info.record.avail_ports = (AUDIO_INTERNAL_CD_IN |
AUDIO_LINE_IN |
AUDIO_MICROPHONE |
AUDIO_ANALOG_LOOPBACK);
/* Announce the hardware to the user. */
printk(KERN_INFO "audio%d: cs4231%c(eb2) at %lx irq %s\n",
drv->index, (cs4231_chip->status & CS_STATUS_REV_A) ? 'a' : ' ',
cs4231_chip->regs, __irq_itoa(cs4231_chip->irq));
/* Success! */
return 0;
}
#endif
#ifdef EB4231_SUPPORT
static int __init ebus_cs4231_p(struct linux_ebus_device *edev)
{
if (!strcmp(edev->prom_name, "SUNW,CS4231"))
return 1;
if (!strcmp(edev->prom_name, "audio")) {
char compat[16];
prom_getstring(edev->prom_node, "compatible",
compat, sizeof(compat));
compat[15] = '\0';
if (!strcmp(compat, "SUNW,CS4231"))
return 1;
}
return 0;
}
#endif
/* Detach from an cs4231 chip given the device structure. */
static void __exit cs4231_detach(struct sparcaudio_driver *drv)
{
struct cs4231_chip *cs4231_chip = (struct cs4231_chip *) drv->private;
cs4231_disable_interrupts(drv);
unregister_sparcaudio_driver(drv, 1);
free_irq(cs4231_chip->irq, drv);
if (!(cs4231_chip->status & CS_STATUS_IS_EBUS)) {
sbus_iounmap(cs4231_chip->regs, cs4231_chip->regs_size);
} else {
#ifdef EB4231_SUPPORT
iounmap(cs4231_chip->regs);
iounmap(cs4231_chip->eb2p);
iounmap(cs4231_chip->eb2c);
free_irq(cs4231_chip->irq2, drv);
#endif
}
kfree(drv->private);
}
/* Probe for the cs4231 chip and then attach the driver. */
static int __init cs4231_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
#ifdef EB4231_SUPPORT
struct linux_ebus *ebus;
struct linux_ebus_device *edev;
#endif
num_drivers = 0;
/* Probe each SBUS for cs4231 chips. */
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "SUNW,CS4231")) {
/* Don't go over the max number of drivers. */
if (num_drivers >= MAX_DRIVERS)
continue;
if (cs4231_attach(&drivers[num_drivers], sdev) == 0)
num_drivers++;
}
}
#ifdef EB4231_SUPPORT
for_each_ebus(ebus) {
for_each_ebusdev(edev, ebus) {
if (ebus_cs4231_p(edev)) {
/* Don't go over the max number of drivers. */
if (num_drivers >= MAX_DRIVERS)
continue;
if (eb4231_attach(&drivers[num_drivers], edev) == 0)
num_drivers++;
}
}
}
#endif
/* Only return success if we found some cs4231 chips. */
return (num_drivers > 0) ? 0 : -EIO;
}
static void __exit cs4231_exit(void)
{
register int i;
for (i = 0; i < num_drivers; i++) {
cs4231_detach(&drivers[i]);
num_drivers--;
}
}
module_init(cs4231_init);
module_exit(cs4231_exit);
MODULE_LICENSE("GPL");
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: nil
* tab-width: 8
* End:
*/
/* $Id: cs4231.h,v 1.13 1999/09/21 14:37:27 davem Exp $
* drivers/sbus/audio/cs4231.h
*
* Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
* Copyright (C) 1997 Derrick J. Brashear (shadow@dementia.org)
* Copyright (C) 1999 David S. Miller (davem@redhat.com)
*/
#ifndef _CS4231_H_
#define _CS4231_H_
#include <linux/types.h>
/* According to the CS4231A data provided on CS web site and sun's includes */
#define IAR 0x00UL /* Index Address Register */
#define IDR 0x04UL /* Index Data Register */
#define STAT 0x08UL /* Status Register */
#define PIOD 0x0cUL /* PIO Data Register */
#define APCCSR 0x10UL /* APC DMA CSR */
#define APCCVA 0x20UL /* APC Capture DMA Address */
#define APCCC 0x24UL /* APC Capture Count */
#define APCCNVA 0x28UL /* APC Capture DMA Next Address */
#define APCCNC 0x2cUL /* APC Capture Next Count */
#define APCPVA 0x30UL /* APC Play DMA Address */
#define APCPC 0x34UL /* APC Play Count */
#define APCPNVA 0x38UL /* APC Play DMA Next Address */
#define APCPNC 0x3cUL /* APC Play Next Count */
/* EBUS DMA Registers */
#define EBDMA_CSR 0x00UL /* Control/Status */
#define EBDMA_ADDR 0x04UL /* DMA Address */
#define EBDMA_COUNT 0x08UL /* DMA Count */
/* Our structure for each chip */
struct cs4231_chip {
unsigned long regs;
unsigned long eb2c;
unsigned long eb2p;
struct audio_info perchip_info;
unsigned int playlen, reclen;
int irq, irq2, nirqs;
unsigned long regs_size;
/* Keep track of various info */
volatile unsigned int status;
/* Current buffer that the driver is playing. */
volatile __u8 * output_ptr;
volatile __u32 output_size;
volatile __u32 output_dma_handle, output_next_dma_handle;
volatile __u32 output_dma_size, output_next_dma_size;
/* Current record buffer. */
volatile __u8 * input_ptr;
volatile __u32 input_size;
volatile __u32 input_dma_handle, input_next_dma_handle;
volatile __u32 input_dma_size, input_next_dma_size;
/* Number of buffers in the pipe. */
volatile __u32 playing_count;
volatile __u32 recording_count;
};
#ifdef EB4231_SUPPORT
#define CS4231_READ32(__C, __REG) \
(((__C)->status & CS_STATUS_IS_EBUS) ? \
readl((__REG)) : \
sbus_readl((__REG)))
#define CS4231_READ8(__C, __REG) \
(((__C)->status & CS_STATUS_IS_EBUS) ? \
readb((__REG)) : \
sbus_readb((__REG)))
#define CS4231_WRITE32(__C, __REG, __VAL) \
(((__C)->status & CS_STATUS_IS_EBUS) ? \
writel((__VAL), (__REG)) : \
sbus_writel((__VAL), (__REG)))
#define CS4231_WRITE8(__C, __REG, __VAL) \
(((__C)->status & CS_STATUS_IS_EBUS) ? \
writeb((__VAL), (__REG)) : \
sbus_writeb((__VAL), (__REG)))
#else
/* We can assume all is SBUS in this case. */
#define CS4231_READ32(__C, __REG) sbus_readl((__REG))
#define CS4231_READ8(__C, __REG) sbus_readb((__REG))
#define CS4231_WRITE32(__C, __REG, __VAL) sbus_writel((__VAL), (__REG))
#define CS4231_WRITE8(__C, __REG, __VAL) sbus_writeb((__VAL), (__REG))
#endif
/* Local status bits */
#define CS_STATUS_NEED_INIT 0x01
#define CS_STATUS_INIT_ON_CLOSE 0x02
#define CS_STATUS_REV_A 0x04
#define CS_STATUS_INTS_ON 0x08
#define CS_STATUS_IS_ULTRA 0x10
#define CS_STATUS_IS_EBUS 0x20
#define CS_TIMEOUT 9000000
#define GAIN_SET(var, gain) ((var & ~(0x3f)) | gain)
#define RECGAIN_SET(var, gain) ((var & ~(0x1f)) | gain)
/* bits 0-3 set address of register accessed by idr register */
/* bit 4 allows access to idr registers 16-31 in mode 2 only */
/* bit 5 if set causes dma transfers to cease if the int bit of status set */
#define IAR_AUTOCAL_BEGIN 0x40 /* MCE */
#define IAR_NOT_READY 0x80 /* INIT */
#define IAR_AUTOCAL_END ~(IAR_AUTOCAL_BEGIN) /* MCD */
/* Registers 1-15 modes 1 and 2. Registers 16-31 mode 2 only */
/* Registers assumed to be same in both modes unless noted */
/* 0 - Left Input Control */
/* 1 - Right Input Control */
#define MIC_ENABLE(var) ((var & 0x2f) | 0x80)
#define LINE_ENABLE(var) (var & 0x2f)
#define CDROM_ENABLE(var) ((var & 0x2f) | 0x40)
#define OUTPUTLOOP_ENABLE(var) ((var & 0x2f) | 0xC0)
#define INPUTCR_AUX1 0x40
/* 2 - Left Aux 1 Input Control */
/* 3 - Right Aux 1 Input Control */
/* 4 - Left Aux 2 Input Control */
/* 5 - Right Aux 2 Input Control */
/* 6 - Left Output Control */
/* 7 - Right Output Control */
#define OUTCR_MUTE 0x80
#define OUTCR_UNMUTE ~0x80
/* 8 - Playback Data Format (Mode 2) */
#define CHANGE_DFR(var, val) ((var & ~(0xF)) | val)
#define CHANGE_ENCODING(var, val) ((var & ~(0xe0)) | val)
#define DEFAULT_DATA_FMAT CS4231_DFR_ULAW
#define CS4231_DFR_5512 0x01
#define CS4231_DFR_6615 0x0f
#define CS4231_DFR_8000 0x00
#define CS4231_DFR_9600 0x0e
#define CS4231_DFR_11025 0x03
#define CS4231_DFR_16000 0x02
#define CS4231_DFR_18900 0x05
#define CS4231_DFR_22050 0x07
#define CS4231_DFR_27429 0x04
#define CS4231_DFR_32000 0x06
#define CS4231_DFR_33075 0x0d
#define CS4231_DFR_37800 0x09
#define CS4231_DFR_44100 0x0b
#define CS4231_DFR_48000 0x0c
#define CS4231_DFR_LINEAR8 0x00
#define CS4231_DFR_ULAW 0x20
#define CS4231_DFR_LINEARLE 0x40
#define CS4231_DFR_ALAW 0x60
#define CS4231_DFR_ADPCM 0xa0 /* N/A in mode 1 */
#define CS4231_DFR_LINEARBE 0xc0 /* N/A in mode 1 */
#define CS4231_STEREO_ON(val) (val | 0x10)
#define CS4231_MONO_ON(val) (val & ~0x10)
/* 9 - Interface Config. Register */
#define PEN_ENABLE (0x01) /* Playback Enable */
#define PEN_DISABLE (~0x01)
#define CEN_ENABLE (0x02) /* Capture Enable */
#define CEN_DISABLE (~0x02)
#define SDC_ENABLE (0x04) /* Turn on single DMA Channel mode */
#define ACAL_CONV 0x08 /* Turn on converter autocal */
#define ACAL_DISABLE (~0x08)
#define ACAL_DAC 0x10 /* Turn on DAC autocal */
#define ACAL_FULL (ACAL_DAC|ACAL_CONV) /* Turn on full autocal */
#define PPIO 0x20 /* do playback via PIO rather than DMA */
#define CPIO 0x40 /* do capture via PIO rather than DMA */
#define ICR_AUTOCAL_INIT 0x01
/* 10 - Pin Control Register */
#define INTR_ON 0x82
#define INTR_OFF 0x80
#define PINCR_LINE_MUTE 0x40
#define PINCR_HDPH_MUTE 0x80
/* 11 - Test/Initialization */
#define DRQ_STAT 0x10
#define AUTOCAL_IN_PROGRESS 0x20
/* 12 - Misc Information */
#define MISC_IR_MODE2 0x40
/* 13 - Loopback Control */
#define LOOPB_ON 0x01
#define LOOPB_OFF 0x00
/* 14 - shared play/capture upper (mode 1) */
/* 15 - shared play/capture lower (mode 1) */
/* 14 - Playback Upper (mode 2) */
/* 15 - Playback Lower (mode 2) */
/* The rest are mode 2 only */
/* 16 - Alternate Feature 1 Enable */
#define DAC_ZERO 0x01
#define PLAY_MCE 0x10
#define CAPTURE_MCE 0x20
#define TIMER_ENABLE 0x40
#define OLB_ENABLE 0x80 /* go to 2.88 vpp analog output */
/* 17 - Alternate Feature 2 Enable */
#define HPF_ON 0x01 /* High Pass Filter */
#define XTALE_ON 0x02 /* Enable both crystals */
#define APAR_OFF 0x04 /* ADPCM playback accum reset */
/* 18 - Left Line Input Gain */
/* 19 - Right Line Input Gain */
/* 20 - Timer High */
/* 21 - Timer Low */
/* 22 - unused */
/* 23 - Alt. Fea. Ena 3 */
#define ACF 0x01
/* 24 - Alternate Feature Status */
#define CS_PU 0x01 /* Underrun */
#define CS_PO 0x02 /* Overrun */
#define CS_CU 0x04 /* Underrun */
#define CS_CO 0x08 /* Overrun */
#define CS_PI 0x10
#define CS_CI 0x20
#define CS_TI 0x40
/* 25 - Version */
#define CS4231A 0x20
#define CS4231CDE 0x80
/* 26 - Mono I/O Control */
#define CHANGE_MONO_GAIN(val) ((val & ~(0xFF)) | val)
#define MONO_IOCR_BYPASS 0x20
#define MONO_IOCR_MUTE 0x40
#define MONO_IOCR_INMUTE 0x80
/* 27 - Unused */
/* 28 - Capture Data Format */
/* see register 8 */
/* 29 - Unused */
/* 30 - Capture Upper */
/* 31 - Capture Lower */
/* Following are APC CSR register definitions for the Sparc */
#define APC_INT_PENDING 0x800000 /* Interrupt Pending */
#define APC_PLAY_INT 0x400000 /* Playback interrupt */
#define APC_CAPT_INT 0x200000 /* Capture interrupt */
#define APC_GENL_INT 0x100000 /* General interrupt */
#define APC_XINT_ENA 0x80000 /* General ext int. enable */
#define APC_XINT_PLAY 0x40000 /* Playback ext intr */
#define APC_XINT_CAPT 0x20000 /* Capture ext intr */
#define APC_XINT_GENL 0x10000 /* Error ext intr */
#define APC_XINT_EMPT 0x8000 /* Pipe empty interrupt (0 write to pva) */
#define APC_XINT_PEMP 0x4000 /* Play pipe empty (pva and pnva not set) */
#define APC_XINT_PNVA 0x2000 /* Playback NVA dirty */
#define APC_XINT_PENA 0x1000 /* play pipe empty Int enable */
#define APC_XINT_COVF 0x800 /* Cap data dropped on floor */
#define APC_XINT_CNVA 0x400 /* Capture NVA dirty */
#define APC_XINT_CEMP 0x200 /* Capture pipe empty (cva and cnva not set) */
#define APC_XINT_CENA 0x100 /* Cap. pipe empty int enable */
#define APC_PPAUSE 0x80 /* Pause the play DMA */
#define APC_CPAUSE 0x40 /* Pause the capture DMA */
#define APC_CDC_RESET 0x20 /* CODEC RESET */
#define APC_PDMA_READY 0x08 /* Play DMA Go */
#define APC_CDMA_READY 0x04 /* Capture DMA Go */
#define APC_CHIP_RESET 0x01 /* Reset the chip */
#define APC_INIT_SETUP (APC_CDMA_READY | APC_PDMA_READY | APC_XINT_ENA | \
APC_XINT_PLAY | APC_XINT_GENL | APC_INT_PENDING | \
APC_PLAY_INT | APC_CAPT_INT | APC_GENL_INT)
#define APC_PLAY_SETUP (APC_GENL_INT | APC_PLAY_INT | APC_XINT_ENA | \
APC_XINT_PLAY | APC_XINT_EMPT | APC_XINT_GENL | \
APC_XINT_PENA | APC_PDMA_READY)
#define APC_CAPT_SETUP (APC_GENL_INT | APC_CAPT_INT | APC_XINT_ENA | \
APC_XINT_CAPT | APC_XINT_CEMP | APC_XINT_GENL | \
APC_CDMA_READY)
/* Following are EB2 CSR register definitions for the Sparc */
/* asm/ebus.h has the base settings */
#define EB2_PLAY_SETUP (EBUS_DCSR_BURST_SZ_8 | EBUS_DCSR_INT_EN | EBUS_DCSR_EN_DMA | \
EBUS_DCSR_EN_CNT | EBUS_DCSR_TC)
#define EB2_CAPT_SETUP (EBUS_DCSR_BURST_SZ_8 | EBUS_DCSR_INT_EN | EBUS_DCSR_EN_DMA| \
EBUS_DCSR_EN_CNT | EBUS_DCSR_TC | EBUS_DCSR_WRITE)
#define CS4231_MIN_ATEN (0)
#define CS4231_MAX_ATEN (31)
#define CS4231_MAX_DEV_ATEN (63)
#define CS4231_MON_MIN_ATEN (0)
#define CS4231_MON_MAX_ATEN (63)
#define CS4231_DEFAULT_PLAYGAIN (132)
#define CS4231_DEFAULT_RECGAIN (126)
#define CS4231_MIN_GAIN (0)
#define CS4231_MAX_GAIN (15)
#define CS4231_PRECISION (8) /* # of bits/sample */
#define CS4231_CHANNELS (1) /* channels/sample */
#define CS4231_RATE (8000) /* default sample rate */
#endif /* _CS4231_H_ */
/* $Id: dbri.c,v 1.27 2001/10/08 22:19:50 davem Exp $
* drivers/sbus/audio/dbri.c
*
* Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de)
* Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org)
*
* This is the lowlevel driver for the DBRI & MMCODEC duo used for ISDN & AUDIO
* on Sun SPARCstation 10, 20, LX and Voyager models.
*
* - DBRI: AT&T T5900FX Dual Basic Rates ISDN Interface. It is a 32 channel
* data time multiplexer with ISDN support (aka T7259)
* Interfaces: SBus,ISDN NT & TE, CHI, 4 bits parallel.
* CHI: (spelled ki) Concentration Highway Interface (AT&T or Intel bus ?).
* Documentation:
* - "STP 4000SBus Dual Basic Rate ISDN (DBRI) Tranceiver" from
* Sparc Technology Business (courtesy of Sun Support)
* - Data sheet of the T7903, a newer but very similar ISA bus equivalent
* available from the Lucent (formarly AT&T microelectronics) home
* page.
* - http://www.freesoft.org/Linux/DBRI/
* - MMCODEC: Crystal Semiconductor CS4215 16 bit Multimedia Audio Codec
* Interfaces: CHI, Audio In & Out, 2 bits parallel
* Documentation: from the Crystal Semiconductor home page.
*
* The DBRI is a 32 pipe machine, each pipe can transfer some bits between
* memory and a serial device (long pipes, nr 0-15) or between two serial
* devices (short pipes, nr 16-31), or simply send a fixed data to a serial
* device (short pipes).
* A timeslot defines the bit-offset and nr of bits read from a serial device.
* The timeslots are linked to 6 circular lists, one for each direction for
* each serial device (NT,TE,CHI). A timeslot is associated to 1 or 2 pipes
* (the second one is a monitor/tee pipe, valid only for serial input).
*
* The mmcodec is connected via the CHI bus and needs the data & some
* parameters (volume, balance, output selection) timemultiplexed in 8 byte
* chunks. It also has a control mode, which serves for audio format setting.
*
* Looking at the CS4215 data sheet it is easy to set up 2 or 4 codecs on
* the same CHI bus, so I thought perhaps it is possible to use the onboard
* & the speakerbox codec simultanously, giving 2 (not very independent :-)
* audio devices. But the SUN HW group decided against it, at least on my
* LX the speakerbox connector has at least 1 pin missing and 1 wrongly
* connected.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <asm/openprom.h>
#include <asm/oplib.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/sbus.h>
#include <asm/pgtable.h>
#include <asm/audioio.h>
#include "dbri.h"
#if defined(DBRI_ISDN)
#include "../../isdn/hisax/hisax.h"
#include "../../isdn/hisax/isdnl1.h"
#include "../../isdn/hisax/foreign.h"
#endif
#define DBRI_DEBUG
#ifdef DBRI_DEBUG
#define dprintk(a, x) if(dbri_debug & a) printk x
#define D_GEN (1<<0)
#define D_INT (1<<1)
#define D_CMD (1<<2)
#define D_MM (1<<3)
#define D_USR (1<<4)
#define D_DESC (1<<5)
static int dbri_debug = 0;
MODULE_PARM(dbri_debug, "i");
static int dbri_trace = 0;
MODULE_PARM(dbri_trace, "i");
#define tprintk(x) if(dbri_trace) printk x
static char *cmds[] = {
"WAIT", "PAUSE", "JUMP", "IIQ", "REX", "SDP", "CDP", "DTS",
"SSP", "CHI", "NT", "TE", "CDEC", "TEST", "CDM", "RESRV"
};
#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | (1 << 27) | value)
#else
#define dprintk(a, x)
#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | (intr << 27) | value)
#endif /* DBRI_DEBUG */
#define MAX_DRIVERS 2 /* Increase this if need more than 2 DBRI's */
static struct sparcaudio_driver drivers[MAX_DRIVERS];
static int num_drivers = 0;
/*
****************************************************************************
************** DBRI initialization and command synchronization *************
****************************************************************************
Commands are sent to the DBRI by building a list of them in memory,
then writing the address of the first list item to DBRI register 8.
The list is terminated with a WAIT command, which can generate a
CPU interrupt if required.
Since the DBRI can run in parallel with the CPU, several means of
synchronization present themselves. The original scheme (Rudolf's)
was to set a flag when we "cmdlock"ed the DBRI, clear the flag when
an interrupt signaled completion, and wait on a wait_queue if a routine
attempted to cmdlock while the flag was set. The problems arose when
we tried to cmdlock from inside an interrupt handler, which might
cause scheduling in an interrupt (if we waited), etc, etc
A more sophisticated scheme might involve a circular command buffer
or an array of command buffers. A routine could fill one with
commands and link it onto a list. When a interrupt signaled
completion of the current command buffer, look on the list for
the next one.
I've decided to implement something much simpler - after each command,
the CPU waits for the DBRI to finish the command by polling the P bit
in DBRI register 0. I've tried to implement this in such a way
that might make implementing a more sophisticated scheme easier.
Every time a routine wants to write commands to the DBRI, it must
first call dbri_cmdlock() and get an initial pointer into dbri->dma->cmd
in return. After the commands have been writen, dbri_cmdsend() is
called with the final pointer value.
Something a little more clever is required if this code is ever run
on an SMP machine.
*/
static int dbri_locked = 0;
static volatile s32 *dbri_cmdlock(struct dbri *dbri)
{
if (dbri_locked)
printk("DBRI: Command buffer locked! (bug in driver)\n");
dbri_locked++;
return &dbri->dma->cmd[0];
}
static void dbri_process_interrupt_buffer(struct dbri *);
static void dbri_cmdsend(struct dbri *dbri, volatile s32 *cmd)
{
int MAXLOOPS = 1000000;
int maxloops = MAXLOOPS;
unsigned long flags;
volatile s32 *ptr;
for (ptr = &dbri->dma->cmd[0]; ptr < cmd; ptr++) {
dprintk(D_CMD, ("DBRI cmd: %lx:%08x\n",
(unsigned long) ptr, *ptr));
}
save_and_cli(flags);
dbri_locked--;
if (dbri_locked != 0) {
printk("DBRI: Command buffer improperly locked! (bug in driver)\n");
} else if ((cmd - &dbri->dma->cmd[0]) >= DBRI_NO_CMDS-1) {
printk("DBRI: Command buffer overflow! (bug in driver)\n");
} else {
*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
*(cmd++) = DBRI_CMD(D_WAIT, 1, 0);
dbri->wait_seen = 0;
sbus_writel(dbri->dma_dvma, dbri->regs + REG8);
while ((--maxloops) > 0 &&
(sbus_readl(dbri->regs + REG0) & D_P))
barrier();
if (maxloops == 0) {
printk("DBRI: Chip never completed command buffer\n");
} else {
while ((--maxloops) > 0 && (! dbri->wait_seen))
dbri_process_interrupt_buffer(dbri);
if (maxloops == 0) {
printk("DBRI: Chip never acked WAIT\n");
} else {
dprintk(D_INT, ("DBRI: Chip completed command "
"buffer (%d)\n",
MAXLOOPS - maxloops));
}
}
}
restore_flags(flags);
}
static void dbri_reset(struct dbri *dbri)
{
int i;
dprintk(D_GEN, ("DBRI: reset 0:%x 2:%x 8:%x 9:%x\n",
sbus_readl(dbri->regs + REG0),
sbus_readl(dbri->regs + REG2),
sbus_readl(dbri->regs + REG8),
sbus_readl(dbri->regs + REG9)));
sbus_writel(D_R, dbri->regs + REG0); /* Soft Reset */
for(i = 0; (sbus_readl(dbri->regs + REG0) & D_R) && i < 64; i++)
udelay(10);
}
static void dbri_detach(struct dbri *dbri)
{
dbri_reset(dbri);
free_irq(dbri->irq, dbri);
sbus_iounmap(dbri->regs, dbri->regs_size);
sbus_free_consistent(dbri->sdev, sizeof(struct dbri_dma),
(void *)dbri->dma, dbri->dma_dvma);
kfree(dbri);
}
static void dbri_initialize(struct dbri *dbri)
{
volatile s32 *cmd;
u32 dma_addr, tmp;
int n;
dbri_reset(dbri);
dprintk(D_GEN, ("DBRI: init: cmd: %p, int: %p\n",
&dbri->dma->cmd[0], &dbri->dma->intr[0]));
/*
* Initialize the interrupt ringbuffer.
*/
for(n = 0; n < DBRI_NO_INTS-1; n++) {
dma_addr = dbri->dma_dvma;
dma_addr += dbri_dma_off(intr, ((n+1) & DBRI_INT_BLK));
dbri->dma->intr[n * DBRI_INT_BLK] = dma_addr;
}
dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0);
dbri->dma->intr[n * DBRI_INT_BLK] = dma_addr;
dbri->dbri_irqp = 1;
/* We should query the openprom to see what burst sizes this
* SBus supports. For now, just disable all SBus bursts */
tmp = sbus_readl(dbri->regs + REG0);
tmp &= ~(D_G | D_S | D_E);
sbus_writel(tmp, dbri->regs + REG0);
/*
* Set up the interrupt queue
*/
cmd = dbri_cmdlock(dbri);
dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0);
*(cmd++) = DBRI_CMD(D_IIQ, 0, 0);
*(cmd++) = dma_addr;
dbri_cmdsend(dbri, cmd);
}
/*
****************************************************************************
*************************** DBRI interrupt handler *************************
****************************************************************************
The DBRI communicates with the CPU mainly via a circular interrupt
buffer. When an interrupt is signaled, the CPU walks through the
buffer and calls dbri_process_one_interrupt() for each interrupt word.
Complicated interrupts are handled by dedicated functions (which
appear first in this file). Any pending interrupts can be serviced by
calling dbri_process_interrupt_buffer(), which works even if the CPU's
interrupts are disabled. This function is used by dbri_cmdsend()
to make sure we're synced up with the chip after each command sequence,
even if we're running cli'ed.
*/
/*
* Short data pipes transmit LSB first. The CS4215 receives MSB first. Grrr.
* So we have to reverse the bits. Note: not all bit lengths are supported
*/
static __u32 reverse_bytes(__u32 b, int len)
{
switch(len) {
case 32:
b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16);
case 16:
b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8);
case 8:
b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4);
case 4:
b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2);
case 2:
b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1);
case 1:
case 0:
break;
default:
printk("DBRI reverse_bytes: unsupported length\n");
};
return b;
}
/* transmission_complete_intr()
*
* Called by main interrupt handler when DBRI signals transmission complete
* on a pipe (interrupt triggered by the B bit in a transmit descriptor).
*
* Walks through the pipe's list of transmit buffer descriptors, releasing
* each one's DMA buffer (if present), flagging the descriptor available,
* and signaling its callback routine (if present), before proceeding
* to the next one. Stops when the first descriptor is found without
* TBC (Transmit Buffer Complete) set, or we've run through them all.
*/
static void transmission_complete_intr(struct dbri *dbri, int pipe)
{
int td;
int status;
void *buffer;
void (*callback)(void *, int);
void *callback_arg;
td = dbri->pipes[pipe].desc;
while (td >= 0) {
if (td >= DBRI_NO_DESCS) {
printk("DBRI: invalid td on pipe %d\n", pipe);
return;
}
status = DBRI_TD_STATUS(dbri->dma->desc[td].word4);
if (! (status & DBRI_TD_TBC)) {
break;
}
dprintk(D_INT, ("DBRI: TD %d, status 0x%02x\n", td, status));
buffer = dbri->descs[td].buffer;
if (buffer)
sbus_unmap_single(dbri->sdev,
dbri->descs[td].buffer_dvma,
dbri->descs[td].len,
SBUS_DMA_TODEVICE);
callback = dbri->descs[td].output_callback;
callback_arg = dbri->descs[td].output_callback_arg;
dbri->descs[td].inuse = 0;
td = dbri->descs[td].next;
dbri->pipes[pipe].desc = td;
if (callback != NULL)
callback(callback_arg, status & 0xe);
}
}
static void reception_complete_intr(struct dbri *dbri, int pipe)
{
int rd = dbri->pipes[pipe].desc;
s32 status;
void *buffer;
void (*callback)(void *, int, unsigned int);
if (rd < 0 || rd >= DBRI_NO_DESCS) {
printk("DBRI: invalid rd on pipe %d\n", pipe);
return;
}
dbri->descs[rd].inuse = 0;
dbri->pipes[pipe].desc = dbri->descs[rd].next;
status = dbri->dma->desc[rd].word1;
buffer = dbri->descs[rd].buffer;
if (buffer)
sbus_unmap_single(dbri->sdev,
dbri->descs[rd].buffer_dvma,
dbri->descs[rd].len,
SBUS_DMA_FROMDEVICE);
callback = dbri->descs[rd].input_callback;
if (callback != NULL)
callback(dbri->descs[rd].input_callback_arg,
DBRI_RD_STATUS(status),
DBRI_RD_CNT(status)-2);
dprintk(D_INT, ("DBRI: Recv RD %d, status 0x%02x, len %d\n",
rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status)));
}
static void dbri_process_one_interrupt(struct dbri *dbri, int x)
{
int val = D_INTR_GETVAL(x);
int channel = D_INTR_GETCHAN(x);
int command = D_INTR_GETCMD(x);
int code = D_INTR_GETCODE(x);
int rval = D_INTR_GETRVAL(x);
if (channel == D_INTR_CMD) {
dprintk(D_INT,("DBRI: INTR: Command: %-5s Value:%d\n",
cmds[command], val));
} else {
dprintk(D_INT,("DBRI: INTR: Chan:%d Code:%d Val:%#x\n",
channel, code, rval));
}
if (channel == D_INTR_CMD && command == D_WAIT)
dbri->wait_seen++;
if (code == D_INTR_SBRI) {
/* SBRI - BRI status change */
const int liu_states[] = {1, 0, 8, 3, 4, 5, 6, 7};
dbri->liu_state = liu_states[val & 0x7];
if (dbri->liu_callback)
dbri->liu_callback(dbri->liu_callback_arg);
}
if (code == D_INTR_BRDY)
reception_complete_intr(dbri, channel);
if (code == D_INTR_XCMP)
transmission_complete_intr(dbri, channel);
if (code == D_INTR_UNDR) {
/* UNDR - Transmission underrun
* resend SDP command with clear pipe bit (C) set
*/
volatile s32 *cmd;
int pipe = channel;
int td = dbri->pipes[pipe].desc;
dbri->dma->desc[td].word4 = 0;
cmd = dbri_cmdlock(dbri);
*(cmd++) = DBRI_CMD(D_SDP, 0,
dbri->pipes[pipe].sdp
| D_SDP_P | D_SDP_C | D_SDP_2SAME);
*(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td);
dbri_cmdsend(dbri, cmd);
}
if (code == D_INTR_FXDT) {
/* FXDT - Fixed data change */
if (dbri->pipes[channel].sdp & D_SDP_MSB)
val = reverse_bytes(val, dbri->pipes[channel].length);
if (dbri->pipes[channel].recv_fixed_ptr)
*(dbri->pipes[channel].recv_fixed_ptr) = val;
}
}
/* dbri_process_interrupt_buffer advances through the DBRI's interrupt
* buffer until it finds a zero word (indicating nothing more to do
* right now). Non-zero words require processing and are handed off
* to dbri_process_one_interrupt AFTER advancing the pointer. This
* order is important since we might recurse back into this function
* and need to make sure the pointer has been advanced first.
*/
static void dbri_process_interrupt_buffer(struct dbri *dbri)
{
s32 x;
while ((x = dbri->dma->intr[dbri->dbri_irqp]) != 0) {
dbri->dma->intr[dbri->dbri_irqp] = 0;
dbri->dbri_irqp++;
if (dbri->dbri_irqp == (DBRI_NO_INTS * DBRI_INT_BLK))
dbri->dbri_irqp = 1;
else if ((dbri->dbri_irqp & (DBRI_INT_BLK-1)) == 0)
dbri->dbri_irqp++;
tprintk(("dbri->dbri_irqp == %d\n", dbri->dbri_irqp));
dbri_process_one_interrupt(dbri, x);
}
}
static void dbri_intr(int irq, void *opaque, struct pt_regs *regs)
{
struct dbri *dbri = (struct dbri *) opaque;
int x;
/*
* Read it, so the interrupt goes away.
*/
x = sbus_readl(dbri->regs + REG1);
dprintk(D_INT, ("DBRI: Interrupt! (reg1=0x%08x)\n", x));
if (x & (D_MRR|D_MLE|D_LBG|D_MBE)) {
u32 tmp;
if(x & D_MRR) printk("DBRI: Multiple Error Ack on SBus\n");
if(x & D_MLE) printk("DBRI: Multiple Late Error on SBus\n");
if(x & D_LBG) printk("DBRI: Lost Bus Grant on SBus\n");
if(x & D_MBE) printk("DBRI: Burst Error on SBus\n");
/* Some of these SBus errors cause the chip's SBus circuitry
* to be disabled, so just re-enable and try to keep going.
*
* The only one I've seen is MRR, which will be triggered
* if you let a transmit pipe underrun, then try to CDP it.
*
* If these things persist, we should probably reset
* and re-init the chip.
*/
tmp = sbus_readl(dbri->regs + REG0);
tmp &= ~(D_D);
sbus_writel(tmp, dbri->regs + REG0);
}
#if 0
if (!(x & D_IR)) /* Not for us */
return;
#endif
dbri_process_interrupt_buffer(dbri);
}
/*
****************************************************************************
************************** DBRI data pipe management ***********************
****************************************************************************
While DBRI control functions use the command and interrupt buffers, the
main data path takes the form of data pipes, which can be short (command
and interrupt driven), or long (attached to DMA buffers). These functions
provide a rudimentary means of setting up and managing the DBRI's pipes,
but the calling functions have to make sure they respect the pipes' linked
list ordering, among other things. The transmit and receive functions
here interface closely with the transmit and receive interrupt code.
*/
static int pipe_active(struct dbri *dbri, int pipe)
{
return (dbri->pipes[pipe].desc != -1);
}
/* reset_pipe(dbri, pipe)
*
* Called on an in-use pipe to clear anything being transmitted or received
*/
static void reset_pipe(struct dbri *dbri, int pipe)
{
int sdp;
int desc;
volatile int *cmd;
if (pipe < 0 || pipe > 31) {
printk("DBRI: reset_pipe called with illegal pipe number\n");
return;
}
sdp = dbri->pipes[pipe].sdp;
if (sdp == 0) {
printk("DBRI: reset_pipe called on uninitialized pipe\n");
return;
}
cmd = dbri_cmdlock(dbri);
*(cmd++) = DBRI_CMD(D_SDP, 0, sdp | D_SDP_C | D_SDP_P);
*(cmd++) = 0;
dbri_cmdsend(dbri, cmd);
desc = dbri->pipes[pipe].desc;
while (desc != -1) {
void *buffer = dbri->descs[desc].buffer;
void (*output_callback) (void *, int)
= dbri->descs[desc].output_callback;
void *output_callback_arg
= dbri->descs[desc].output_callback_arg;
void (*input_callback) (void *, int, unsigned int)
= dbri->descs[desc].input_callback;
void *input_callback_arg
= dbri->descs[desc].input_callback_arg;
if (buffer)
sbus_unmap_single(dbri->sdev,
dbri->descs[desc].buffer_dvma,
dbri->descs[desc].len,
output_callback != NULL ? SBUS_DMA_TODEVICE
: SBUS_DMA_FROMDEVICE);
dbri->descs[desc].inuse = 0;
desc = dbri->descs[desc].next;
if (output_callback)
output_callback(output_callback_arg, -1);
if (input_callback)
input_callback(input_callback_arg, -1, 0);
}
dbri->pipes[pipe].desc = -1;
}
static void setup_pipe(struct dbri *dbri, int pipe, int sdp)
{
if (pipe < 0 || pipe > 31) {
printk("DBRI: setup_pipe called with illegal pipe number\n");
return;
}
if ((sdp & 0xf800) != sdp) {
printk("DBRI: setup_pipe called with strange SDP value\n");
/* sdp &= 0xf800; */
}
/* If this is a fixed receive pipe, arrange for an interrupt
* every time its data changes
*/
if (D_SDP_MODE(sdp) == D_SDP_FIXED && ! (sdp & D_SDP_TO_SER))
sdp |= D_SDP_CHANGE;
sdp |= D_PIPE(pipe);
dbri->pipes[pipe].sdp = sdp;
dbri->pipes[pipe].desc = -1;
reset_pipe(dbri, pipe);
}
static void link_time_slot(struct dbri *dbri, int pipe,
enum in_or_out direction, int basepipe,
int length, int cycle)
{
volatile s32 *cmd;
int val;
int prevpipe;
int nextpipe;
if (pipe < 0 || pipe > 31 || basepipe < 0 || basepipe > 31) {
printk("DBRI: link_time_slot called with illegal pipe number\n");
return;
}
if (dbri->pipes[pipe].sdp == 0 || dbri->pipes[basepipe].sdp == 0) {
printk("DBRI: link_time_slot called on uninitialized pipe\n");
return;
}
/* Deal with CHI special case:
* "If transmission on edges 0 or 1 is desired, then cycle n
* (where n = # of bit times per frame...) must be used."
* - DBRI data sheet, page 11
*/
if (basepipe == 16 && direction == PIPEoutput && cycle == 0)
cycle = dbri->chi_bpf;
if (basepipe == pipe) {
prevpipe = pipe;
nextpipe = pipe;
} else {
/* We're not initializing a new linked list (basepipe != pipe),
* so run through the linked list and find where this pipe
* should be sloted in, based on its cycle. CHI confuses
* things a bit, since it has a single anchor for both its
* transmit and receive lists.
*/
if (basepipe == 16) {
if (direction == PIPEinput) {
prevpipe = dbri->chi_in_pipe;
} else {
prevpipe = dbri->chi_out_pipe;
}
} else {
prevpipe = basepipe;
}
nextpipe = dbri->pipes[prevpipe].nextpipe;
while (dbri->pipes[nextpipe].cycle < cycle
&& dbri->pipes[nextpipe].nextpipe != basepipe) {
prevpipe = nextpipe;
nextpipe = dbri->pipes[nextpipe].nextpipe;
}
}
if (prevpipe == 16) {
if (direction == PIPEinput) {
dbri->chi_in_pipe = pipe;
} else {
dbri->chi_out_pipe = pipe;
}
} else {
dbri->pipes[prevpipe].nextpipe = pipe;
}
dbri->pipes[pipe].nextpipe = nextpipe;
dbri->pipes[pipe].cycle = cycle;
dbri->pipes[pipe].length = length;
cmd = dbri_cmdlock(dbri);
if (direction == PIPEinput) {
val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(prevpipe) | pipe;
*(cmd++) = DBRI_CMD(D_DTS, 0, val);
*(cmd++) = D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe);
*(cmd++) = 0;
} else {
val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(prevpipe) | pipe;
*(cmd++) = DBRI_CMD(D_DTS, 0, val);
*(cmd++) = 0;
*(cmd++) = D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe);
}
dbri_cmdsend(dbri, cmd);
}
/* I don't use this function, so it's basically untested. */
static void unlink_time_slot(struct dbri *dbri, int pipe,
enum in_or_out direction, int prevpipe,
int nextpipe)
{
volatile s32 *cmd;
int val;
if (pipe < 0 || pipe > 31 || prevpipe < 0 || prevpipe > 31) {
printk("DBRI: unlink_time_slot called with illegal pipe number\n");
return;
}
cmd = dbri_cmdlock(dbri);
if (direction == PIPEinput) {
val = D_DTS_VI | D_DTS_DEL | D_DTS_PRVIN(prevpipe) | pipe;
*(cmd++) = DBRI_CMD(D_DTS, 0, val);
*(cmd++) = D_TS_NEXT(nextpipe);
*(cmd++) = 0;
} else {
val = D_DTS_VO | D_DTS_DEL | D_DTS_PRVOUT(prevpipe) | pipe;
*(cmd++) = DBRI_CMD(D_DTS, 0, val);
*(cmd++) = 0;
*(cmd++) = D_TS_NEXT(nextpipe);
}
dbri_cmdsend(dbri, cmd);
}
/* xmit_fixed() / recv_fixed()
*
* Transmit/receive data on a "fixed" pipe - i.e, one whose contents are not
* expected to change much, and which we don't need to buffer.
* The DBRI only interrupts us when the data changes (receive pipes),
* or only changes the data when this function is called (transmit pipes).
* Only short pipes (numbers 16-31) can be used in fixed data mode.
*
* These function operate on a 32-bit field, no matter how large
* the actual time slot is. The interrupt handler takes care of bit
* ordering and alignment. An 8-bit time slot will always end up
* in the low-order 8 bits, filled either MSB-first or LSB-first,
* depending on the settings passed to setup_pipe()
*/
static void xmit_fixed(struct dbri *dbri, int pipe, unsigned int data)
{
volatile s32 *cmd;
if (pipe < 16 || pipe > 31) {
printk("DBRI: xmit_fixed: Illegal pipe number\n");
return;
}
if (D_SDP_MODE(dbri->pipes[pipe].sdp) == 0) {
printk("DBRI: xmit_fixed: Uninitialized pipe %d\n", pipe);
return;
}
if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) {
printk("DBRI: xmit_fixed: Non-fixed pipe %d\n", pipe);
return;
}
if (! (dbri->pipes[pipe].sdp & D_SDP_TO_SER)) {
printk("DBRI: xmit_fixed: Called on receive pipe %d\n", pipe);
return;
}
/* DBRI short pipes always transmit LSB first */
if (dbri->pipes[pipe].sdp & D_SDP_MSB)
data = reverse_bytes(data, dbri->pipes[pipe].length);
cmd = dbri_cmdlock(dbri);
*(cmd++) = DBRI_CMD(D_SSP, 0, pipe);
*(cmd++) = data;
dbri_cmdsend(dbri, cmd);
}
static void recv_fixed(struct dbri *dbri, int pipe, volatile __u32 *ptr)
{
if (pipe < 16 || pipe > 31) {
printk("DBRI: recv_fixed called with illegal pipe number\n");
return;
}
if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) {
printk("DBRI: recv_fixed called on non-fixed pipe %d\n", pipe);
return;
}
if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) {
printk("DBRI: recv_fixed called on transmit pipe %d\n", pipe);
return;
}
dbri->pipes[pipe].recv_fixed_ptr = ptr;
}
/* xmit_on_pipe() / recv_on_pipe()
*
* Transmit/receive data on a "long" pipe - i.e, one associated
* with a DMA buffer.
*
* Only pipe numbers 0-15 can be used in this mode.
*
* Both functions take pointer/len arguments pointing to a data buffer,
* and both provide callback functions (may be NULL) to notify higher
* level code when transmission/reception is complete.
*
* Both work by building chains of descriptors which identify the
* data buffers. Buffers too large for a single descriptor will
* be spread across multiple descriptors.
*/
static void xmit_on_pipe(struct dbri *dbri, int pipe,
void * buffer, unsigned int len,
void (*callback)(void *, int), void * callback_arg)
{
volatile s32 *cmd;
unsigned long flags;
int td = 0;
int first_td = -1;
int last_td = -1;
__u32 dvma_buffer, dvma_buffer_base;
if (pipe < 0 || pipe > 15) {
printk("DBRI: xmit_on_pipe: Illegal pipe number\n");
return;
}
if (dbri->pipes[pipe].sdp == 0) {
printk("DBRI: xmit_on_pipe: Uninitialized pipe %d\n", pipe);
return;
}
if (! (dbri->pipes[pipe].sdp & D_SDP_TO_SER)) {
printk("DBRI: xmit_on_pipe: Called on receive pipe %d\n",
pipe);
return;
}
dvma_buffer_base = dvma_buffer = sbus_map_single(dbri->sdev, buffer, len,
SBUS_DMA_TODEVICE);
while (len > 0) {
int mylen;
for (; td < DBRI_NO_DESCS; td ++) {
if (! dbri->descs[td].inuse)
break;
}
if (td == DBRI_NO_DESCS) {
printk("DBRI: xmit_on_pipe: No descriptors\n");
break;
}
if (len > ((1 << 13) - 1)) {
mylen = (1 << 13) - 1;
} else {
mylen = len;
}
dbri->descs[td].inuse = 1;
dbri->descs[td].next = -1;
dbri->descs[td].buffer = NULL;
dbri->descs[td].output_callback = NULL;
dbri->descs[td].input_callback = NULL;
dbri->dma->desc[td].word1 = DBRI_TD_CNT(mylen);
dbri->dma->desc[td].ba = dvma_buffer;
dbri->dma->desc[td].nda = 0;
dbri->dma->desc[td].word4 = 0;
if (first_td == -1) {
first_td = td;
} else {
dbri->descs[last_td].next = td;
dbri->dma->desc[last_td].nda =
dbri->dma_dvma + dbri_dma_off(desc, td);
}
last_td = td;
dvma_buffer += mylen;
len -= mylen;
}
if (first_td == -1 || last_td == -1) {
sbus_unmap_single(dbri->sdev, dvma_buffer_base,
dvma_buffer - dvma_buffer_base + len,
SBUS_DMA_TODEVICE);
return;
}
dbri->dma->desc[last_td].word1 |= DBRI_TD_I | DBRI_TD_F | DBRI_TD_B;
dbri->descs[last_td].buffer = buffer;
dbri->descs[last_td].buffer_dvma = dvma_buffer_base;
dbri->descs[last_td].len = dvma_buffer - dvma_buffer_base + len;
dbri->descs[last_td].output_callback = callback;
dbri->descs[last_td].output_callback_arg = callback_arg;
for (td=first_td; td != -1; td = dbri->descs[td].next) {
dprintk(D_DESC, ("DBRI TD %d: %08x %08x %08x %08x\n",
td,
dbri->dma->desc[td].word1,
dbri->dma->desc[td].ba,
dbri->dma->desc[td].nda,
dbri->dma->desc[td].word4));
}
save_and_cli(flags);
if (pipe_active(dbri, pipe)) {
/* Pipe is already active - find last TD in use
* and link our first TD onto its end. Then issue
* a CDP command to let the DBRI know there's more data.
*/
last_td = dbri->pipes[pipe].desc;
while (dbri->descs[last_td].next != -1)
last_td = dbri->descs[last_td].next;
dbri->descs[last_td].next = first_td;
dbri->dma->desc[last_td].nda =
dbri->dma_dvma + dbri_dma_off(desc, first_td);
cmd = dbri_cmdlock(dbri);
*(cmd++) = DBRI_CMD(D_CDP, 0, pipe);
dbri_cmdsend(dbri,cmd);
} else {
/* Pipe isn't active - issue an SDP command to start
* our chain of TDs running.
*/
dbri->pipes[pipe].desc = first_td;
cmd = dbri_cmdlock(dbri);
*(cmd++) = DBRI_CMD(D_SDP, 0,
dbri->pipes[pipe].sdp
| D_SDP_P | D_SDP_EVERY | D_SDP_C);
*(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td);
dbri_cmdsend(dbri, cmd);
}
restore_flags(flags);
}
static void recv_on_pipe(struct dbri *dbri, int pipe,
void * buffer, unsigned int len,
void (*callback)(void *, int, unsigned int),
void * callback_arg)
{
volatile s32 *cmd;
int first_rd = -1;
int last_rd = -1;
int rd;
__u32 bus_buffer, bus_buffer_base;
if (pipe < 0 || pipe > 15) {
printk("DBRI: recv_on_pipe: Illegal pipe number\n");
return;
}
if (dbri->pipes[pipe].sdp == 0) {
printk("DBRI: recv_on_pipe: Uninitialized pipe %d\n", pipe);
return;
}
if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) {
printk("DBRI: recv_on_pipe: Called on transmit pipe %d\n",
pipe);
return;
}
/* XXX Fix this XXX
* Should be able to queue multiple buffers to receive on a pipe
*/
if (dbri->pipes[pipe].desc != -1) {
printk("DBRI: recv_on_pipe: Called on active pipe %d\n", pipe);
return;
}
/* Make sure buffer size is multiple of four */
len &= ~3;
bus_buffer_base = bus_buffer = sbus_map_single(dbri->sdev, buffer, len,
SBUS_DMA_FROMDEVICE);
while (len > 0) {
int rd, mylen;
if (len > ((1 << 13) - 4)) {
mylen = (1 << 13) - 4;
} else {
mylen = len;
}
for (rd = 0; rd < DBRI_NO_DESCS; rd ++) {
if (! dbri->descs[rd].inuse)
break;
}
if (rd == DBRI_NO_DESCS) {
printk("DBRI recv_on_pipe: No descriptors\n");
break;
}
dbri->dma->desc[rd].word1 = 0;
dbri->dma->desc[rd].ba = bus_buffer;
dbri->dma->desc[rd].nda = 0;
dbri->dma->desc[rd].word4 = DBRI_RD_B | DBRI_RD_BCNT(mylen);
dbri->descs[rd].buffer = NULL;
dbri->descs[rd].len = 0;
dbri->descs[rd].input_callback = NULL;
dbri->descs[rd].output_callback = NULL;
dbri->descs[rd].next = -1;
dbri->descs[rd].inuse = 1;
if (first_rd == -1) first_rd = rd;
if (last_rd != -1) {
dbri->dma->desc[last_rd].nda =
dbri->dma_dvma + dbri_dma_off(desc, rd);
dbri->descs[last_rd].next = rd;
}
last_rd = rd;
bus_buffer += mylen;
len -= mylen;
}
if (last_rd == -1 || first_rd == -1) {
sbus_unmap_single(dbri->sdev, bus_buffer_base,
bus_buffer - bus_buffer_base + len,
SBUS_DMA_FROMDEVICE);
return;
}
for (rd=first_rd; rd != -1; rd = dbri->descs[rd].next) {
dprintk(D_DESC, ("DBRI RD %d: %08x %08x %08x %08x\n",
rd,
dbri->dma->desc[rd].word1,
dbri->dma->desc[rd].ba,
dbri->dma->desc[rd].nda,
dbri->dma->desc[rd].word4));
}
dbri->descs[last_rd].buffer = buffer;
dbri->descs[last_rd].buffer_dvma = bus_buffer_base;
dbri->descs[last_rd].len = bus_buffer - bus_buffer_base + len;
dbri->descs[last_rd].input_callback = callback;
dbri->descs[last_rd].input_callback_arg = callback_arg;
dbri->pipes[pipe].desc = first_rd;
cmd = dbri_cmdlock(dbri);
*(cmd++) = DBRI_CMD(D_SDP, 0, dbri->pipes[pipe].sdp | D_SDP_P | D_SDP_C);
*(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_rd);
dbri_cmdsend(dbri, cmd);
}
/*
****************************************************************************
************************** DBRI - CHI interface ****************************
****************************************************************************
The CHI is a four-wire (clock, frame sync, data in, data out) time-division
multiplexed serial interface which the DBRI can operate in either master
(give clock/frame sync) or slave (take clock/frame sync) mode.
*/
enum master_or_slave { CHImaster, CHIslave };
static void reset_chi(struct dbri *dbri, enum master_or_slave master_or_slave,
int bits_per_frame)
{
volatile s32 *cmd;
int val;
static int chi_initialized = 0;
if (!chi_initialized) {
cmd = dbri_cmdlock(dbri);
/* Set CHI Anchor: Pipe 16 */
val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(16) | D_PIPE(16);
*(cmd++) = DBRI_CMD(D_DTS, 0, val);
*(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
*(cmd++) = 0;
val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(16) | D_PIPE(16);
*(cmd++) = DBRI_CMD(D_DTS, 0, val);
*(cmd++) = 0;
*(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
dbri->pipes[16].sdp = 1;
dbri->pipes[16].nextpipe = 16;
dbri->chi_in_pipe = 16;
dbri->chi_out_pipe = 16;
#if 0
chi_initialized ++;
#endif
} else {
int pipe;
for (pipe = dbri->chi_in_pipe;
pipe != 16;
pipe = dbri->pipes[pipe].nextpipe) {
unlink_time_slot(dbri, pipe, PIPEinput,
16, dbri->pipes[pipe].nextpipe);
}
for (pipe = dbri->chi_out_pipe;
pipe != 16;
pipe = dbri->pipes[pipe].nextpipe) {
unlink_time_slot(dbri, pipe, PIPEoutput,
16, dbri->pipes[pipe].nextpipe);
}
dbri->chi_in_pipe = 16;
dbri->chi_out_pipe = 16;
cmd = dbri_cmdlock(dbri);
}
if (master_or_slave == CHIslave) {
/* Setup DBRI for CHI Slave - receive clock, frame sync (FS)
*
* CHICM = 0 (slave mode, 8 kHz frame rate)
* IR = give immediate CHI status interrupt
* EN = give CHI status interrupt upon change
*/
*(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(0));
} else {
/* Setup DBRI for CHI Master - generate clock, FS
*
* BPF = bits per 8 kHz frame
* 12.288 MHz / CHICM_divisor = clock rate
* FD = 1 - drive CHIFS on rising edge of CHICK
*/
int clockrate = bits_per_frame * 8;
int divisor = 12288 / clockrate;
if (divisor > 255 || divisor * clockrate != 12288)
printk("DBRI: illegal bits_per_frame in setup_chi\n");
*(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(divisor) | D_CHI_FD
| D_CHI_BPF(bits_per_frame));
}
dbri->chi_bpf = bits_per_frame;
/* CHI Data Mode
*
* RCE = 0 - receive on falling edge of CHICK
* XCE = 1 - transmit on rising edge of CHICK
* XEN = 1 - enable transmitter
* REN = 1 - enable receiver
*/
*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
*(cmd++) = DBRI_CMD(D_CDM, 0, D_CDM_XCE|D_CDM_XEN|D_CDM_REN);
dbri_cmdsend(dbri, cmd);
}
/*
****************************************************************************
*********************** CS4215 audio codec management **********************
****************************************************************************
In the standard SPARC audio configuration, the CS4215 codec is attached
to the DBRI via the CHI interface and few of the DBRI's PIO pins.
*/
static void mmcodec_default(struct cs4215 *mm)
{
/*
* No action, memory resetting only.
*
* Data Time Slot 5-8
* Speaker,Line and Headphone enable. Gain set to the half.
* Input is mike.
*/
mm->data[0] = CS4215_LO(0x20) | CS4215_HE|CS4215_LE;
mm->data[1] = CS4215_RO(0x20) | CS4215_SE;
mm->data[2] = CS4215_LG( 0x8) | CS4215_IS | CS4215_PIO0 | CS4215_PIO1;
mm->data[3] = CS4215_RG( 0x8) | CS4215_MA(0xf);
/*
* Control Time Slot 1-4
* 0: Default I/O voltage scale
* 1: 8 bit ulaw, 8kHz, mono, high pass filter disabled
* 2: Serial enable, CHI master, 128 bits per frame, clock 1
* 3: Tests disabled
*/
mm->ctrl[0] = CS4215_RSRVD_1 | CS4215_MLB;
mm->ctrl[1] = CS4215_DFR_ULAW | CS4215_FREQ[0].csval;
mm->ctrl[2] = CS4215_XCLK |
CS4215_BSEL_128 | CS4215_FREQ[0].xtal;
mm->ctrl[3] = 0;
}
static void mmcodec_setup_pipes(struct dbri *dbri)
{
/*
* Data mode:
* Pipe 4: Send timeslots 1-4 (audio data)
* Pipe 20: Send timeslots 5-8 (part of ctrl data)
* Pipe 6: Receive timeslots 1-4 (audio data)
* Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via
* interrupt, and the rest of the data (slot 5 and 8) is
* not relevant for us (only for doublechecking).
*
* Control mode:
* Pipe 17: Send timeslots 1-4 (slots 5-8 are readonly)
* Pipe 18: Receive timeslot 1 (clb).
* Pipe 19: Receive timeslot 7 (version).
*/
setup_pipe(dbri, 4, D_SDP_MEM | D_SDP_TO_SER | D_SDP_MSB);
setup_pipe(dbri, 20, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB);
setup_pipe(dbri, 6, D_SDP_MEM | D_SDP_FROM_SER | D_SDP_MSB);
setup_pipe(dbri, 21, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
setup_pipe(dbri, 17, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB);
setup_pipe(dbri, 18, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
setup_pipe(dbri, 19, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
dbri->mm.status = 0;
recv_fixed(dbri, 18, & dbri->mm.status);
recv_fixed(dbri, 19, & dbri->mm.version);
}
static void mmcodec_setgain(struct dbri *dbri, int muted)
{
if (muted || dbri->perchip_info.output_muted) {
dbri->mm.data[0] = 63;
dbri->mm.data[1] = 63;
} else {
int left_gain = (dbri->perchip_info.play.gain / 4) % 64;
int right_gain = (dbri->perchip_info.play.gain / 4) % 64;
int outport = dbri->perchip_info.play.port;
if (dbri->perchip_info.play.balance < AUDIO_MID_BALANCE) {
right_gain *= dbri->perchip_info.play.balance;
right_gain /= AUDIO_MID_BALANCE;
} else {
left_gain *= AUDIO_RIGHT_BALANCE
- dbri->perchip_info.play.balance;
left_gain /= AUDIO_MID_BALANCE;
}
dprintk(D_MM, ("DBRI: Setting codec gain left: %d right: %d\n",
left_gain, right_gain));
dbri->mm.data[0] = (63 - left_gain);
if (outport & AUDIO_HEADPHONE) dbri->mm.data[0] |= CS4215_HE;
if (outport & AUDIO_LINE_OUT) dbri->mm.data[0] |= CS4215_LE;
dbri->mm.data[1] = (63 - right_gain);
if (outport & AUDIO_SPEAKER) dbri->mm.data[1] |= CS4215_SE;
}
xmit_fixed(dbri, 20, *(int *)dbri->mm.data);
}
static void mmcodec_init_data(struct dbri *dbri)
{
int data_width;
u32 tmp;
/*
* Data mode:
* Pipe 4: Send timeslots 1-4 (audio data)
* Pipe 20: Send timeslots 5-8 (part of ctrl data)
* Pipe 6: Receive timeslots 1-4 (audio data)
* Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via
* interrupt, and the rest of the data (slot 5 and 8) is
* not relevant for us (only for doublechecking).
*
* Just like in control mode, the time slots are all offset by eight
* bits. The CS4215, it seems, observes TSIN (the delayed signal)
* even if it's the CHI master. Don't ask me...
*/
tmp = sbus_readl(dbri->regs + REG0);
tmp &= ~(D_C); /* Disable CHI */
sbus_writel(tmp, dbri->regs + REG0);
/* Switch CS4215 to data mode - set PIO3 to 1 */
sbus_writel(D_ENPIO | D_PIO1 | D_PIO3 |
(dbri->mm.onboard ? D_PIO0 : D_PIO2),
dbri->regs + REG2);
reset_chi(dbri, CHIslave, 128);
/* Note: this next doesn't work for 8-bit stereo, because the two
* channels would be on timeslots 1 and 3, with 2 and 4 idle.
* (See CS4215 datasheet Fig 15)
*
* DBRI non-contiguous mode would be required to make this work.
*/
data_width = dbri->perchip_info.play.channels
* dbri->perchip_info.play.precision;
link_time_slot(dbri, 20, PIPEoutput, 16,
32, dbri->mm.offset + 32);
link_time_slot(dbri, 4, PIPEoutput, 16,
data_width, dbri->mm.offset);
link_time_slot(dbri, 6, PIPEinput, 16,
data_width, dbri->mm.offset);
link_time_slot(dbri, 21, PIPEinput, 16,
16, dbri->mm.offset + 40);
mmcodec_setgain(dbri, 0);
tmp = sbus_readl(dbri->regs + REG0);
tmp |= D_C; /* Enable CHI */
sbus_writel(tmp, dbri->regs + REG0);
}
/*
* Send the control information (i.e. audio format)
*/
static int mmcodec_setctrl(struct dbri *dbri)
{
int i, val;
u32 tmp;
/* XXX - let the CPU do something useful during these delays */
/* Temporarily mute outputs, and wait 1/8000 sec (125 us)
* to make sure this takes. This avoids clicking noises.
*/
mmcodec_setgain(dbri, 1);
udelay(125);
/*
* Enable Control mode: Set DBRI's PIO3 (4215's D/~C) to 0, then wait
* 12 cycles <= 12/(5512.5*64) sec = 34.01 usec
*/
val = D_ENPIO | D_PIO1 | (dbri->mm.onboard ? D_PIO0 : D_PIO2);
sbus_writel(val, dbri->regs + REG2);
udelay(34);
/* In Control mode, the CS4215 is a slave device, so the DBRI must
* operate as CHI master, supplying clocking and frame synchronization.
*
* In Data mode, however, the CS4215 must be CHI master to insure
* that its data stream is synchronous with its codec.
*
* The upshot of all this? We start by putting the DBRI into master
* mode, program the CS4215 in Control mode, then switch the CS4215
* into Data mode and put the DBRI into slave mode. Various timing
* requirements must be observed along the way.
*
* Oh, and one more thing, on a SPARCStation 20 (and maybe
* others?), the addressing of the CS4215's time slots is
* offset by eight bits, so we add eight to all the "cycle"
* values in the Define Time Slot (DTS) commands. This is
* done in hardware by a TI 248 that delays the DBRI->4215
* frame sync signal by eight clock cycles. Anybody know why?
*/
tmp = sbus_readl(dbri->regs + REG0);
tmp &= ~D_C; /* Disable CHI */
sbus_writel(tmp, dbri->regs + REG0);
reset_chi(dbri, CHImaster, 128);
/*
* Control mode:
* Pipe 17: Send timeslots 1-4 (slots 5-8 are readonly)
* Pipe 18: Receive timeslot 1 (clb).
* Pipe 19: Receive timeslot 7 (version).
*/
link_time_slot(dbri, 17, PIPEoutput, 16,
32, dbri->mm.offset);
link_time_slot(dbri, 18, PIPEinput, 16,
8, dbri->mm.offset);
link_time_slot(dbri, 19, PIPEinput, 16,
8, dbri->mm.offset + 48);
/* Wait for the chip to echo back CLB (Control Latch Bit) as zero */
dbri->mm.ctrl[0] &= ~CS4215_CLB;
xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl);
tmp = sbus_readl(dbri->regs + REG0);
tmp |= D_C; /* Enable CHI */
sbus_writel(tmp, dbri->regs + REG0);
i = 64;
while (((dbri->mm.status & 0xe4) != 0x20) && --i)
udelay(125);
if (i == 0) {
dprintk(D_MM, ("DBRI: CS4215 didn't respond to CLB (0x%02x)\n",
dbri->mm.status));
return -1;
}
/* Terminate CS4215 control mode - data sheet says
* "Set CLB=1 and send two more frames of valid control info"
*/
dbri->mm.ctrl[0] |= CS4215_CLB;
xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl);
/* Two frames of control info @ 8kHz frame rate = 250 us delay */
udelay(250);
mmcodec_setgain(dbri, 0);
return 0;
}
static int mmcodec_init(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
u32 reg2 = sbus_readl(dbri->regs + REG2);
/* Look for the cs4215 chips */
if(reg2 & D_PIO2) {
dprintk(D_MM, ("DBRI: Onboard CS4215 detected\n"));
dbri->mm.onboard = 1;
}
if(reg2 & D_PIO0) {
dprintk(D_MM, ("DBRI: Speakerbox detected\n"));
dbri->mm.onboard = 0;
}
/* Using the Speakerbox, if both are attached. */
if((reg2 & D_PIO2) && (reg2 & D_PIO0)) {
printk("DBRI: Using speakerbox / ignoring onboard mmcodec.\n");
sbus_writel(D_ENPIO2, dbri->regs + REG2);
dbri->mm.onboard = 0;
}
if(!(reg2 & (D_PIO0|D_PIO2))) {
printk("DBRI: no mmcodec found.\n");
return -EIO;
}
mmcodec_setup_pipes(dbri);
mmcodec_default(&dbri->mm);
dbri->mm.version = 0xff;
dbri->mm.offset = dbri->mm.onboard ? 0 : 8;
if (mmcodec_setctrl(dbri) == -1 || dbri->mm.version == 0xff) {
dprintk(D_MM, ("DBRI: CS4215 failed probe at offset %d\n",
dbri->mm.offset));
return -EIO;
}
dprintk(D_MM, ("DBRI: Found CS4215 at offset %d\n", dbri->mm.offset));
dbri->perchip_info.play.channels = 1;
dbri->perchip_info.play.precision = 8;
dbri->perchip_info.play.gain = (AUDIO_MAX_GAIN * 7 / 10); /* 70% */
dbri->perchip_info.play.balance = AUDIO_MID_BALANCE;
dbri->perchip_info.play.port = dbri->perchip_info.play.avail_ports =
AUDIO_SPEAKER | AUDIO_HEADPHONE | AUDIO_LINE_OUT;
dbri->perchip_info.record.port = AUDIO_MICROPHONE;
dbri->perchip_info.record.avail_ports =
AUDIO_MICROPHONE | AUDIO_LINE_IN;
mmcodec_init_data(dbri);
return 0;
}
/*
****************************************************************************
******************** Interface with sparcaudio midlevel ********************
****************************************************************************
The sparcaudio midlevel is contained in the file audio.c. It interfaces
to the user process and performs buffering, intercepts SunOS-style ioctl's,
etc. It interfaces to a abstract audio device via a struct sparcaudio_driver.
This code presents such an interface for the DBRI with an attached CS4215.
All our routines are defined, and then comes our struct sparcaudio_driver.
*/
/******************* sparcaudio midlevel - audio output *******************/
static void dbri_audio_output_callback(void * callback_arg, int status)
{
struct sparcaudio_driver *drv = callback_arg;
if (status != -1)
sparcaudio_output_done(drv, 1);
}
static void dbri_start_output(struct sparcaudio_driver *drv,
__u8 * buffer, unsigned long count)
{
struct dbri *dbri = (struct dbri *) drv->private;
dprintk(D_USR, ("DBRI: start audio output buf=%p/%ld\n",
buffer, count));
/* Pipe 4 is audio transmit */
xmit_on_pipe(dbri, 4, buffer, count,
&dbri_audio_output_callback, drv);
#if 0
/* Notify midlevel that we're a DMA-capable driver that
* can accept another buffer immediately. We should probably
* check that we've got enough resources (i.e, descriptors)
* available before doing this, but the default midlevel
* settings only buffer 64KB, which we can handle with 16
* of our DBRI_NO_DESCS (64) descriptors.
*
* This code is #ifdef'ed out because it's caused me more
* problems than it solved. It'd be nice to provide the
* DBRI with a chain of buffers, but the midlevel code is
* so tricky that I really don't want to deal with it.
*/
sparcaudio_output_done(drv, 2);
#endif
}
static void dbri_stop_output(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
reset_pipe(dbri, 4);
}
/******************* sparcaudio midlevel - audio input ********************/
static void dbri_audio_input_callback(void * callback_arg, int status,
unsigned int len)
{
struct sparcaudio_driver * drv =
(struct sparcaudio_driver *) callback_arg;
if (status != -1)
sparcaudio_input_done(drv, 3);
}
static void dbri_start_input(struct sparcaudio_driver *drv,
__u8 * buffer, unsigned long len)
{
struct dbri *dbri = (struct dbri *) drv->private;
/* Pipe 6 is audio receive */
recv_on_pipe(dbri, 6, buffer, len,
&dbri_audio_input_callback, (void *)drv);
dprintk(D_USR, ("DBRI: start audio input buf=%p/%ld\n",
buffer, len));
}
static void dbri_stop_input(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
reset_pipe(dbri, 6);
}
/******************* sparcaudio midlevel - volume & balance ***************/
static int dbri_set_output_volume(struct sparcaudio_driver *drv, int volume)
{
struct dbri *dbri = (struct dbri *) drv->private;
dbri->perchip_info.play.gain = volume;
mmcodec_setgain(dbri, 0);
return 0;
}
static int dbri_get_output_volume(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.gain;
}
static int dbri_set_input_volume(struct sparcaudio_driver *drv, int volume)
{
return 0;
}
static int dbri_get_input_volume(struct sparcaudio_driver *drv)
{
return 0;
}
static int dbri_set_monitor_volume(struct sparcaudio_driver *drv, int volume)
{
return 0;
}
static int dbri_get_monitor_volume(struct sparcaudio_driver *drv)
{
return 0;
}
static int dbri_set_output_balance(struct sparcaudio_driver *drv, int balance)
{
struct dbri *dbri = (struct dbri *) drv->private;
dbri->perchip_info.play.balance = balance;
mmcodec_setgain(dbri, 0);
return 0;
}
static int dbri_get_output_balance(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.balance;
}
static int dbri_set_input_balance(struct sparcaudio_driver *drv, int balance)
{
return 0;
}
static int dbri_get_input_balance(struct sparcaudio_driver *drv)
{
return 0;
}
static int dbri_set_output_muted(struct sparcaudio_driver *drv, int mute)
{
struct dbri *dbri = (struct dbri *) drv->private;
dbri->perchip_info.output_muted = mute;
return 0;
}
static int dbri_get_output_muted(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.output_muted;
}
/******************* sparcaudio midlevel - encoding format ****************/
static int dbri_set_output_channels(struct sparcaudio_driver *drv, int chan)
{
struct dbri *dbri = (struct dbri *) drv->private;
switch (chan) {
case 0:
return 0;
case 1:
dbri->mm.ctrl[1] &= ~CS4215_DFR_STEREO;
break;
case 2:
dbri->mm.ctrl[1] |= CS4215_DFR_STEREO;
break;
default:
return -1;
}
dbri->perchip_info.play.channels = chan;
mmcodec_setctrl(dbri);
mmcodec_init_data(dbri);
return 0;
}
static int dbri_get_output_channels(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.channels;
}
static int dbri_set_input_channels(struct sparcaudio_driver *drv, int chan)
{
return dbri_set_output_channels(drv, chan);
}
static int dbri_get_input_channels(struct sparcaudio_driver *drv)
{
return dbri_get_output_channels(drv);
}
static int dbri_set_output_precision(struct sparcaudio_driver *drv, int prec)
{
return 0;
}
static int dbri_get_output_precision(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.precision;
}
static int dbri_set_input_precision(struct sparcaudio_driver *drv, int prec)
{
return 0;
}
static int dbri_get_input_precision(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.precision;
}
static int dbri_set_output_encoding(struct sparcaudio_driver *drv, int enc)
{
struct dbri *dbri = (struct dbri *) drv->private;
/* For ULAW and ALAW, audio.c enforces precision = 8,
* for LINEAR, precision must be 16
*/
switch (enc) {
case AUDIO_ENCODING_NONE:
return 0;
case AUDIO_ENCODING_ULAW:
dbri->mm.ctrl[1] &= ~3;
dbri->mm.ctrl[1] |= CS4215_DFR_ULAW;
dbri->perchip_info.play.encoding = enc;
dbri->perchip_info.play.precision = 8;
break;
case AUDIO_ENCODING_ALAW:
dbri->mm.ctrl[1] &= ~3;
dbri->mm.ctrl[1] |= CS4215_DFR_ALAW;
dbri->perchip_info.play.encoding = enc;
dbri->perchip_info.play.precision = 8;
break;
case AUDIO_ENCODING_LINEAR:
dbri->mm.ctrl[1] &= ~3;
dbri->mm.ctrl[1] |= CS4215_DFR_LINEAR16;
dbri->perchip_info.play.encoding = enc;
dbri->perchip_info.play.precision = 16;
break;
default:
return -1;
};
mmcodec_setctrl(dbri);
mmcodec_init_data(dbri);
return 0;
}
static int dbri_get_output_encoding(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.encoding;
}
static int dbri_set_input_encoding(struct sparcaudio_driver *drv, int enc)
{
return dbri_set_output_encoding(drv, enc);
}
static int dbri_get_input_encoding(struct sparcaudio_driver *drv)
{
return dbri_get_output_encoding(drv);
}
static int dbri_set_output_rate(struct sparcaudio_driver *drv, int rate)
{
struct dbri *dbri = (struct dbri *) drv->private;
int i;
if (rate == 0)
return 0;
for (i=0; CS4215_FREQ[i].freq; i++) {
if (CS4215_FREQ[i].freq == rate)
break;
}
if (CS4215_FREQ[i].freq == 0)
return -1;
dbri->mm.ctrl[1] &= ~ 0x38;
dbri->mm.ctrl[1] |= CS4215_FREQ[i].csval;
dbri->mm.ctrl[2] &= ~ 0x70;
dbri->mm.ctrl[2] |= CS4215_FREQ[i].xtal;
dbri->perchip_info.play.sample_rate = rate;
mmcodec_setctrl(dbri);
mmcodec_init_data(dbri);
return 0;
}
static int dbri_get_output_rate(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.sample_rate;
}
static int dbri_set_input_rate(struct sparcaudio_driver *drv, int rate)
{
return dbri_set_output_rate(drv, rate);
}
static int dbri_get_input_rate(struct sparcaudio_driver *drv)
{
return dbri_get_output_rate(drv);
}
/******************* sparcaudio midlevel - ports ***********************/
static int dbri_set_output_port(struct sparcaudio_driver *drv, int port)
{
struct dbri *dbri = (struct dbri *) drv->private;
port &= dbri->perchip_info.play.avail_ports;
dbri->perchip_info.play.port = port;
mmcodec_setgain(dbri, 0);
return 0;
}
static int dbri_get_output_port(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.port;
}
static int dbri_set_input_port(struct sparcaudio_driver *drv, int port)
{
struct dbri *dbri = (struct dbri *) drv->private;
port &= dbri->perchip_info.record.avail_ports;
dbri->perchip_info.record.port = port;
mmcodec_setgain(dbri, 0);
return 0;
}
static int dbri_get_input_port(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.record.port;
}
static int dbri_get_output_ports(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.play.avail_ports;
}
static int dbri_get_input_ports(struct sparcaudio_driver *drv)
{
struct dbri *dbri = (struct dbri *) drv->private;
return dbri->perchip_info.record.avail_ports;
}
/******************* sparcaudio midlevel - driver ID ********************/
static void dbri_audio_getdev(struct sparcaudio_driver *drv,
audio_device_t *audinfo)
{
struct dbri *dbri = (struct dbri *) drv->private;
strncpy(audinfo->name, "SUNW,DBRI", sizeof(audinfo->name) - 1);
audinfo->version[0] = dbri->dbri_version;
audinfo->version[1] = '\0';
strncpy(audinfo->config, "onboard1", sizeof(audinfo->config) - 1);
}
static int dbri_sunaudio_getdev_sunos(struct sparcaudio_driver *drv)
{
return AUDIO_DEV_CODEC;
}
/******************* sparcaudio midlevel - open & close ******************/
static int dbri_open(struct inode * inode, struct file * file,
struct sparcaudio_driver *drv)
{
MOD_INC_USE_COUNT;
return 0;
}
static void dbri_release(struct inode * inode, struct file * file,
struct sparcaudio_driver *drv)
{
MOD_DEC_USE_COUNT;
}
static int dbri_ioctl(struct inode * inode, struct file * file,
unsigned int x, unsigned long y,
struct sparcaudio_driver *drv)
{
return -EINVAL;
}
/*********** sparcaudio midlevel - struct sparcaudio_driver ************/
static struct sparcaudio_operations dbri_ops = {
dbri_open,
dbri_release,
dbri_ioctl,
dbri_start_output,
dbri_stop_output,
dbri_start_input,
dbri_stop_input,
dbri_audio_getdev,
dbri_set_output_volume,
dbri_get_output_volume,
dbri_set_input_volume,
dbri_get_input_volume,
dbri_set_monitor_volume,
dbri_get_monitor_volume,
dbri_set_output_balance,
dbri_get_output_balance,
dbri_set_input_balance,
dbri_get_input_balance,
dbri_set_output_channels,
dbri_get_output_channels,
dbri_set_input_channels,
dbri_get_input_channels,
dbri_set_output_precision,
dbri_get_output_precision,
dbri_set_input_precision,
dbri_get_input_precision,
dbri_set_output_port,
dbri_get_output_port,
dbri_set_input_port,
dbri_get_input_port,
dbri_set_output_encoding,
dbri_get_output_encoding,
dbri_set_input_encoding,
dbri_get_input_encoding,
dbri_set_output_rate,
dbri_get_output_rate,
dbri_set_input_rate,
dbri_get_input_rate,
dbri_sunaudio_getdev_sunos,
dbri_get_output_ports,
dbri_get_input_ports,
dbri_set_output_muted,
dbri_get_output_muted,
};
/*
****************************************************************************
************************** ISDN (Hisax) Interface **************************
****************************************************************************
*/
void dbri_isdn_init(struct dbri *dbri)
{
/* Pipe 0: Receive D channel
* Pipe 8: Receive B1 channel
* Pipe 9: Receive B2 channel
* Pipe 1: Transmit D channel
* Pipe 10: Transmit B1 channel
* Pipe 11: Transmit B2 channel
*/
setup_pipe(dbri, 0, D_SDP_HDLC | D_SDP_FROM_SER | D_SDP_LSB);
setup_pipe(dbri, 8, D_SDP_HDLC | D_SDP_FROM_SER | D_SDP_LSB);
setup_pipe(dbri, 9, D_SDP_HDLC | D_SDP_FROM_SER | D_SDP_LSB);
setup_pipe(dbri, 1, D_SDP_HDLC_D | D_SDP_TO_SER | D_SDP_LSB);
setup_pipe(dbri,10, D_SDP_HDLC | D_SDP_TO_SER | D_SDP_LSB);
setup_pipe(dbri,11, D_SDP_HDLC | D_SDP_TO_SER | D_SDP_LSB);
link_time_slot(dbri, 0, PIPEinput, 0, 2, 17);
link_time_slot(dbri, 8, PIPEinput, 0, 8, 0);
link_time_slot(dbri, 9, PIPEinput, 8, 8, 8);
link_time_slot(dbri, 1, PIPEoutput, 1, 2, 17);
link_time_slot(dbri, 10, PIPEoutput, 1, 8, 0);
link_time_slot(dbri, 11, PIPEoutput, 10, 8, 8);
}
int dbri_get_irqnum(int dev)
{
struct dbri *dbri;
if (dev >= num_drivers)
return(0);
dbri = (struct dbri *) drivers[dev].private;
tprintk(("dbri_get_irqnum()\n"));
/* On the sparc, the cpu's irq number is only part of the "irq" */
return (dbri->irq & NR_IRQS);
}
int dbri_get_liu_state(int dev)
{
struct dbri *dbri;
if (dev >= num_drivers)
return(0);
dbri = (struct dbri *) drivers[dev].private;
tprintk(("dbri_get_liu_state() returns %d\n", dbri->liu_state));
return dbri->liu_state;
}
void dbri_liu_activate(int dev, int priority);
void dbri_liu_init(int dev, void (*callback)(void *), void *callback_arg)
{
struct dbri *dbri;
if (dev >= num_drivers)
return;
dbri = (struct dbri *) drivers[dev].private;
tprintk(("dbri_liu_init()\n"));
/* Set callback for LIU state change */
dbri->liu_callback = callback;
dbri->liu_callback_arg = callback_arg;
dbri_isdn_init(dbri);
dbri_liu_activate(dev, 0);
}
void dbri_liu_activate(int dev, int priority)
{
struct dbri *dbri;
int val;
volatile s32 *cmd;
if (dev >= num_drivers)
return;
dbri = (struct dbri *) drivers[dev].private;
tprintk(("dbri_liu_activate()\n"));
if (dbri->liu_state <= 3) {
u32 tmp;
cmd = dbri_cmdlock(dbri);
/* Turn on the ISDN TE interface and request activation */
val = D_NT_IRM_IMM | D_NT_IRM_EN | D_NT_ACT;
#ifdef LOOPBACK_D
val |= D_NT_LLB(4);
#endif
*(cmd++) = DBRI_CMD(D_TE, 0, val);
dbri_cmdsend(dbri, cmd);
/* Activate the interface */
tmp = sbus_readl(dbri->regs + REG0);
tmp |= D_T;
sbus_writel(tmp, dbri->regs + REG0);
}
}
void dbri_liu_deactivate(int dev)
{
struct dbri *dbri;
#if 0
u32 tmp;
#endif
if (dev >= num_drivers)
return;
dbri = (struct dbri *) drivers[dev].private;
tprintk(("dbri_liu_deactivate()\n"));
#if 0
/* Turn off the ISDN TE interface */
tmp = sbus_readl(dbri->regs + REG0);
tmp &= ~D_T;
sbus_writel(tmp, dbri->regs + REG0);
dbri->liu_state = 0;
#endif
}
void dbri_dxmit(int dev, __u8 *buffer, unsigned int count,
void (*callback)(void *, int), void *callback_arg)
{
struct dbri *dbri;
if (dev >= num_drivers)
return;
dbri = (struct dbri *) drivers[dev].private;
/* Pipe 1 is D channel transmit */
xmit_on_pipe(dbri, 1, buffer, count, callback, callback_arg);
}
void dbri_drecv(int dev, __u8 *buffer, unsigned int size,
void (*callback)(void *, int, unsigned int),
void *callback_arg)
{
struct dbri *dbri;
if (dev >= num_drivers)
return;
dbri = (struct dbri *) drivers[dev].private;
/* Pipe 0 is D channel receive */
recv_on_pipe(dbri, 0, buffer, size, callback, callback_arg);
}
int dbri_bopen(int dev, unsigned int chan,
int hdlcmode, u_char xmit_idle_char)
{
struct dbri *dbri;
if (dev >= num_drivers || chan > 1)
return -1;
dbri = (struct dbri *) drivers[dev].private;
if (hdlcmode) {
/* return -1; */
/* Pipe 8/9: receive B1/B2 channel */
setup_pipe(dbri, 8+chan, D_SDP_HDLC | D_SDP_FROM_SER|D_SDP_LSB);
/* Pipe 10/11: transmit B1/B2 channel */
setup_pipe(dbri,10+chan, D_SDP_HDLC | D_SDP_TO_SER | D_SDP_LSB);
} else { /* !hdlcmode means transparent */
/* Pipe 8/9: receive B1/B2 channel */
setup_pipe(dbri, 8+chan, D_SDP_MEM | D_SDP_FROM_SER|D_SDP_LSB);
/* Pipe 10/11: transmit B1/B2 channel */
setup_pipe(dbri,10+chan, D_SDP_MEM | D_SDP_TO_SER | D_SDP_LSB);
}
return 0;
}
void dbri_bclose(int dev, unsigned int chan)
{
struct dbri *dbri;
if (dev >= num_drivers || chan > 1)
return;
dbri = (struct dbri *) drivers[dev].private;
reset_pipe(dbri, 8+chan);
reset_pipe(dbri, 10+chan);
}
void dbri_bxmit(int dev, unsigned int chan,
__u8 *buffer, unsigned long count,
void (*callback)(void *, int),
void *callback_arg)
{
struct dbri *dbri;
if (dev >= num_drivers || chan > 1)
return;
dbri = (struct dbri *) drivers[dev].private;
/* Pipe 10/11 is B1/B2 channel transmit */
xmit_on_pipe(dbri, 10+chan, buffer, count, callback, callback_arg);
}
void dbri_brecv(int dev, unsigned int chan,
__u8 *buffer, unsigned long size,
void (*callback)(void *, int, unsigned int),
void *callback_arg)
{
struct dbri *dbri;
if (dev >= num_drivers || chan > 1)
return;
dbri = (struct dbri *) drivers[dev].private;
/* Pipe 8/9 is B1/B2 channel receive */
recv_on_pipe(dbri, 8+chan, buffer, size, callback, callback_arg);
}
#if defined(DBRI_ISDN)
struct foreign_interface dbri_foreign_interface = {
dbri_get_irqnum,
dbri_get_liu_state,
dbri_liu_init,
dbri_liu_activate,
dbri_liu_deactivate,
dbri_dxmit,
dbri_drecv,
dbri_bopen,
dbri_bclose,
dbri_bxmit,
dbri_brecv
};
EXPORT_SYMBOL(dbri_foreign_interface);
#endif
/*
****************************************************************************
**************************** Initialization ********************************
****************************************************************************
*/
static int dbri_attach(struct sparcaudio_driver *drv,
struct sbus_dev *sdev)
{
struct dbri *dbri;
struct linux_prom_irqs irq;
int err;
if (sdev->prom_name[9] < 'e') {
printk(KERN_ERR "DBRI: unsupported chip version %c found.\n",
sdev->prom_name[9]);
return -EIO;
}
drv->ops = &dbri_ops;
drv->private = kmalloc(sizeof(struct dbri), GFP_KERNEL);
if (drv->private == NULL)
return -ENOMEM;
dbri = (struct dbri *) drv->private;
memset(dbri, 0, sizeof(*dbri));
dbri->dma = sbus_alloc_consistent(sdev,
sizeof(struct dbri_dma),
&dbri->dma_dvma);
memset((void *) dbri->dma, 0, sizeof(struct dbri_dma));
dprintk(D_GEN, ("DBRI: DMA Cmd Block 0x%p (0x%08x)\n",
dbri->dma, dbri->dma_dvma));
dbri->dbri_version = sdev->prom_name[9];
dbri->sdev = sdev;
/* Map the registers into memory. */
dbri->regs_size = sdev->reg_addrs[0].reg_size;
dbri->regs = sbus_ioremap(&sdev->resource[0], 0,
sdev->reg_addrs[0].reg_size,
"DBRI Registers");
if (!dbri->regs) {
printk(KERN_ERR "DBRI: could not allocate registers\n");
sbus_free_consistent(sdev, sizeof(struct dbri_dma),
(void *)dbri->dma, dbri->dma_dvma);
kfree(drv->private);
return -EIO;
}
prom_getproperty(sdev->prom_node, "intr", (char *)&irq, sizeof(irq));
dbri->irq = irq.pri;
err = request_irq(dbri->irq, dbri_intr, SA_SHIRQ,
"DBRI audio/ISDN", dbri);
if (err) {
printk(KERN_ERR "DBRI: Can't get irq %d\n", dbri->irq);
sbus_iounmap(dbri->regs, dbri->regs_size);
sbus_free_consistent(sdev, sizeof(struct dbri_dma),
(void *)dbri->dma, dbri->dma_dvma);
kfree(drv->private);
return err;
}
dbri_initialize(dbri);
err = mmcodec_init(drv);
if(err) {
dbri_detach(dbri);
return err;
}
/* Register ourselves with the midlevel audio driver. */
err = register_sparcaudio_driver(drv,1);
if (err) {
printk(KERN_ERR "DBRI: unable to register audio\n");
dbri_detach(dbri);
return err;
}
dbri->perchip_info.play.active = dbri->perchip_info.play.pause = 0;
dbri->perchip_info.record.active = dbri->perchip_info.record.pause = 0;
printk(KERN_INFO "audio%d at 0x%lx (irq %d) is DBRI(%c)+CS4215(%d)\n",
num_drivers, dbri->regs,
dbri->irq, dbri->dbri_version, dbri->mm.version);
return 0;
}
/* Probe for the dbri chip and then attach the driver. */
static int __init dbri_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
num_drivers = 0;
/* Probe each SBUS for the DBRI chip(s). */
for_all_sbusdev(sdev, sbus) {
/*
* The version is coded in the last character
*/
if (!strncmp(sdev->prom_name, "SUNW,DBRI", 9)) {
dprintk(D_GEN, ("DBRI: Found %s in SBUS slot %d\n",
sdev->prom_name, sdev->slot));
if (num_drivers >= MAX_DRIVERS) {
printk("DBRI: Ignoring slot %d\n", sdev->slot);
continue;
}
if (dbri_attach(&drivers[num_drivers], sdev) == 0)
num_drivers++;
}
}
return (num_drivers > 0) ? 0 : -EIO;
}
static void __exit dbri_exit(void)
{
register int i;
for (i = 0; i < num_drivers; i++) {
dbri_detach((struct dbri *) drivers[i].private);
unregister_sparcaudio_driver(& drivers[i], 1);
num_drivers--;
}
}
module_init(dbri_init);
module_exit(dbri_exit);
MODULE_LICENSE("GPL");
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local Variables:
* c-indent-level: 8
* c-brace-imaginary-offset: 0
* c-brace-offset: -8
* c-argdecl-indent: 8
* c-label-offset: -8
* c-continued-statement-offset: 8
* c-continued-brace-offset: 0
* indent-tabs-mode: nil
* tab-width: 8
* End:
*/
/* $Id: dbri.h,v 1.13 2000/10/13 00:34:24 uzi Exp $
* drivers/sbus/audio/cs4231.h
*
* Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de)
*/
#ifndef _DBRI_H_
#define _DBRI_H_
#include <linux/types.h>
/* DBRI main registers */
#define REG0 0x00UL /* Status and Control */
#define REG1 0x04UL /* Mode and Interrupt */
#define REG2 0x08UL /* Parallel IO */
#define REG3 0x0cUL /* Test */
#define REG8 0x20UL /* Command Queue Pointer */
#define REG9 0x24UL /* Interrupt Queue Pointer */
#define DBRI_NO_CMDS 64
#define DBRI_NO_INTS 1 /* Note: the value of this define was
* originally 2. The ringbuffer to store
* interrupts in dma is currently broken.
* This is a temporary fix until the ringbuffer
* is fixed.
*/
#define DBRI_INT_BLK 64
#define DBRI_NO_DESCS 64
#define DBRI_MM_ONB 1
#define DBRI_MM_SB 2
struct dbri_mem {
volatile __u32 word1;
volatile __u32 ba; /* Transmit/Receive Buffer Address */
volatile __u32 nda; /* Next Descriptor Address */
volatile __u32 word4;
};
#include "cs4215.h"
/* This structure is in a DMA region where it can accessed by both
* the CPU and the DBRI
*/
struct dbri_dma {
volatile s32 cmd[DBRI_NO_CMDS]; /* Place for commands */
volatile s32 intr[DBRI_NO_INTS * DBRI_INT_BLK]; /* Interrupt field */
struct dbri_mem desc[DBRI_NO_DESCS]; /* Xmit/receive descriptors */
};
#define dbri_dma_off(member, elem) \
((u32)(unsigned long) \
(&(((struct dbri_dma *)0)->member[elem])))
enum in_or_out { PIPEinput, PIPEoutput };
enum direction { in, out };
struct dbri_pipe {
u32 sdp; /* SDP command word */
enum direction direction;
int nextpipe; /* Next pipe in linked list */
int prevpipe;
int cycle; /* Offset of timeslot (bits) */
int length; /* Length of timeslot (bits) */
int desc; /* Index of active descriptor*/
volatile __u32 *recv_fixed_ptr; /* Ptr to receive fixed data */
};
struct dbri_desc {
int inuse; /* Boolean flag */
int next; /* Index of next desc, or -1 */
void *buffer; /* CPU view of buffer */
u32 buffer_dvma; /* Device view */
unsigned int len;
void (*output_callback)(void *, int);
void *output_callback_arg;
void (*input_callback)(void *, int, unsigned int);
void *input_callback_arg;
};
/* This structure holds the information for both chips (DBRI & CS4215) */
struct dbri {
int regs_size, irq; /* Needed for unload */
struct sbus_dev *sdev; /* SBUS device info */
volatile struct dbri_dma *dma; /* Pointer to our DMA block */
u32 dma_dvma; /* DBRI visible DMA address */
unsigned long regs; /* dbri HW regs */
int dbri_version; /* 'e' and up is OK */
int dbri_irqp; /* intr queue pointer */
int wait_seen;
struct dbri_pipe pipes[32]; /* DBRI's 32 data pipes */
struct dbri_desc descs[DBRI_NO_DESCS];
int chi_in_pipe;
int chi_out_pipe;
int chi_bpf;
struct cs4215 mm; /* mmcodec special info */
#if 0
/* Where to sleep if busy */
wait_queue_head_t wait, int_wait;
#endif
struct audio_info perchip_info;
/* Track ISDN LIU and notify changes */
int liu_state;
void (*liu_callback)(void *);
void *liu_callback_arg;
};
/* DBRI Reg0 - Status Control Register - defines. (Page 17) */
#define D_P (1<<15) /* Program command & queue pointer valid */
#define D_G (1<<14) /* Allow 4-Word SBus Burst */
#define D_S (1<<13) /* Allow 16-Word SBus Burst */
#define D_E (1<<12) /* Allow 8-Word SBus Burst */
#define D_X (1<<7) /* Sanity Timer Disable */
#define D_T (1<<6) /* Permit activation of the TE interface */
#define D_N (1<<5) /* Permit activation of the NT interface */
#define D_C (1<<4) /* Permit activation of the CHI interface */
#define D_F (1<<3) /* Force Sanity Timer Time-Out */
#define D_D (1<<2) /* Disable Master Mode */
#define D_H (1<<1) /* Halt for Analysis */
#define D_R (1<<0) /* Soft Reset */
/* DBRI Reg1 - Mode and Interrupt Register - defines. (Page 18) */
#define D_LITTLE_END (1<<8) /* Byte Order */
#define D_BIG_END (0<<8) /* Byte Order */
#define D_MRR (1<<4) /* Multiple Error Ack on SBus (readonly) */
#define D_MLE (1<<3) /* Multiple Late Error on SBus (readonly) */
#define D_LBG (1<<2) /* Lost Bus Grant on SBus (readonly) */
#define D_MBE (1<<1) /* Burst Error on SBus (readonly) */
#define D_IR (1<<0) /* Interrupt Indicator (readonly) */
/* DBRI Reg2 - Parallel IO Register - defines. (Page 18) */
#define D_ENPIO3 (1<<7) /* Enable Pin 3 */
#define D_ENPIO2 (1<<6) /* Enable Pin 2 */
#define D_ENPIO1 (1<<5) /* Enable Pin 1 */
#define D_ENPIO0 (1<<4) /* Enable Pin 0 */
#define D_ENPIO (0xf0) /* Enable all the pins */
#define D_PIO3 (1<<3) /* Pin 3: 1: Data mode, 0: Ctrl mode */
#define D_PIO2 (1<<2) /* Pin 2: 1: Onboard PDN */
#define D_PIO1 (1<<1) /* Pin 1: 0: Reset */
#define D_PIO0 (1<<0) /* Pin 0: 1: Speakerbox PDN */
/* DBRI Commands (Page 20) */
#define D_WAIT 0x0 /* Stop execution */
#define D_PAUSE 0x1 /* Flush long pipes */
#define D_JUMP 0x2 /* New command queue */
#define D_IIQ 0x3 /* Initialize Interrupt Queue */
#define D_REX 0x4 /* Report command execution via interrupt */
#define D_SDP 0x5 /* Setup Data Pipe */
#define D_CDP 0x6 /* Continue Data Pipe (reread NULL Pointer) */
#define D_DTS 0x7 /* Define Time Slot */
#define D_SSP 0x8 /* Set short Data Pipe */
#define D_CHI 0x9 /* Set CHI Global Mode */
#define D_NT 0xa /* NT Command */
#define D_TE 0xb /* TE Command */
#define D_CDEC 0xc /* Codec setup */
#define D_TEST 0xd /* No comment */
#define D_CDM 0xe /* CHI Data mode command */
/* Special bits for some commands */
#define D_PIPE(v) ((v)<<0) /* Pipe Nr: 0-15 long, 16-21 short */
/* Setup Data Pipe */
/* IRM */
#define D_SDP_2SAME (1<<18) /* Report 2nd time in a row value rcvd*/
#define D_SDP_CHANGE (2<<18) /* Report any changes */
#define D_SDP_EVERY (3<<18) /* Report any changes */
#define D_SDP_EOL (1<<17) /* EOL interrupt enable */
#define D_SDP_IDLE (1<<16) /* HDLC idle interrupt enable */
/* Pipe data MODE */
#define D_SDP_MEM (0<<13) /* To/from memory */
#define D_SDP_HDLC (2<<13)
#define D_SDP_HDLC_D (3<<13) /* D Channel (prio control)*/
#define D_SDP_SER (4<<13) /* Serial to serial */
#define D_SDP_FIXED (6<<13) /* Short only */
#define D_SDP_MODE(v) ((v)&(7<<13))
#define D_SDP_TO_SER (1<<12) /* Direction */
#define D_SDP_FROM_SER (0<<12) /* Direction */
#define D_SDP_MSB (1<<11) /* Bit order within Byte */
#define D_SDP_LSB (0<<11) /* Bit order within Byte */
#define D_SDP_P (1<<10) /* Pointer Valid */
#define D_SDP_A (1<<8) /* Abort */
#define D_SDP_C (1<<7) /* Clear */
/* Define Time Slot */
#define D_DTS_VI (1<<17) /* Valid Input Time-Slot Descriptor */
#define D_DTS_VO (1<<16) /* Valid Output Time-Slot Descriptor */
#define D_DTS_INS (1<<15) /* Insert Time Slot */
#define D_DTS_DEL (0<<15) /* Delete Time Slot */
#define D_DTS_PRVIN(v) ((v)<<10) /* Previous In Pipe */
#define D_DTS_PRVOUT(v) ((v)<<5) /* Previous Out Pipe */
/* Time Slot defines */
#define D_TS_LEN(v) ((v)<<24) /* Number of bits in this time slot */
#define D_TS_CYCLE(v) ((v)<<14) /* Bit Count at start of TS */
#define D_TS_DI (1<<13) /* Data Invert */
#define D_TS_1CHANNEL (0<<10) /* Single Channel / Normal mode */
#define D_TS_MONITOR (2<<10) /* Monitor pipe */
#define D_TS_NONCONTIG (3<<10) /* Non contiguous mode */
#define D_TS_ANCHOR (7<<10) /* Starting short pipes */
#define D_TS_MON(v) ((v)<<5) /* Monitor Pipe */
#define D_TS_NEXT(v) ((v)<<0) /* Pipe Nr: 0-15 long, 16-21 short */
/* Concentration Highway Interface Modes */
#define D_CHI_CHICM(v) ((v)<<16) /* Clock mode */
#define D_CHI_IR (1<<15) /* Immediate Interrupt Report */
#define D_CHI_EN (1<<14) /* CHIL Interrupt enabled */
#define D_CHI_OD (1<<13) /* Open Drain Enable */
#define D_CHI_FE (1<<12) /* Sample CHIFS on Rising Frame Edge */
#define D_CHI_FD (1<<11) /* Frame Drive */
#define D_CHI_BPF(v) ((v)<<0) /* Bits per Frame */
/* NT: These are here for completeness */
#define D_NT_FBIT (1<<17) /* Frame Bit */
#define D_NT_NBF (1<<16) /* Number of bad frames to loose framing */
#define D_NT_IRM_IMM (1<<15) /* Interrupt Report & Mask: Immediate */
#define D_NT_IRM_EN (1<<14) /* Interrupt Report & Mask: Enable */
#define D_NT_ISNT (1<<13) /* Configfure interface as NT */
#define D_NT_FT (1<<12) /* Fixed Timing */
#define D_NT_EZ (1<<11) /* Echo Channel is Zeros */
#define D_NT_IFA (1<<10) /* Inhibit Final Activation */
#define D_NT_ACT (1<<9) /* Activate Interface */
#define D_NT_MFE (1<<8) /* Multiframe Enable */
#define D_NT_RLB(v) ((v)<<5) /* Remote Loopback */
#define D_NT_LLB(v) ((v)<<2) /* Local Loopback */
#define D_NT_FACT (1<<1) /* Force Activation */
#define D_NT_ABV (1<<0) /* Activate Bipolar Violation */
/* Codec Setup */
#define D_CDEC_CK(v) ((v)<<24) /* Clock Select */
#define D_CDEC_FED(v) ((v)<<12) /* FSCOD Falling Edge Delay */
#define D_CDEC_RED(v) ((v)<<0) /* FSCOD Rising Edge Delay */
/* Test */
#define D_TEST_RAM(v) ((v)<<16) /* RAM Pointer */
#define D_TEST_SIZE(v) ((v)<<11) /* */
#define D_TEST_ROMONOFF 0x5 /* Toggle ROM opcode monitor on/off */
#define D_TEST_PROC 0x6 /* MicroProcessor test */
#define D_TEST_SER 0x7 /* Serial-Controller test */
#define D_TEST_RAMREAD 0x8 /* Copy from Ram to system memory */
#define D_TEST_RAMWRITE 0x9 /* Copy into Ram from system memory */
#define D_TEST_RAMBIST 0xa /* RAM Built-In Self Test */
#define D_TEST_MCBIST 0xb /* Microcontroller Built-In Self Test */
#define D_TEST_DUMP 0xe /* ROM Dump */
/* CHI Data Mode */
#define D_CDM_THI (1<<8) /* Transmit Data on CHIDR Pin */
#define D_CDM_RHI (1<<7) /* Receive Data on CHIDX Pin */
#define D_CDM_RCE (1<<6) /* Receive on Rising Edge of CHICK */
#define D_CDM_XCE (1<<2) /* Transmit Data on Rising Edge of CHICK */
#define D_CDM_XEN (1<<1) /* Transmit Highway Enable */
#define D_CDM_REN (1<<0) /* Receive Highway Enable */
/* The Interrupts */
#define D_INTR_BRDY 1 /* Buffer Ready for processing */
#define D_INTR_MINT 2 /* Marked Interrupt in RD/TD */
#define D_INTR_IBEG 3 /* Flag to idle transition detected (HDLC) */
#define D_INTR_IEND 4 /* Idle to flag transition detected (HDLC) */
#define D_INTR_EOL 5 /* End of List */
#define D_INTR_CMDI 6 /* Command has bean read */
#define D_INTR_XCMP 8 /* Transmission of frame complete */
#define D_INTR_SBRI 9 /* BRI status change info */
#define D_INTR_FXDT 10 /* Fixed data change */
#define D_INTR_CHIL 11 /* CHI lost frame sync (channel 36 only) */
#define D_INTR_COLL 11 /* Unrecoverable D-Channel collision */
#define D_INTR_DBYT 12 /* Dropped by frame slip */
#define D_INTR_RBYT 13 /* Repeated by frame slip */
#define D_INTR_LINT 14 /* Lost Interrupt */
#define D_INTR_UNDR 15 /* DMA underrun */
#define D_INTR_TE 32
#define D_INTR_NT 34
#define D_INTR_CHI 36
#define D_INTR_CMD 38
#define D_INTR_GETCHAN(v) (((v)>>24) & 0x3f)
#define D_INTR_GETCODE(v) (((v)>>20) & 0xf)
#define D_INTR_GETCMD(v) (((v)>>16) & 0xf)
#define D_INTR_GETVAL(v) ((v) & 0xffff)
#define D_INTR_GETRVAL(v) ((v) & 0xfffff)
#define D_P_0 0 /* TE receive anchor */
#define D_P_1 1 /* TE transmit anchor */
#define D_P_2 2 /* NT transmit anchor */
#define D_P_3 3 /* NT receive anchor */
#define D_P_4 4 /* CHI send data */
#define D_P_5 5 /* CHI receive data */
#define D_P_6 6 /* */
#define D_P_7 7 /* */
#define D_P_8 8 /* */
#define D_P_9 9 /* */
#define D_P_10 10 /* */
#define D_P_11 11 /* */
#define D_P_12 12 /* */
#define D_P_13 13 /* */
#define D_P_14 14 /* */
#define D_P_15 15 /* */
#define D_P_16 16 /* CHI anchor pipe */
#define D_P_17 17 /* CHI send */
#define D_P_18 18 /* CHI receive */
#define D_P_19 19 /* CHI receive */
#define D_P_20 20 /* CHI receive */
#define D_P_21 21 /* */
#define D_P_22 22 /* */
#define D_P_23 23 /* */
#define D_P_24 24 /* */
#define D_P_25 25 /* */
#define D_P_26 26 /* */
#define D_P_27 27 /* */
#define D_P_28 28 /* */
#define D_P_29 29 /* */
#define D_P_30 30 /* */
#define D_P_31 31 /* */
/* Transmit descriptor defines */
#define DBRI_TD_F (1<<31) /* End of Frame */
#define DBRI_TD_D (1<<30) /* Do not append CRC */
#define DBRI_TD_CNT(v) ((v)<<16) /* Number of valid bytes in the buffer */
#define DBRI_TD_B (1<<15) /* Final interrupt */
#define DBRI_TD_M (1<<14) /* Marker interrupt */
#define DBRI_TD_I (1<<13) /* Transmit Idle Characters */
#define DBRI_TD_FCNT(v) (v) /* Flag Count */
#define DBRI_TD_UNR (1<<3) /* Underrun: transmitter is out of data */
#define DBRI_TD_ABT (1<<2) /* Abort: frame aborted */
#define DBRI_TD_TBC (1<<0) /* Transmit buffer Complete */
#define DBRI_TD_STATUS(v) ((v)&0xff) /* Transmit status */
/* Receive descriptor defines */
#define DBRI_RD_F (1<<31) /* End of Frame */
#define DBRI_RD_C (1<<30) /* Completed buffer */
#define DBRI_RD_B (1<<15) /* Final interrupt */
#define DBRI_RD_M (1<<14) /* Marker interrupt */
#define DBRI_RD_BCNT(v) (v) /* Buffer size */
#define DBRI_RD_CRC (1<<7) /* 0: CRC is correct */
#define DBRI_RD_BBC (1<<6) /* 1: Bad Byte received */
#define DBRI_RD_ABT (1<<5) /* Abort: frame aborted */
#define DBRI_RD_OVRN (1<<3) /* Overrun: data lost */
#define DBRI_RD_STATUS(v) ((v)&0xff) /* Receive status */
#define DBRI_RD_CNT(v) (((v)>>16)&0x1fff) /* Number of valid bytes in the buffer */
#endif /* _DBRI_H_ */
/* $Id: dmy.c,v 1.10 2001/10/08 22:19:50 davem Exp $
* drivers/sbus/audio/dummy.c
*
* Copyright 1998 Derrick J Brashear (shadow@andrew.cmu.edu)
*
* This is a dummy lowlevel driver. Consider it a distant cousin of
* /proc/audio; It pretends to be a piece of audio hardware, and writes
* to a file instead. (or will shortly)
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/soundcard.h>
#include <linux/delay.h>
#include <asm/openprom.h>
#include <asm/oplib.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/sbus.h>
#include <asm/audioio.h>
#include "dummy.h"
#define MAX_DRIVERS 1
static struct sparcaudio_driver drivers[MAX_DRIVERS];
static int num_drivers;
static int dummy_play_gain(struct sparcaudio_driver *drv, int value,
unsigned char balance);
static int dummy_record_gain(struct sparcaudio_driver *drv, int value,
unsigned char balance);
static int dummy_output_muted(struct sparcaudio_driver *drv, int value);
static int dummy_attach(struct sparcaudio_driver *drv) __init;
static int
dummy_set_output_encoding(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (value != 0) {
dummy_chip->perchip_info.play.encoding = value;
return 0;
}
return -EINVAL;
}
static int
dummy_set_input_encoding(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (value != 0) {
dummy_chip->perchip_info.record.encoding = value;
return 0;
}
return -EINVAL;
}
static int dummy_get_output_encoding(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.encoding;
}
static int dummy_get_input_encoding(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.encoding;
}
static int
dummy_set_output_rate(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (value != 0) {
dummy_chip->perchip_info.play.sample_rate = value;
return 0;
}
return -EINVAL;
}
static int
dummy_set_input_rate(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (value != 0) {
dummy_chip->perchip_info.record.sample_rate = value;
return 0;
}
return -EINVAL;
}
static int dummy_get_output_rate(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.sample_rate;
}
static int dummy_get_input_rate(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.sample_rate;
}
/* Generically we support 4 channels. This does 2 */
static int
dummy_set_output_channels(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
switch (value) {
case 1:
case 2:
break;
default:
return -(EINVAL);
};
dummy_chip->perchip_info.play.channels = value;
return 0;
}
/* Generically we support 4 channels. This does 2 */
static int
dummy_set_input_channels(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
switch (value) {
case 1:
case 2:
break;
default:
return -(EINVAL);
};
dummy_chip->perchip_info.record.channels = value;
return 0;
}
static int dummy_get_input_channels(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.channels;
}
static int dummy_get_output_channels(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.channels;
}
static int dummy_get_output_precision(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.precision;
}
static int dummy_get_input_precision(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.precision;
}
static int dummy_set_output_precision(struct sparcaudio_driver *drv, int val)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.play.precision = val;
return dummy_chip->perchip_info.play.precision;
}
static int dummy_set_input_precision(struct sparcaudio_driver *drv, int val)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.record.precision = val;
return dummy_chip->perchip_info.record.precision;
}
/* Set output mute */
static int dummy_output_muted(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (!value)
dummy_chip->perchip_info.output_muted = 0;
else
dummy_chip->perchip_info.output_muted = 1;
return 0;
}
static int dummy_get_output_muted(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.output_muted;
}
static int dummy_get_formats(struct sparcaudio_driver *drv)
{
return (AFMT_MU_LAW | AFMT_A_LAW |
AFMT_U8 | AFMT_IMA_ADPCM |
AFMT_S16_LE | AFMT_S16_BE);
}
static int dummy_get_output_ports(struct sparcaudio_driver *drv)
{
return (AUDIO_LINE_OUT | AUDIO_SPEAKER | AUDIO_HEADPHONE);
}
static int dummy_get_input_ports(struct sparcaudio_driver *drv)
{
return (AUDIO_ANALOG_LOOPBACK);
}
/* Set chip "output" port */
static int dummy_set_output_port(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.play.port = value;
return value;
}
static int dummy_set_input_port(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.record.port = value;
return value;
}
static int dummy_get_output_port(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.port;
}
static int dummy_get_input_port(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.port;
}
static int dummy_get_output_error(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return (int) dummy_chip->perchip_info.play.error;
}
static int dummy_get_input_error(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return (int) dummy_chip->perchip_info.record.error;
}
static int dummy_get_output_samples(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.samples;
}
static int dummy_get_output_pause(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return (int) dummy_chip->perchip_info.play.pause;
}
static int dummy_set_output_volume(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_play_gain(drv, value, dummy_chip->perchip_info.play.balance);
return 0;
}
static int dummy_get_output_volume(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.play.gain;
}
static int dummy_set_output_balance(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.play.balance = value;
dummy_play_gain(drv, dummy_chip->perchip_info.play.gain,
dummy_chip->perchip_info.play.balance);
return 0;
}
static int dummy_get_output_balance(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return (int) dummy_chip->perchip_info.play.balance;
}
/* Set chip play gain */
static int dummy_play_gain(struct sparcaudio_driver *drv,
int value, unsigned char balance)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
int tmp = 0, r, l, r_adj, l_adj;
r = l = value;
if (balance < AUDIO_MID_BALANCE) {
r = (int) (value -
((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT));
if (r < 0)
r = 0;
} else if (balance > AUDIO_MID_BALANCE) {
l = (int) (value -
((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT));
if (l < 0)
l = 0;
}
(l == 0) ? (l_adj = DUMMY_MAX_DEV_ATEN) : (l_adj = DUMMY_MAX_ATEN -
(l * (DUMMY_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1)));
(r == 0) ? (r_adj = DUMMY_MAX_DEV_ATEN) : (r_adj = DUMMY_MAX_ATEN -
(r * (DUMMY_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1)));
if ((value == 0) || (value == AUDIO_MAX_GAIN)) {
tmp = value;
} else {
if (value == l) {
tmp = ((DUMMY_MAX_ATEN - l_adj) * (AUDIO_MAX_GAIN + 1) /
(DUMMY_MAX_ATEN + 1));
} else if (value == r) {
tmp = ((DUMMY_MAX_ATEN - r_adj) * (AUDIO_MAX_GAIN + 1) /
(DUMMY_MAX_ATEN + 1));
}
}
dummy_chip->perchip_info.play.gain = tmp;
return 0;
}
static int dummy_get_input_samples(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.samples;
}
static int dummy_get_input_pause(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return (int) dummy_chip->perchip_info.record.pause;
}
static int dummy_set_monitor_volume(struct sparcaudio_driver *drv, int value)
{
return 0;
}
static int dummy_get_monitor_volume(struct sparcaudio_driver *drv)
{
return 0;
}
static int dummy_set_input_volume(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_record_gain(drv, value, dummy_chip->perchip_info.record.balance);
return 0;
}
static int dummy_get_input_volume(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return dummy_chip->perchip_info.record.gain;
}
static int dummy_set_input_balance(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.record.balance = value;
dummy_record_gain(drv, dummy_chip->perchip_info.record.gain,
dummy_chip->perchip_info.play.balance);
return 0;
}
static int dummy_get_input_balance(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
return (int) dummy_chip->perchip_info.record.balance;
}
static int dummy_record_gain(struct sparcaudio_driver *drv,
int value, unsigned char balance)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
int tmp = 0, r, l, r_adj, l_adj;
r = l = value;
if (balance < AUDIO_MID_BALANCE) {
r = (int) (value -
((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT));
if (r < 0)
r = 0;
} else if (balance > AUDIO_MID_BALANCE) {
l = (int) (value -
((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT));
if (l < 0)
l = 0;
}
(l == 0) ? (l_adj = DUMMY_MAX_DEV_ATEN) : (l_adj = DUMMY_MAX_ATEN -
(l * (DUMMY_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1)));
(r == 0) ? (r_adj = DUMMY_MAX_DEV_ATEN) : (r_adj = DUMMY_MAX_ATEN -
(r * (DUMMY_MAX_ATEN + 1) /
(AUDIO_MAX_GAIN + 1)));
if ((value == 0) || (value == AUDIO_MAX_GAIN)) {
tmp = value;
} else {
if (value == l) {
tmp = ((DUMMY_MAX_ATEN - l_adj) * (AUDIO_MAX_GAIN + 1) /
(DUMMY_MAX_ATEN + 1));
} else if (value == r) {
tmp = ((DUMMY_MAX_ATEN - r_adj) * (AUDIO_MAX_GAIN + 1) /
(DUMMY_MAX_ATEN + 1));
}
}
dummy_chip->perchip_info.record.gain = tmp;
return 0;
}
/* Reset the audio chip to a sane state. */
static void dummy_chip_reset(struct sparcaudio_driver *drv)
{
dummy_set_output_encoding(drv, AUDIO_ENCODING_ULAW);
dummy_set_output_rate(drv, DUMMY_RATE);
dummy_set_output_channels(drv, DUMMY_CHANNELS);
dummy_set_output_precision(drv, DUMMY_PRECISION);
dummy_set_output_balance(drv, AUDIO_MID_BALANCE);
dummy_set_output_volume(drv, DUMMY_DEFAULT_PLAYGAIN);
dummy_set_output_port(drv, AUDIO_SPEAKER);
dummy_output_muted(drv, 0);
dummy_set_input_encoding(drv, AUDIO_ENCODING_ULAW);
dummy_set_input_rate(drv, DUMMY_RATE);
dummy_set_input_channels(drv, DUMMY_CHANNELS);
dummy_set_input_precision(drv, DUMMY_PRECISION);
dummy_set_input_balance(drv, AUDIO_MID_BALANCE);
dummy_set_input_volume(drv, DUMMY_DEFAULT_PLAYGAIN);
dummy_set_input_port(drv, AUDIO_SPEAKER);
}
static int dummy_open(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
/* Set the default audio parameters if not already in use. */
if (file->f_mode & FMODE_WRITE) {
if (!(drv->flags & SDF_OPEN_WRITE) &&
(dummy_chip->perchip_info.play.active == 0)) {
dummy_chip->perchip_info.play.open = 1;
dummy_chip->perchip_info.play.samples =
dummy_chip->perchip_info.play.error = 0;
}
}
if (file->f_mode & FMODE_READ) {
if (!(drv->flags & SDF_OPEN_READ) &&
(dummy_chip->perchip_info.record.active == 0)) {
dummy_chip->perchip_info.record.open = 1;
dummy_chip->perchip_info.record.samples =
dummy_chip->perchip_info.record.error = 0;
}
}
MOD_INC_USE_COUNT;
return 0;
}
static void dummy_release(struct inode * inode, struct file * file, struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (file->f_mode & FMODE_WRITE) {
dummy_chip->perchip_info.play.active =
dummy_chip->perchip_info.play.open = 0;
}
if (file->f_mode & FMODE_READ) {
dummy_chip->perchip_info.record.active =
dummy_chip->perchip_info.record.open = 0;
}
MOD_DEC_USE_COUNT;
}
static void dummy_output_done_task(void * arg)
{
struct sparcaudio_driver *drv = (struct sparcaudio_driver *) arg;
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
sparcaudio_output_done(drv, 1);
if (dummy_chip->perchip_info.record.active)
sparcaudio_input_done(drv, 1);
}
static void dummy_start_output(struct sparcaudio_driver *drv, __u8 * buffer,
unsigned long count)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
if (dummy_chip->perchip_info.play.pause || !count)
return;
dummy_chip->perchip_info.play.active = 1;
/* fake an "interrupt" to deal with this block */
INIT_LIST_HEAD(&dummy_chip->tqueue.list);
dummy_chip->tqueue.sync = 0;
dummy_chip->tqueue.routine = dummy_output_done_task;
dummy_chip->tqueue.data = drv;
queue_task(&dummy_chip->tqueue, &tq_immediate);
mark_bh(IMMEDIATE_BH);
}
static void dummy_start_input(struct sparcaudio_driver *drv, __u8 * buffer,
unsigned long count)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.record.active = 1;
}
static void dummy_stop_output(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.play.active = 0;
}
static void dummy_stop_input(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.record.active = 0;
}
static int dummy_set_output_pause(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.play.pause = value;
if (!value)
sparcaudio_output_done(drv, 0);
return value;
}
static int dummy_set_input_pause(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
dummy_chip->perchip_info.record.pause = value;
/* This should probably cause play pause. */
return value;
}
static int dummy_set_input_error(struct sparcaudio_driver *drv, int value)
{
return 0;
}
static int dummy_set_output_error(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
int i;
i = dummy_chip->perchip_info.play.error;
dummy_chip->perchip_info.play.error = value;
return i;
}
static int dummy_set_output_samples(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
int i;
i = dummy_chip->perchip_info.play.samples;
dummy_chip->perchip_info.play.samples = value;
return i;
}
static int dummy_set_input_samples(struct sparcaudio_driver *drv, int value)
{
struct dummy_chip *dummy_chip = (struct dummy_chip *) drv->private;
int i;
i = dummy_chip->perchip_info.play.samples;
dummy_chip->perchip_info.record.samples = value;
return i;
}
/* In order to fake things which care out, play we're a 4231 */
static void dummy_audio_getdev(struct sparcaudio_driver *drv,
audio_device_t * audinfo)
{
strncpy(audinfo->name, "SUNW,cs4231", sizeof(audinfo->name) - 1);
strncpy(audinfo->version, "a", sizeof(audinfo->version) - 1);
strncpy(audinfo->config, "onboard1", sizeof(audinfo->config) - 1);
}
static int dummy_audio_getdev_sunos(struct sparcaudio_driver *drv)
{
return 5;
}
static struct sparcaudio_operations dummy_ops = {
dummy_open,
dummy_release,
NULL,
dummy_start_output,
dummy_stop_output,
dummy_start_input,
dummy_stop_input,
dummy_audio_getdev,
dummy_set_output_volume,
dummy_get_output_volume,
dummy_set_input_volume,
dummy_get_input_volume,
dummy_set_monitor_volume,
dummy_get_monitor_volume,
dummy_set_output_balance,
dummy_get_output_balance,
dummy_set_input_balance,
dummy_get_input_balance,
dummy_set_output_channels,
dummy_get_output_channels,
dummy_set_input_channels,
dummy_get_input_channels,
dummy_set_output_precision,
dummy_get_output_precision,
dummy_set_input_precision,
dummy_get_input_precision,
dummy_set_output_port,
dummy_get_output_port,
dummy_set_input_port,
dummy_get_input_port,
dummy_set_output_encoding,
dummy_get_output_encoding,
dummy_set_input_encoding,
dummy_get_input_encoding,
dummy_set_output_rate,
dummy_get_output_rate,
dummy_set_input_rate,
dummy_get_input_rate,
dummy_audio_getdev_sunos,
dummy_get_output_ports,
dummy_get_input_ports,
dummy_output_muted,
dummy_get_output_muted,
dummy_set_output_pause,
dummy_get_output_pause,
dummy_set_input_pause,
dummy_get_input_pause,
dummy_set_output_samples,
dummy_get_output_samples,
dummy_set_input_samples,
dummy_get_input_samples,
dummy_set_output_error,
dummy_get_output_error,
dummy_set_input_error,
dummy_get_input_error,
dummy_get_formats,
};
/* Attach to an dummy chip given its PROM node. */
static int __init dummy_attach(struct sparcaudio_driver *drv)
{
struct dummy_chip *dummy_chip;
int err;
/* Allocate our private information structure. */
drv->private = kmalloc(sizeof(struct dummy_chip), GFP_KERNEL);
if (drv->private == NULL)
return -ENOMEM;
/* Point at the information structure and initialize it. */
drv->ops = &dummy_ops;
dummy_chip = (struct dummy_chip *) drv->private;
/* Reset parameters. */
dummy_chip_reset(drv);
/* Register ourselves with the midlevel audio driver. */
err = register_sparcaudio_driver(drv, 2);
if (err < 0) {
printk(KERN_ERR "dummy: unable to register\n");
kfree(drv->private);
return -EIO;
}
dummy_chip->perchip_info.play.active =
dummy_chip->perchip_info.play.pause = 0;
dummy_chip->perchip_info.play.avail_ports = (AUDIO_HEADPHONE |
AUDIO_SPEAKER |
AUDIO_LINE_OUT);
/* Announce the hardware to the user. */
printk(KERN_INFO "audio%d: dummy at 0x0 irq 0\n", drv->index);
/* Success! */
return 0;
}
/* Detach from an dummy chip given the device structure. */
static void __exit dummy_detach(struct sparcaudio_driver *drv)
{
unregister_sparcaudio_driver(drv, 2);
kfree(drv->private);
}
/* Probe for the dummy chip and then attach the driver. */
static int __init dummy_init(void)
{
num_drivers = 0;
/* Add support here for specifying multiple dummies to attach at once. */
if (dummy_attach(&drivers[num_drivers]) == 0)
num_drivers++;
/* Only return success if we found some dummy chips. */
return (num_drivers > 0) ? 0 : -EIO;
}
static void __exit dummy_exit(void)
{
int i;
for (i = 0; i < num_drivers; i++) {
dummy_detach(&drivers[i]);
num_drivers--;
}
}
module_init(dummy_init);
module_exit(dummy_exit);
MODULE_LICENSE("GPL");
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: nil
* tab-width: 8
* End:
*/
/* $Id: dummy.h,v 1.3 1999/09/21 14:37:41 davem Exp $
* drivers/sbus/audio/dummy.h
*
* Copyright (C) 1998 Derrick J. Brashear (shadow@dementia.org)
*/
#ifndef _DUMMY_H_
#define _DUMMY_H_
#include <linux/types.h>
#include <linux/tqueue.h>
#define DUMMY_OUTFILE "/usr/tmp/dummy.au"
/* Our structure for each chip */
struct dummy_chip {
struct audio_info perchip_info;
unsigned int playlen;
struct tq_struct tqueue;
};
#define DUMMY_MIN_ATEN (0)
#define DUMMY_MAX_ATEN (31)
#define DUMMY_MAX_DEV_ATEN (63)
#define DUMMY_MON_MIN_ATEN (0)
#define DUMMY_MON_MAX_ATEN (63)
#define DUMMY_DEFAULT_PLAYGAIN (132)
#define DUMMY_DEFAULT_RECGAIN (126)
#define DUMMY_MIN_GAIN (0)
#define DUMMY_MAX_GAIN (15)
#define DUMMY_PRECISION (8) /* # of bits/sample */
#define DUMMY_CHANNELS (1) /* channels/sample */
#define DUMMY_RATE (8000) /* default sample rate */
#endif /* _DUMMY_H_ */
......@@ -231,414 +231,4 @@ typedef struct audio_device {
*/
#define AUDIO_DIAG_LOOPBACK _IOW('A', 101, int)
/*
* Linux kernel internal implementation.
*/
#ifdef __KERNEL__
#include <linux/fs.h>
#include <linux/tqueue.h>
#include <linux/wait.h>
#define SDF_OPEN_WRITE 0x00000001
#define SDF_OPEN_READ 0x00000002
struct sparcaudio_ringbuffer
{
__u8 *rb_start, *rb_end; /* start, end of this memory buffer */
__u8 *rb_in, *rb_out; /* input, output pointers */
int rb_fragsize; /* size of an audio frag */
int rb_numfrags; /* number of frags */
int rb_count, rb_hiwat, rb_lowat; /* bytes in use, hi/lo wat points */
int rb_bufsize; /* total size of buffer */
};
struct sparcaudio_driver
{
const char * name;
struct sparcaudio_operations *ops;
void *private;
unsigned long flags;
struct strevent *sd_siglist;
/* duplex: 0=simplex, 1=duplex, 2=loop */
int sd_sigflags, duplex;
/* Which audio device are we? */
int index;
/* This device */
struct sbus_dev *dev;
/* Processes blocked on open() sit here. */
wait_queue_head_t open_wait;
/* Task queue for this driver's bottom half. */
struct tq_struct tqueue;
/* Start of ring buffer support */
__u8 *input_buffer, *output_buffer;
/* Support for a circular queue of output buffers. */
__u8 **output_buffers;
size_t *output_sizes, output_size, output_buffer_size;
int num_output_buffers, output_front, output_rear, output_offset;
int output_count, output_active, playing_count, output_eof;
wait_queue_head_t output_write_wait, output_drain_wait;
char *output_notify;
/* Support for a circular queue of input buffers. */
__u8 **input_buffers;
size_t *input_sizes, input_size, input_buffer_size;
int num_input_buffers, input_front, input_rear, input_offset;
int input_count, input_active, recording_count;
wait_queue_head_t input_read_wait;
/* Hack to make it look like we support variable size buffers. */
int buffer_size;
int mixer_modify_counter;
};
struct sparcaudio_operations
{
int (*open)(struct inode *, struct file *, struct sparcaudio_driver *);
void (*release)(struct inode *, struct file *, struct sparcaudio_driver *);
int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long,
struct sparcaudio_driver *);
/* Ask driver to begin playing a buffer. */
void (*start_output)(struct sparcaudio_driver *, __u8 *, unsigned long);
/* Ask driver to stop playing a buffer. */
void (*stop_output)(struct sparcaudio_driver *);
/* Ask driver to begin recording into a buffer. */
void (*start_input)(struct sparcaudio_driver *, __u8 *, unsigned long);
/* Ask driver to stop recording. */
void (*stop_input)(struct sparcaudio_driver *);
/* Return driver name/version to caller. (/dev/audio specific) */
void (*sunaudio_getdev)(struct sparcaudio_driver *, audio_device_t *);
/* Get and set the output volume. (0-255) */
int (*set_output_volume)(struct sparcaudio_driver *, int);
int (*get_output_volume)(struct sparcaudio_driver *);
/* Get and set the input volume. (0-255) */
int (*set_input_volume)(struct sparcaudio_driver *, int);
int (*get_input_volume)(struct sparcaudio_driver *);
/* Get and set the monitor volume. (0-255) */
int (*set_monitor_volume)(struct sparcaudio_driver *, int);
int (*get_monitor_volume)(struct sparcaudio_driver *);
/* Get and set the output balance. (0-64) */
int (*set_output_balance)(struct sparcaudio_driver *, int);
int (*get_output_balance)(struct sparcaudio_driver *);
/* Get and set the input balance. (0-64) */
int (*set_input_balance)(struct sparcaudio_driver *, int);
int (*get_input_balance)(struct sparcaudio_driver *);
/* Get and set the output channels. (1-4) */
int (*set_output_channels)(struct sparcaudio_driver *, int);
int (*get_output_channels)(struct sparcaudio_driver *);
/* Get and set the input channels. (1-4) */
int (*set_input_channels)(struct sparcaudio_driver *, int);
int (*get_input_channels)(struct sparcaudio_driver *);
/* Get and set the output precision. (8-32) */
int (*set_output_precision)(struct sparcaudio_driver *, int);
int (*get_output_precision)(struct sparcaudio_driver *);
/* Get and set the input precision. (8-32) */
int (*set_input_precision)(struct sparcaudio_driver *, int);
int (*get_input_precision)(struct sparcaudio_driver *);
/* Get and set the output port. () */
int (*set_output_port)(struct sparcaudio_driver *, int);
int (*get_output_port)(struct sparcaudio_driver *);
/* Get and set the input port. () */
int (*set_input_port)(struct sparcaudio_driver *, int);
int (*get_input_port)(struct sparcaudio_driver *);
/* Get and set the output encoding. () */
int (*set_output_encoding)(struct sparcaudio_driver *, int);
int (*get_output_encoding)(struct sparcaudio_driver *);
/* Get and set the input encoding. () */
int (*set_input_encoding)(struct sparcaudio_driver *, int);
int (*get_input_encoding)(struct sparcaudio_driver *);
/* Get and set the output rate. () */
int (*set_output_rate)(struct sparcaudio_driver *, int);
int (*get_output_rate)(struct sparcaudio_driver *);
/* Get and set the input rate. () */
int (*set_input_rate)(struct sparcaudio_driver *, int);
int (*get_input_rate)(struct sparcaudio_driver *);
/* Return driver number to caller. (SunOS /dev/audio specific) */
int (*sunaudio_getdev_sunos)(struct sparcaudio_driver *);
/* Get available ports */
int (*get_output_ports)(struct sparcaudio_driver *);
int (*get_input_ports)(struct sparcaudio_driver *);
/* Get and set output mute */
int (*set_output_muted)(struct sparcaudio_driver *, int);
int (*get_output_muted)(struct sparcaudio_driver *);
/* Get and set output pause */
int (*set_output_pause)(struct sparcaudio_driver *, int);
int (*get_output_pause)(struct sparcaudio_driver *);
/* Get and set input pause */
int (*set_input_pause)(struct sparcaudio_driver *, int);
int (*get_input_pause)(struct sparcaudio_driver *);
/* Get and set output samples */
int (*set_output_samples)(struct sparcaudio_driver *, int);
int (*get_output_samples)(struct sparcaudio_driver *);
/* Get and set input samples */
int (*set_input_samples)(struct sparcaudio_driver *, int);
int (*get_input_samples)(struct sparcaudio_driver *);
/* Get and set output error */
int (*set_output_error)(struct sparcaudio_driver *, int);
int (*get_output_error)(struct sparcaudio_driver *);
/* Get and set input error */
int (*set_input_error)(struct sparcaudio_driver *, int);
int (*get_input_error)(struct sparcaudio_driver *);
/* Get supported encodings */
int (*get_formats)(struct sparcaudio_driver *);
};
extern int register_sparcaudio_driver(struct sparcaudio_driver *, int);
extern int unregister_sparcaudio_driver(struct sparcaudio_driver *, int);
extern void sparcaudio_output_done(struct sparcaudio_driver *, int);
extern void sparcaudio_input_done(struct sparcaudio_driver *, int);
#endif
/* Device minor numbers */
#define SPARCAUDIO_MIXER_MINOR 0
/* No sequencer (1) */
/* No midi (2) */
#define SPARCAUDIO_DSP_MINOR 3
#define SPARCAUDIO_AUDIO_MINOR 4
#define SPARCAUDIO_DSP16_MINOR 5
#define SPARCAUDIO_STATUS_MINOR 6
#define SPARCAUDIO_AUDIOCTL_MINOR 7
/* No sequencer l2 (8) */
/* No sound processor (9) */
/* allocate 2^SPARCAUDIO_DEVICE_SHIFT minors per audio device */
#define SPARCAUDIO_DEVICE_SHIFT 4
/* With the coming of dummy devices this should perhaps be as high as 5? */
#define SPARCAUDIO_MAX_DEVICES 3
/* Streams crap for realaudio */
typedef
struct strevent {
struct strevent *se_next; /* next event for this stream or NULL*/
struct strevent *se_prev; /* previous event for this stream or last
* event if this is the first one*/
pid_t se_pid; /* process to be signaled */
short se_evs; /* events wanted */
} strevent_t;
typedef
struct stdata
{
struct stdata *sd_next ; /* all stdatas are linked together */
struct stdata *sd_prev ;
struct strevent *sd_siglist; /* processes to be sent SIGPOLL */
int sd_sigflags; /* logical OR of all siglist events */
} stdata_t;
#define I_NREAD _IOR('S',01, int)
#define I_NREAD_SOLARIS (('S'<<8)|1)
#define I_FLUSH _IO('S',05)
#define I_FLUSH_SOLARIS (('S'<<8)|5)
#define FLUSHR 1 /* flush read queue */
#define FLUSHW 2 /* flush write queue */
#define FLUSHRW 3 /* flush both queues */
#define I_SETSIG _IO('S',011)
#define I_SETSIG_SOLARIS (('S'<<8)|11)
#define S_INPUT 0x01
#define S_HIPRI 0x02
#define S_OUTPUT 0x04
#define S_MSG 0x08
#define S_ERROR 0x0010
#define S_HANGUP 0x0020
#define S_RDNORM 0x0040
#define S_WRNORM S_OUTPUT
#define S_RDBAND 0x0080
#define S_WRBAND 0x0100
#define S_BANDURG 0x0200
#define S_ALL 0x03FF
#define I_GETSIG _IOR('S',012,int)
#define I_GETSIG_SOLARIS (('S'<<8)|12)
/* Conversion between Sun and OSS volume settings */
static __inline__
int OSS_LEFT(int value)
{
return ((value & 0xff) % 101);
}
static __inline__
int OSS_RIGHT(int value)
{
return (((value >> 8) & 0xff) % 101);
}
static __inline__
int O_TO_S(int value)
{
return value * 255 / 100;
}
static __inline__
int S_TO_O(int value)
{
return value * 100 / 255;
}
static __inline__
int OSS_TO_GAIN(int value)
{
int l = O_TO_S(OSS_LEFT(value));
int r = O_TO_S(OSS_RIGHT(value));
return ((l > r) ? l : r);
}
static __inline__
int OSS_TO_LGAIN(int value)
{
int l = O_TO_S(OSS_LEFT(value));
int r = O_TO_S(OSS_RIGHT(value));
return ((l < r) ? l : r);
}
static __inline__
int OSS_TO_BAL(int value)
{
if (!OSS_TO_GAIN(value))
return AUDIO_MID_BALANCE;
if (!OSS_TO_LGAIN(value)) {
if (OSS_TO_GAIN(value) == OSS_TO_GAIN(OSS_RIGHT(value)))
return AUDIO_RIGHT_BALANCE;
else
return AUDIO_LEFT_BALANCE;
}
if (OSS_TO_GAIN(value) == OSS_TO_GAIN(OSS_RIGHT(value)))
return ((OSS_TO_GAIN(value) - OSS_TO_LGAIN(value)) >> AUDIO_BALANCE_SHIFT)
+ AUDIO_MID_BALANCE;
else
return AUDIO_MID_BALANCE - ((OSS_TO_GAIN(value) - OSS_TO_LGAIN(value))
>> AUDIO_BALANCE_SHIFT);
}
static __inline__
int BAL_TO_OSS(int value, unsigned char balance)
{
int l, r, adj;
if (balance > 63) balance = 63;
if (balance < AUDIO_MID_BALANCE) {
l = (int)value * 100 / 255 + ((value * 100 % 255) > 0);
adj = ((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT);
if (adj < value)
r = (int)(value - adj)
* 100 / 255;
else r = 0;
} else if (balance > AUDIO_MID_BALANCE) {
r = (int)value * 100 / 255 + ((value * 100 % 255) > 0);
adj = ((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT);
if (adj < value)
l = (int)(value - adj)
* 100 / 255;
else l = 0;
} else {
l = r = (int)value * 100 / 255 + ((value * 100 % 255) > 0);
}
return ((r << 8) + l);
}
#ifdef __KERNEL__
/* OSS mixer ioctl port handling */
static __inline__
int OSS_PORT_AUDIO(struct sparcaudio_driver *drv, unsigned int set)
{
int p;
if (drv->ops->get_output_port) {
p = drv->ops->get_output_port(drv);
if (p & set)
return 0x6464;
}
return 0;
}
static __inline__
int OSS_IPORT_AUDIO(struct sparcaudio_driver *drv, unsigned int set)
{
int p;
if (drv->ops->get_input_port) {
p = drv->ops->get_input_port(drv);
if (p & set)
return 0x6464;
}
return 0;
}
static __inline__
void OSS_TWIDDLE_PORT(struct sparcaudio_driver *drv, unsigned int ioctl,
unsigned int port, unsigned int set, unsigned int value)
{
if (ioctl == port) {
int p;
if (drv->ops->get_output_port && drv->ops->set_output_port) {
p = drv->ops->get_output_port(drv);
if ((value == 0) || ((p & set) && (OSS_LEFT(value) < 100)))
drv->ops->set_output_port(drv, p & ~(set));
else
drv->ops->set_output_port(drv, p | set);
}
}
}
static __inline__
void OSS_TWIDDLE_IPORT(struct sparcaudio_driver *drv, unsigned int ioctl,
unsigned int port, unsigned int set, unsigned int value)
{
if (ioctl == port) {
int p;
if (drv->ops->get_input_port && drv->ops->set_input_port) {
p = drv->ops->get_input_port(drv);
if ((value == 0) || ((p & set) && (OSS_LEFT(value) < 100)))
drv->ops->set_input_port(drv, p & ~(set));
else
drv->ops->set_input_port(drv, p | set);
}
}
}
#endif /* __KERNEL__ */
#endif /* _AUDIOIO_H_ */
......@@ -231,414 +231,4 @@ typedef struct audio_device {
*/
#define AUDIO_DIAG_LOOPBACK _IOW('A', 101, int)
/*
* Linux kernel internal implementation.
*/
#ifdef __KERNEL__
#include <linux/fs.h>
#include <linux/tqueue.h>
#include <linux/wait.h>
#define SDF_OPEN_WRITE 0x00000001
#define SDF_OPEN_READ 0x00000002
struct sparcaudio_ringbuffer
{
__u8 *rb_start, *rb_end; /* start, end of this memory buffer */
__u8 *rb_in, *rb_out; /* input, output pointers */
int rb_fragsize; /* size of an audio frag */
int rb_numfrags; /* number of frags */
int rb_count, rb_hiwat, rb_lowat; /* bytes in use, hi/lo wat points */
int rb_bufsize; /* total size of buffer */
};
struct sparcaudio_driver
{
const char * name;
struct sparcaudio_operations *ops;
void *private;
unsigned long flags;
struct strevent *sd_siglist;
/* duplex: 0=simplex, 1=duplex, 2=loop */
int sd_sigflags, duplex;
/* Which audio device are we? */
int index;
/* This device */
struct sbus_dev *dev;
/* Processes blocked on open() sit here. */
wait_queue_head_t open_wait;
/* Task queue for this driver's bottom half. */
struct tq_struct tqueue;
/* Start of ring buffer support */
__u8 *input_buffer, *output_buffer;
/* Support for a circular queue of output buffers. */
__u8 **output_buffers;
size_t *output_sizes, output_size, output_buffer_size;
int num_output_buffers, output_front, output_rear, output_offset;
int output_count, output_active, playing_count, output_eof;
wait_queue_head_t output_write_wait, output_drain_wait;
char *output_notify;
/* Support for a circular queue of input buffers. */
__u8 **input_buffers;
size_t *input_sizes, input_size, input_buffer_size;
int num_input_buffers, input_front, input_rear, input_offset;
int input_count, input_active, recording_count;
wait_queue_head_t input_read_wait;
/* Hack to make it look like we support variable size buffers. */
int buffer_size;
int mixer_modify_counter;
};
struct sparcaudio_operations
{
int (*open)(struct inode *, struct file *, struct sparcaudio_driver *);
void (*release)(struct inode *, struct file *, struct sparcaudio_driver *);
int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long,
struct sparcaudio_driver *);
/* Ask driver to begin playing a buffer. */
void (*start_output)(struct sparcaudio_driver *, __u8 *, unsigned long);
/* Ask driver to stop playing a buffer. */
void (*stop_output)(struct sparcaudio_driver *);
/* Ask driver to begin recording into a buffer. */
void (*start_input)(struct sparcaudio_driver *, __u8 *, unsigned long);
/* Ask driver to stop recording. */
void (*stop_input)(struct sparcaudio_driver *);
/* Return driver name/version to caller. (/dev/audio specific) */
void (*sunaudio_getdev)(struct sparcaudio_driver *, audio_device_t *);
/* Get and set the output volume. (0-255) */
int (*set_output_volume)(struct sparcaudio_driver *, int);
int (*get_output_volume)(struct sparcaudio_driver *);
/* Get and set the input volume. (0-255) */
int (*set_input_volume)(struct sparcaudio_driver *, int);
int (*get_input_volume)(struct sparcaudio_driver *);
/* Get and set the monitor volume. (0-255) */
int (*set_monitor_volume)(struct sparcaudio_driver *, int);
int (*get_monitor_volume)(struct sparcaudio_driver *);
/* Get and set the output balance. (0-64) */
int (*set_output_balance)(struct sparcaudio_driver *, int);
int (*get_output_balance)(struct sparcaudio_driver *);
/* Get and set the input balance. (0-64) */
int (*set_input_balance)(struct sparcaudio_driver *, int);
int (*get_input_balance)(struct sparcaudio_driver *);
/* Get and set the output channels. (1-4) */
int (*set_output_channels)(struct sparcaudio_driver *, int);
int (*get_output_channels)(struct sparcaudio_driver *);
/* Get and set the input channels. (1-4) */
int (*set_input_channels)(struct sparcaudio_driver *, int);
int (*get_input_channels)(struct sparcaudio_driver *);
/* Get and set the output precision. (8-32) */
int (*set_output_precision)(struct sparcaudio_driver *, int);
int (*get_output_precision)(struct sparcaudio_driver *);
/* Get and set the input precision. (8-32) */
int (*set_input_precision)(struct sparcaudio_driver *, int);
int (*get_input_precision)(struct sparcaudio_driver *);
/* Get and set the output port. () */
int (*set_output_port)(struct sparcaudio_driver *, int);
int (*get_output_port)(struct sparcaudio_driver *);
/* Get and set the input port. () */
int (*set_input_port)(struct sparcaudio_driver *, int);
int (*get_input_port)(struct sparcaudio_driver *);
/* Get and set the output encoding. () */
int (*set_output_encoding)(struct sparcaudio_driver *, int);
int (*get_output_encoding)(struct sparcaudio_driver *);
/* Get and set the input encoding. () */
int (*set_input_encoding)(struct sparcaudio_driver *, int);
int (*get_input_encoding)(struct sparcaudio_driver *);
/* Get and set the output rate. () */
int (*set_output_rate)(struct sparcaudio_driver *, int);
int (*get_output_rate)(struct sparcaudio_driver *);
/* Get and set the input rate. () */
int (*set_input_rate)(struct sparcaudio_driver *, int);
int (*get_input_rate)(struct sparcaudio_driver *);
/* Return driver number to caller. (SunOS /dev/audio specific) */
int (*sunaudio_getdev_sunos)(struct sparcaudio_driver *);
/* Get available ports */
int (*get_output_ports)(struct sparcaudio_driver *);
int (*get_input_ports)(struct sparcaudio_driver *);
/* Get and set output mute */
int (*set_output_muted)(struct sparcaudio_driver *, int);
int (*get_output_muted)(struct sparcaudio_driver *);
/* Get and set output pause */
int (*set_output_pause)(struct sparcaudio_driver *, int);
int (*get_output_pause)(struct sparcaudio_driver *);
/* Get and set input pause */
int (*set_input_pause)(struct sparcaudio_driver *, int);
int (*get_input_pause)(struct sparcaudio_driver *);
/* Get and set output samples */
int (*set_output_samples)(struct sparcaudio_driver *, int);
int (*get_output_samples)(struct sparcaudio_driver *);
/* Get and set input samples */
int (*set_input_samples)(struct sparcaudio_driver *, int);
int (*get_input_samples)(struct sparcaudio_driver *);
/* Get and set output error */
int (*set_output_error)(struct sparcaudio_driver *, int);
int (*get_output_error)(struct sparcaudio_driver *);
/* Get and set input error */
int (*set_input_error)(struct sparcaudio_driver *, int);
int (*get_input_error)(struct sparcaudio_driver *);
/* Get supported encodings */
int (*get_formats)(struct sparcaudio_driver *);
};
extern int register_sparcaudio_driver(struct sparcaudio_driver *, int);
extern int unregister_sparcaudio_driver(struct sparcaudio_driver *, int);
extern void sparcaudio_output_done(struct sparcaudio_driver *, int);
extern void sparcaudio_input_done(struct sparcaudio_driver *, int);
#endif
/* Device minor numbers */
#define SPARCAUDIO_MIXER_MINOR 0
/* No sequencer (1) */
/* No midi (2) */
#define SPARCAUDIO_DSP_MINOR 3
#define SPARCAUDIO_AUDIO_MINOR 4
#define SPARCAUDIO_DSP16_MINOR 5
#define SPARCAUDIO_STATUS_MINOR 6
#define SPARCAUDIO_AUDIOCTL_MINOR 7
/* No sequencer l2 (8) */
/* No sound processor (9) */
/* allocate 2^SPARCAUDIO_DEVICE_SHIFT minors per audio device */
#define SPARCAUDIO_DEVICE_SHIFT 4
/* With the coming of dummy devices this should perhaps be as high as 5? */
#define SPARCAUDIO_MAX_DEVICES 3
/* Streams crap for realaudio */
typedef
struct strevent {
struct strevent *se_next; /* next event for this stream or NULL*/
struct strevent *se_prev; /* previous event for this stream or last
* event if this is the first one*/
pid_t se_pid; /* process to be signaled */
short se_evs; /* events wanted */
} strevent_t;
typedef
struct stdata
{
struct stdata *sd_next ; /* all stdatas are linked together */
struct stdata *sd_prev ;
struct strevent *sd_siglist; /* processes to be sent SIGPOLL */
int sd_sigflags; /* logical OR of all siglist events */
} stdata_t;
#define I_NREAD _IOR('S',01, int)
#define I_NREAD_SOLARIS (('S'<<8)|1)
#define I_FLUSH _IO('S',05)
#define I_FLUSH_SOLARIS (('S'<<8)|5)
#define FLUSHR 1 /* flush read queue */
#define FLUSHW 2 /* flush write queue */
#define FLUSHRW 3 /* flush both queues */
#define I_SETSIG _IO('S',011)
#define I_SETSIG_SOLARIS (('S'<<8)|11)
#define S_INPUT 0x01
#define S_HIPRI 0x02
#define S_OUTPUT 0x04
#define S_MSG 0x08
#define S_ERROR 0x0010
#define S_HANGUP 0x0020
#define S_RDNORM 0x0040
#define S_WRNORM S_OUTPUT
#define S_RDBAND 0x0080
#define S_WRBAND 0x0100
#define S_BANDURG 0x0200
#define S_ALL 0x03FF
#define I_GETSIG _IOR('S',012,int)
#define I_GETSIG_SOLARIS (('S'<<8)|12)
/* Conversion between Sun and OSS volume settings */
static __inline__
int OSS_LEFT(int value)
{
return ((value & 0xff) % 101);
}
static __inline__
int OSS_RIGHT(int value)
{
return (((value >> 8) & 0xff) % 101);
}
static __inline__
int O_TO_S(int value)
{
return value * 255 / 100;
}
static __inline__
int S_TO_O(int value)
{
return value * 100 / 255;
}
static __inline__
int OSS_TO_GAIN(int value)
{
int l = O_TO_S(OSS_LEFT(value));
int r = O_TO_S(OSS_RIGHT(value));
return ((l > r) ? l : r);
}
static __inline__
int OSS_TO_LGAIN(int value)
{
int l = O_TO_S(OSS_LEFT(value));
int r = O_TO_S(OSS_RIGHT(value));
return ((l < r) ? l : r);
}
static __inline__
int OSS_TO_BAL(int value)
{
if (!OSS_TO_GAIN(value))
return AUDIO_MID_BALANCE;
if (!OSS_TO_LGAIN(value)) {
if (OSS_TO_GAIN(value) == OSS_TO_GAIN(OSS_RIGHT(value)))
return AUDIO_RIGHT_BALANCE;
else
return AUDIO_LEFT_BALANCE;
}
if (OSS_TO_GAIN(value) == OSS_TO_GAIN(OSS_RIGHT(value)))
return ((OSS_TO_GAIN(value) - OSS_TO_LGAIN(value)) >> AUDIO_BALANCE_SHIFT)
+ AUDIO_MID_BALANCE;
else
return AUDIO_MID_BALANCE - ((OSS_TO_GAIN(value) - OSS_TO_LGAIN(value))
>> AUDIO_BALANCE_SHIFT);
}
static __inline__
int BAL_TO_OSS(int value, unsigned char balance)
{
int l, r, adj;
if (balance > 63) balance = 63;
if (balance < AUDIO_MID_BALANCE) {
l = (int)value * 100 / 255 + ((value * 100 % 255) > 0);
adj = ((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT);
if (adj < value)
r = (int)(value - adj)
* 100 / 255;
else r = 0;
} else if (balance > AUDIO_MID_BALANCE) {
r = (int)value * 100 / 255 + ((value * 100 % 255) > 0);
adj = ((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT);
if (adj < value)
l = (int)(value - adj)
* 100 / 255;
else l = 0;
} else {
l = r = (int)value * 100 / 255 + ((value * 100 % 255) > 0);
}
return ((r << 8) + l);
}
#ifdef __KERNEL__
/* OSS mixer ioctl port handling */
static __inline__
int OSS_PORT_AUDIO(struct sparcaudio_driver *drv, unsigned int set)
{
int p;
if (drv->ops->get_output_port) {
p = drv->ops->get_output_port(drv);
if (p & set)
return 0x6464;
}
return 0;
}
static __inline__
int OSS_IPORT_AUDIO(struct sparcaudio_driver *drv, unsigned int set)
{
int p;
if (drv->ops->get_input_port) {
p = drv->ops->get_input_port(drv);
if (p & set)
return 0x6464;
}
return 0;
}
static __inline__
void OSS_TWIDDLE_PORT(struct sparcaudio_driver *drv, unsigned int ioctl,
unsigned int port, unsigned int set, unsigned int value)
{
if (ioctl == port) {
int p;
if (drv->ops->get_output_port && drv->ops->set_output_port) {
p = drv->ops->get_output_port(drv);
if ((value == 0) || ((p & set) && (OSS_LEFT(value) < 100)))
drv->ops->set_output_port(drv, p & ~(set));
else
drv->ops->set_output_port(drv, p | set);
}
}
}
static __inline__
void OSS_TWIDDLE_IPORT(struct sparcaudio_driver *drv, unsigned int ioctl,
unsigned int port, unsigned int set, unsigned int value)
{
if (ioctl == port) {
int p;
if (drv->ops->get_input_port && drv->ops->set_input_port) {
p = drv->ops->get_input_port(drv);
if ((value == 0) || ((p & set) && (OSS_LEFT(value) < 100)))
drv->ops->set_input_port(drv, p & ~(set));
else
drv->ops->set_input_port(drv, p | set);
}
}
}
#endif /* __KERNEL__ */
#endif /* _AUDIOIO_H_ */
......@@ -22,9 +22,8 @@
*
*/
#ifdef CONFIG_PM
#include <linux/sched.h> /* wake_up() and struct semaphore */
#endif
#include <linux/sched.h> /* wake_up() */
#include <asm/semaphore.h> /* struct semaphore */
/* Typedef's */
typedef struct timeval snd_timestamp_t;
......@@ -38,6 +37,9 @@ typedef struct sndrv_xferv snd_xferv_t;
#ifdef CONFIG_PCI
struct pci_dev;
#endif
#ifdef CONFIG_SBUS
struct sbus_dev;
#endif
/* device allocation stuff */
......@@ -256,6 +258,11 @@ void *snd_malloc_pci_pages(struct pci_dev *pci, unsigned long size, dma_addr_t *
void *snd_malloc_pci_pages_fallback(struct pci_dev *pci, unsigned long size, dma_addr_t *dma_addr, unsigned long *res_size);
void snd_free_pci_pages(struct pci_dev *pci, unsigned long size, void *ptr, dma_addr_t dma_addr);
#endif
#ifdef CONFIG_SBUS
void *snd_malloc_sbus_pages(struct sbus_dev *sdev, unsigned long size, dma_addr_t *dma_addr);
void *snd_malloc_sbus_pages_fallback(struct sbus_dev *sdev, unsigned long size, dma_addr_t *dma_addr, unsigned long *res_size);
void snd_free_sbus_pages(struct sbus_dev *sdev, unsigned long size, void *ptr, dma_addr_t dma_addr);
#endif
#ifdef CONFIG_ISA
#ifdef CONFIG_PCI
#define snd_malloc_isa_pages(size, dma_addr) snd_malloc_pci_pages(NULL, size, dma_addr)
......
......@@ -121,6 +121,7 @@ typedef struct _snd_pcm_ops {
#define SNDRV_PCM_DMA_TYPE_CONTINUOUS 0 /* continuous no-DMA memory */
#define SNDRV_PCM_DMA_TYPE_ISA 1 /* ISA continuous */
#define SNDRV_PCM_DMA_TYPE_PCI 2 /* PCI continuous */
#define SNDRV_PCM_DMA_TYPE_SBUS 3 /* SBUS continuous */
/* If you change this don't forget to changed snd_pcm_rates table in pcm_lib.c */
#define SNDRV_PCM_RATE_5512 (1<<0) /* 5512Hz */
......@@ -848,6 +849,15 @@ int snd_pcm_lib_preallocate_pci_pages_for_all(struct pci_dev *pci,
size_t size,
size_t max);
#endif
#ifdef CONFIG_SBUS
int snd_pcm_lib_preallocate_sbus_pages(struct sbus_dev *sdev,
snd_pcm_substream_t *substream,
size_t size, size_t max);
int snd_pcm_lib_preallocate_sbus_pages_for_all(struct sbus_dev *sdev,
snd_pcm_t *pcm,
size_t size,
size_t max);
#endif
static inline void snd_pcm_limit_isa_dma_size(int dma, size_t *max)
{
......
......@@ -23,6 +23,9 @@
*/
#include <sound/asound.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <asm/semaphore.h>
#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
#include "seq_device.h"
......
......@@ -31,5 +31,11 @@ fi
if [ "$CONFIG_SND" != "n" -a "$CONFIG_ARM" = "y" ]; then
source sound/arm/Config.in
fi
if [ "$CONFIG_SND" != "n" -a "$CONFIG_SPARC32" = "y" ]; then
source sound/sparc/Config.in
fi
if [ "$CONFIG_SND" != "n" -a "$CONFIG_SPARC64" = "y" ]; then
source sound/sparc/Config.in
fi
endmenu
......@@ -5,7 +5,7 @@ export-objs := sound_core.o
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SOUND_PRIME) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ sparc/
ifeq ($(CONFIG_SND),y)
obj-y += last.o
......
......@@ -23,6 +23,7 @@
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/errno.h>
#include <sound/core.h>
int snd_device_new(snd_card_t *card, snd_device_type_t type,
......
......@@ -22,6 +22,7 @@
#include <sound/driver.h>
#include <linux/smp_lock.h>
#include <linux/time.h>
#include <linux/init.h>
#include <sound/core.h>
#include <sound/control.h>
#include <asm/uaccess.h>
......@@ -393,6 +394,8 @@ static int __init snd_ioctl32_init(void)
return err;
}
#endif
return 0;
}
module_init(snd_ioctl32_init)
......
......@@ -26,6 +26,10 @@
#ifndef __ALSA_IOCTL32_H
#define __ALSA_IOCTL32_H
#ifndef A
#define A(__x) ((unsigned long)(__x))
#endif
#define TO_PTR(x) A(x)
#define COPY(x) (dst->x = src->x)
......
......@@ -29,6 +29,9 @@
#include <linux/pci.h>
#include <sound/core.h>
#include <sound/info.h>
#ifdef CONFIG_SBUS
#include <asm/sbus.h>
#endif
/*
* memory allocation helpers and debug routines
......@@ -405,6 +408,72 @@ void snd_free_pci_pages(struct pci_dev *pci,
#endif /* CONFIG_PCI */
#ifdef CONFIG_SBUS
void *snd_malloc_sbus_pages(struct sbus_dev *sdev,
unsigned long size,
dma_addr_t *dma_addr)
{
int pg;
void *res;
snd_assert(size > 0, return NULL);
snd_assert(dma_addr != NULL, return NULL);
for (pg = 0; PAGE_SIZE * (1 << pg) < size; pg++);
res = sbus_alloc_consistent(sdev, PAGE_SIZE * (1 << pg), dma_addr);
if (res != NULL) {
struct page *page = virt_to_page(res);
struct page *last_page = page + (1 << pg);
while (page < last_page)
SetPageReserved(page++);
#ifdef CONFIG_SND_DEBUG_MEMORY
snd_alloc_pages += 1 << pg;
#endif
}
return res;
}
void *snd_malloc_sbus_pages_fallback(struct sbus_dev *sdev,
unsigned long size,
dma_addr_t *dma_addr,
unsigned long *res_size)
{
void *res;
snd_assert(res_size != NULL, return NULL);
do {
if ((res = snd_malloc_sbus_pages(sdev, size, dma_addr)) != NULL) {
*res_size = size;
return res;
}
size >>= 1;
} while (size >= PAGE_SIZE);
return NULL;
}
void snd_free_sbus_pages(struct sbus_dev *sdev,
unsigned long size,
void *ptr,
dma_addr_t dma_addr)
{
int pg;
struct page *page, *last_page;
if (ptr == NULL)
return;
for (pg = 0; PAGE_SIZE * (1 << pg) < size; pg++);
page = virt_to_page(ptr);
last_page = page + (1 << pg);
while (page < last_page)
ClearPageReserved(page++);
sbus_free_consistent(sdev, PAGE_SIZE * (1 << pg), ptr, dma_addr);
#ifdef CONFIG_SND_DEBUG_MEMORY
snd_alloc_pages -= 1 << pg;
#endif
}
#endif /* CONFIG_SBUS */
void *snd_kcalloc(size_t size, int flags)
{
void *ptr;
......
......@@ -2379,3 +2379,7 @@ EXPORT_SYMBOL(snd_pcm_lib_preallocate_isa_pages_for_all);
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pci_pages);
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pci_pages_for_all);
#endif
#ifdef CONFIG_SBUS
EXPORT_SYMBOL(snd_pcm_lib_preallocate_sbus_pages);
EXPORT_SYMBOL(snd_pcm_lib_preallocate_sbus_pages_for_all);
#endif
......@@ -58,6 +58,11 @@ static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
case SNDRV_PCM_DMA_TYPE_PCI:
snd_free_pci_pages((struct pci_dev *)substream->dma_private, substream->dma_bytes, substream->dma_area, substream->dma_addr);
break;
#endif
#ifdef CONFIG_SBUS
case SNDRV_PCM_DMA_TYPE_SBUS:
snd_free_sbus_pages((struct sbus_dev *)substream->dma_private, substream->dma_bytes, substream->dma_area, substream->dma_addr);
break;
#endif
}
substream->dma_area = NULL;
......@@ -128,6 +133,11 @@ static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
case SNDRV_PCM_DMA_TYPE_PCI:
dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, &dma_addr);
break;
#endif
#ifdef CONFIG_SBUS
case SNDRV_PCM_DMA_TYPE_SBUS:
dma_area = snd_malloc_sbus_pages((struct sbus_dev *)substream->dma_private, size, &dma_addr);
break;
#endif
default:
dma_area = NULL;
......@@ -176,6 +186,11 @@ static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
case SNDRV_PCM_DMA_TYPE_PCI:
dma_area = snd_malloc_pci_pages_fallback((struct pci_dev *)substream->dma_private, size, &dma_addr, &rsize);
break;
#endif
#ifdef CONFIG_SBUS
case SNDRV_PCM_DMA_TYPE_SBUS:
dma_area = snd_malloc_sbus_pages_fallback((struct sbus_dev *)substream->dma_private, size, &dma_addr, &rsize);
break;
#endif
default:
size = 0;
......@@ -287,6 +302,11 @@ int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
case SNDRV_PCM_DMA_TYPE_PCI:
dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, &dma_addr);
break;
#endif
#ifdef CONFIG_SBUS
case SNDRV_PCM_DMA_TYPE_SBUS:
dma_area = snd_malloc_sbus_pages((struct sbus_dev *)substream->dma_private, size, &dma_addr);
break;
#endif
default:
return -ENXIO;
......@@ -320,6 +340,11 @@ int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
case SNDRV_PCM_DMA_TYPE_PCI:
snd_free_pci_pages((struct pci_dev *)substream->dma_private, runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
break;
#endif
#ifdef CONFIG_SBUS
case SNDRV_PCM_DMA_TYPE_SBUS:
snd_free_sbus_pages((struct sbus_dev *)substream->dma_private, runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
break;
#endif
}
}
......@@ -355,3 +380,30 @@ int snd_pcm_lib_preallocate_pci_pages_for_all(struct pci_dev *pci,
}
#endif /* CONFIG_PCI */
#ifdef CONFIG_SBUS
int snd_pcm_lib_preallocate_sbus_pages(struct sbus_dev *sdev,
snd_pcm_substream_t *substream,
size_t size, size_t max)
{
substream->dma_type = SNDRV_PCM_DMA_TYPE_SBUS;
substream->dma_private = sdev;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
int snd_pcm_lib_preallocate_sbus_pages_for_all(struct sbus_dev *sdev,
snd_pcm_t *pcm,
size_t size, size_t max)
{
snd_pcm_substream_t *substream;
int stream, err;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
if ((err = snd_pcm_lib_preallocate_sbus_pages(sdev, substream, size, max)) < 0)
return err;
return 0;
}
#endif /* CONFIG_SBUS */
......@@ -128,14 +128,14 @@ int snd_seq_dump_var_event(const snd_seq_event_t *event, snd_seq_dump_func_t fun
* expand the variable length event to linear buffer space.
*/
static int copy_in_kernel(char **bufptr, const void *src, int size)
static int seq_copy_in_kernel(char **bufptr, const void *src, int size)
{
memcpy(*bufptr, src, size);
*bufptr += size;
return 0;
}
static int copy_in_user(char **bufptr, const void *src, int size)
static int seq_copy_in_user(char **bufptr, const void *src, int size)
{
if (copy_to_user(*bufptr, src, size))
return -EFAULT;
......@@ -164,8 +164,8 @@ int snd_seq_expand_var_event(const snd_seq_event_t *event, int count, char *buf,
return newlen;
}
err = snd_seq_dump_var_event(event,
in_kernel ? (snd_seq_dump_func_t)copy_in_kernel :
(snd_seq_dump_func_t)copy_in_user,
in_kernel ? (snd_seq_dump_func_t)seq_copy_in_kernel :
(snd_seq_dump_func_t)seq_copy_in_user,
&buf);
return err < 0 ? err : newlen;
}
......
......@@ -30,6 +30,7 @@ Possible options for midisynth module:
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <asm/semaphore.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
......
......@@ -32,6 +32,7 @@
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_emul.h>
......
......@@ -22,6 +22,7 @@
#include <sound/driver.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_event.h>
......
......@@ -429,6 +429,11 @@ EXPORT_SYMBOL(snd_malloc_pci_pages);
EXPORT_SYMBOL(snd_malloc_pci_pages_fallback);
EXPORT_SYMBOL(snd_free_pci_pages);
#endif
#ifdef CONFIG_SBUS
EXPORT_SYMBOL(snd_malloc_sbus_pages);
EXPORT_SYMBOL(snd_malloc_sbus_pages_fallback);
EXPORT_SYMBOL(snd_free_sbus_pages);
#endif
EXPORT_SYMBOL(copy_to_user_fromio);
EXPORT_SYMBOL(copy_from_user_toio);
/* init.c */
......
......@@ -23,6 +23,8 @@
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <sound/core.h>
#include <sound/i2c.h>
......
......@@ -794,7 +794,7 @@ static void snd_emu10k1_write_op(emu10k1_fx8010_code_t *icode, unsigned int *ptr
u32 op, u32 r, u32 a, u32 x, u32 y)
{
snd_assert(*ptr < 512, return);
set_bit(*ptr, &icode->code_valid);
set_bit(*ptr, icode->code_valid);
icode->code[*ptr ][0] = ((x & 0x3ff) << 10) | (y & 0x3ff);
icode->code[(*ptr)++][1] = ((op & 0x0f) << 20) | ((r & 0x3ff) << 10) | (a & 0x3ff);
}
......@@ -806,7 +806,7 @@ static void snd_emu10k1_audigy_write_op(emu10k1_fx8010_code_t *icode, unsigned i
u32 op, u32 r, u32 a, u32 x, u32 y)
{
snd_assert(*ptr < 512, return);
set_bit(*ptr, &icode->code_valid);
set_bit(*ptr, icode->code_valid);
icode->code[*ptr ][0] = ((x & 0x7ff) << 12) | (y & 0x7ff);
icode->code[(*ptr)++][1] = ((op & 0x0f) << 24) | ((r & 0x7ff) << 12) | (a & 0x7ff);
}
......@@ -831,7 +831,7 @@ static void snd_emu10k1_gpr_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
int gpr;
for (gpr = 0; gpr < 0x100; gpr++) {
if (!test_bit(gpr, &icode->gpr_valid))
if (!test_bit(gpr, icode->gpr_valid))
continue;
snd_emu10k1_ptr_write(emu, emu->gpr_base + gpr, 0, icode->gpr_map[gpr]);
}
......@@ -842,7 +842,7 @@ static void snd_emu10k1_gpr_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
int gpr;
for (gpr = 0; gpr < 0x100; gpr++) {
set_bit(gpr, &icode->gpr_valid);
set_bit(gpr, icode->gpr_valid);
icode->gpr_map[gpr] = snd_emu10k1_ptr_read(emu, emu->gpr_base + gpr, 0);
}
}
......@@ -852,7 +852,7 @@ static void snd_emu10k1_tram_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
int tram;
for (tram = 0; tram < 0xa0; tram++) {
if (!test_bit(tram, &icode->tram_valid))
if (!test_bit(tram, icode->tram_valid))
continue;
snd_emu10k1_ptr_write(emu, TANKMEMDATAREGBASE + tram, 0, icode->tram_data_map[tram]);
snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, icode->tram_addr_map[tram]);
......@@ -864,7 +864,7 @@ static void snd_emu10k1_tram_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
int tram;
for (tram = 0; tram < 0xa0; tram++) {
set_bit(tram, &icode->tram_valid);
set_bit(tram, icode->tram_valid);
icode->tram_data_map[tram] = snd_emu10k1_ptr_read(emu, TANKMEMDATAREGBASE + tram, 0);
icode->tram_addr_map[tram] = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0);
}
......@@ -875,7 +875,7 @@ static void snd_emu10k1_code_poke(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
u32 pc;
for (pc = 0; pc < 512; pc++) {
if (!test_bit(pc, &icode->code_valid))
if (!test_bit(pc, icode->code_valid))
continue;
snd_emu10k1_efx_write(emu, pc * 2, icode->code[pc][0]);
snd_emu10k1_efx_write(emu, pc * 2 + 1, icode->code[pc][1]);
......@@ -887,7 +887,7 @@ static void snd_emu10k1_code_peek(emu10k1_t *emu, emu10k1_fx8010_code_t *icode)
u32 pc;
for (pc = 0; pc < 512; pc++) {
set_bit(pc, &icode->code_valid);
set_bit(pc, icode->code_valid);
icode->code[pc][0] = snd_emu10k1_efx_read(emu, pc * 2);
icode->code[pc][1] = snd_emu10k1_efx_read(emu, pc * 2 + 1);
}
......@@ -1215,7 +1215,7 @@ static int __devinit _snd_emu10k1_audigy_init_efx(emu10k1_t *emu)
/* clear free GPRs */
for (i = 0; i < 256; i++)
set_bit(i, &icode->gpr_valid);
set_bit(i, icode->gpr_valid);
strcpy(icode->name, "Audigy DSP code for ALSA");
ptr = 0;
......@@ -1467,11 +1467,11 @@ static int __devinit _snd_emu10k1_init_efx(emu10k1_t *emu)
/* clear free GPRs */
for (i = 0; i < 256; i++)
set_bit(i, &icode->gpr_valid);
set_bit(i, icode->gpr_valid);
/* clear TRAM data & address lines */
for (i = 0; i < 160; i++)
set_bit(i, &icode->tram_valid);
set_bit(i, icode->tram_valid);
strcpy(icode->name, "SB Live! FX8010 code for ALSA v1.2 by Jaroslav Kysela");
ptr = 0; i = 0;
......
# ALSA Sparc drivers
mainmenu_option next_comment
comment 'ALSA Sparc devices'
if [ "$CONFIG_SBUS" != "n" ]; then
dep_tristate 'Sun AMD7930' CONFIG_SND_SUN_AMD7930 $CONFIG_SND
# dep_tristate 'Sun DBRI' CONFIG_SND_SUN_DBRI $CONFIG_SND
fi
dep_tristate 'Sun CS4231' CONFIG_SND_SUN_CS4231 $CONFIG_SND
endmenu
#
# Makefile for ALSA
# Copyright (c) 2002 by David S. Miller <davem@redhat.com>
#
snd-sun-amd7930-objs := amd7930.o
#snd-sun-dbri-objs := dbri.o
snd-sun-cs4231-objs := cs4231.o
obj-$(CONFIG_SND_SUN_AMD7930) += snd-sun-amd7930.o
#obj-$(CONFIG_SND_SUN_DBRI) += snd-sun-dbri.o
obj-$(CONFIG_SND_SUN_CS4231) += snd-sun-cs4231.o
include $(TOPDIR)/Rules.make
/*
* Driver for AMD7930 sound chips found on Sparcs.
* Copyright (C) 2002 David S. Miller <davem@redhat.com>
*
* Based entirely upon drivers/sbus/audio/amd7930.c which is:
* Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu)
*
* --- Notes from Thomas's original driver ---
* This is the lowlevel driver for the AMD7930 audio chip found on all
* sun4c machines and some sun4m machines.
*
* 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.
*
* 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.
* --- End of notes from Thoamas's original driver ---
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/control.h>
#define SNDRV_GET_ID
#include <sound/initval.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/sbus.h>
static int snd_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *snd_id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int snd_enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for Sun AMD7930 soundcard.");
MODULE_PARM_SYNTAX(snd_index, SNDRV_INDEX_DESC);
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for Sun AMD7930 soundcard.");
MODULE_PARM_SYNTAX(snd_id, SNDRV_ID_DESC);
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable Sun AMD7930 soundcard.");
MODULE_PARM_SYNTAX(snd_enable, SNDRV_ENABLE_DESC);
MODULE_AUTHOR("Thomas K. Dyas and David S. Miller");
MODULE_DESCRIPTION("Sun AMD7930");
MODULE_LICENSE("GPL");
MODULE_CLASSES("{sound}");
MODULE_DEVICES("{{Sun,AMD7930}}");
/* Device register layout. */
/* Register interface presented to the CPU by the amd7930. */
#define AMD7930_CR 0x00UL /* Command Register (W) */
#define AMD7930_IR AMD7930_CR /* Interrupt Register (R) */
#define AMD7930_DR 0x01UL /* Data Register (R/W) */
#define AMD7930_DSR1 0x02UL /* D-channel Status Register 1 (R) */
#define AMD7930_DER 0x03UL /* D-channel Error Register (R) */
#define AMD7930_DCTB 0x04UL /* D-channel Transmit Buffer (W) */
#define AMD7930_DCRB AMD7930_DCTB /* D-channel Receive Buffer (R) */
#define AMD7930_BBTB 0x05UL /* Bb-channel Transmit Buffer (W) */
#define AMD7930_BBRB AMD7930_BBTB /* Bb-channel Receive Buffer (R) */
#define AMD7930_BCTB 0x06UL /* Bc-channel Transmit Buffer (W) */
#define AMD7930_BCRB AMD7930_BCTB /* Bc-channel Receive Buffer (R) */
#define AMD7930_DSR2 0x07UL /* D-channel Status Register 2 (R) */
/* Indirect registers in the Main Audio Processor. */
struct amd7930_map {
__u16 x[8];
__u16 r[8];
__u16 gx;
__u16 gr;
__u16 ger;
__u16 stgr;
__u16 ftgr;
__u16 atgr;
__u8 mmr1;
__u8 mmr2;
};
/* After an amd7930 interrupt, reading the Interrupt Register (ir)
* clears the interrupt and returns a bitmask indicating which
* interrupt source(s) require service.
*/
#define AMR_IR_DTTHRSH 0x01 /* D-channel xmit threshold */
#define AMR_IR_DRTHRSH 0x02 /* D-channel recv threshold */
#define AMR_IR_DSRI 0x04 /* D-channel packet status */
#define AMR_IR_DERI 0x08 /* D-channel error */
#define AMR_IR_BBUF 0x10 /* B-channel data xfer */
#define AMR_IR_LSRI 0x20 /* LIU status */
#define AMR_IR_DSR2I 0x40 /* D-channel buffer status */
#define AMR_IR_MLTFRMI 0x80 /* multiframe or PP */
/* The amd7930 has "indirect registers" which are accessed by writing
* the register number into the Command Register and then reading or
* writing values from the Data Register as appropriate. We define the
* AMR_* macros to be the indirect register numbers and AM_* macros to
* be bits in whatever register is referred to.
*/
/* Initialization */
#define AMR_INIT 0x21
#define AM_INIT_ACTIVE 0x01
#define AM_INIT_DATAONLY 0x02
#define AM_INIT_POWERDOWN 0x03
#define AM_INIT_DISABLE_INTS 0x04
#define AMR_INIT2 0x20
#define AM_INIT2_ENABLE_POWERDOWN 0x20
#define AM_INIT2_ENABLE_MULTIFRAME 0x10
/* Line Interface Unit */
#define AMR_LIU_LSR 0xA1
#define AM_LIU_LSR_STATE 0x07
#define AM_LIU_LSR_F3 0x08
#define AM_LIU_LSR_F7 0x10
#define AM_LIU_LSR_F8 0x20
#define AM_LIU_LSR_HSW 0x40
#define AM_LIU_LSR_HSW_CHG 0x80
#define AMR_LIU_LPR 0xA2
#define AMR_LIU_LMR1 0xA3
#define AM_LIU_LMR1_B1_ENABL 0x01
#define AM_LIU_LMR1_B2_ENABL 0x02
#define AM_LIU_LMR1_F_DISABL 0x04
#define AM_LIU_LMR1_FA_DISABL 0x08
#define AM_LIU_LMR1_REQ_ACTIV 0x10
#define AM_LIU_LMR1_F8_F3 0x20
#define AM_LIU_LMR1_LIU_ENABL 0x40
#define AMR_LIU_LMR2 0xA4
#define AM_LIU_LMR2_DECHO 0x01
#define AM_LIU_LMR2_DLOOP 0x02
#define AM_LIU_LMR2_DBACKOFF 0x04
#define AM_LIU_LMR2_EN_F3_INT 0x08
#define AM_LIU_LMR2_EN_F8_INT 0x10
#define AM_LIU_LMR2_EN_HSW_INT 0x20
#define AM_LIU_LMR2_EN_F7_INT 0x40
#define AMR_LIU_2_4 0xA5
#define AMR_LIU_MF 0xA6
#define AMR_LIU_MFSB 0xA7
#define AMR_LIU_MFQB 0xA8
/* Multiplexor */
#define AMR_MUX_MCR1 0x41
#define AMR_MUX_MCR2 0x42
#define AMR_MUX_MCR3 0x43
#define AM_MUX_CHANNEL_B1 0x01
#define AM_MUX_CHANNEL_B2 0x02
#define AM_MUX_CHANNEL_Ba 0x03
#define AM_MUX_CHANNEL_Bb 0x04
#define AM_MUX_CHANNEL_Bc 0x05
#define AM_MUX_CHANNEL_Bd 0x06
#define AM_MUX_CHANNEL_Be 0x07
#define AM_MUX_CHANNEL_Bf 0x08
#define AMR_MUX_MCR4 0x44
#define AM_MUX_MCR4_ENABLE_INTS 0x08
#define AM_MUX_MCR4_REVERSE_Bb 0x10
#define AM_MUX_MCR4_REVERSE_Bc 0x20
#define AMR_MUX_1_4 0x45
/* Main Audio Processor */
#define AMR_MAP_X 0x61
#define AMR_MAP_R 0x62
#define AMR_MAP_GX 0x63
#define AMR_MAP_GR 0x64
#define AMR_MAP_GER 0x65
#define AMR_MAP_STGR 0x66
#define AMR_MAP_FTGR_1_2 0x67
#define AMR_MAP_ATGR_1_2 0x68
#define AMR_MAP_MMR1 0x69
#define AM_MAP_MMR1_ALAW 0x01
#define AM_MAP_MMR1_GX 0x02
#define AM_MAP_MMR1_GR 0x04
#define AM_MAP_MMR1_GER 0x08
#define AM_MAP_MMR1_X 0x10
#define AM_MAP_MMR1_R 0x20
#define AM_MAP_MMR1_STG 0x40
#define AM_MAP_MMR1_LOOPBACK 0x80
#define AMR_MAP_MMR2 0x6A
#define AM_MAP_MMR2_AINB 0x01
#define AM_MAP_MMR2_LS 0x02
#define AM_MAP_MMR2_ENABLE_DTMF 0x04
#define AM_MAP_MMR2_ENABLE_TONEGEN 0x08
#define AM_MAP_MMR2_ENABLE_TONERING 0x10
#define AM_MAP_MMR2_DISABLE_HIGHPASS 0x20
#define AM_MAP_MMR2_DISABLE_AUTOZERO 0x40
#define AMR_MAP_1_10 0x6B
#define AMR_MAP_MMR3 0x6C
#define AMR_MAP_STRA 0x6D
#define AMR_MAP_STRF 0x6E
#define AMR_MAP_PEAKX 0x70
#define AMR_MAP_PEAKR 0x71
#define AMR_MAP_15_16 0x72
/* Data Link Controller */
#define AMR_DLC_FRAR_1_2_3 0x81
#define AMR_DLC_SRAR_1_2_3 0x82
#define AMR_DLC_TAR 0x83
#define AMR_DLC_DRLR 0x84
#define AMR_DLC_DTCR 0x85
#define AMR_DLC_DMR1 0x86
#define AMR_DLC_DMR1_DTTHRSH_INT 0x01
#define AMR_DLC_DMR1_DRTHRSH_INT 0x02
#define AMR_DLC_DMR1_TAR_ENABL 0x04
#define AMR_DLC_DMR1_EORP_INT 0x08
#define AMR_DLC_DMR1_EN_ADDR1 0x10
#define AMR_DLC_DMR1_EN_ADDR2 0x20
#define AMR_DLC_DMR1_EN_ADDR3 0x40
#define AMR_DLC_DMR1_EN_ADDR4 0x80
#define AMR_DLC_DMR1_EN_ADDRS 0xf0
#define AMR_DLC_DMR2 0x87
#define AMR_DLC_DMR2_RABRT_INT 0x01
#define AMR_DLC_DMR2_RESID_INT 0x02
#define AMR_DLC_DMR2_COLL_INT 0x04
#define AMR_DLC_DMR2_FCS_INT 0x08
#define AMR_DLC_DMR2_OVFL_INT 0x10
#define AMR_DLC_DMR2_UNFL_INT 0x20
#define AMR_DLC_DMR2_OVRN_INT 0x40
#define AMR_DLC_DMR2_UNRN_INT 0x80
#define AMR_DLC_1_7 0x88
#define AMR_DLC_DRCR 0x89
#define AMR_DLC_RNGR1 0x8A
#define AMR_DLC_RNGR2 0x8B
#define AMR_DLC_FRAR4 0x8C
#define AMR_DLC_SRAR4 0x8D
#define AMR_DLC_DMR3 0x8E
#define AMR_DLC_DMR3_VA_INT 0x01
#define AMR_DLC_DMR3_EOTP_INT 0x02
#define AMR_DLC_DMR3_LBRP_INT 0x04
#define AMR_DLC_DMR3_RBA_INT 0x08
#define AMR_DLC_DMR3_LBT_INT 0x10
#define AMR_DLC_DMR3_TBE_INT 0x20
#define AMR_DLC_DMR3_RPLOST_INT 0x40
#define AMR_DLC_DMR3_KEEP_FCS 0x80
#define AMR_DLC_DMR4 0x8F
#define AMR_DLC_DMR4_RCV_1 0x00
#define AMR_DLC_DMR4_RCV_2 0x01
#define AMR_DLC_DMR4_RCV_4 0x02
#define AMR_DLC_DMR4_RCV_8 0x03
#define AMR_DLC_DMR4_RCV_16 0x01
#define AMR_DLC_DMR4_RCV_24 0x02
#define AMR_DLC_DMR4_RCV_30 0x03
#define AMR_DLC_DMR4_XMT_1 0x00
#define AMR_DLC_DMR4_XMT_2 0x04
#define AMR_DLC_DMR4_XMT_4 0x08
#define AMR_DLC_DMR4_XMT_8 0x0c
#define AMR_DLC_DMR4_XMT_10 0x08
#define AMR_DLC_DMR4_XMT_14 0x0c
#define AMR_DLC_DMR4_IDLE_MARK 0x00
#define AMR_DLC_DMR4_IDLE_FLAG 0x10
#define AMR_DLC_DMR4_ADDR_BOTH 0x00
#define AMR_DLC_DMR4_ADDR_1ST 0x20
#define AMR_DLC_DMR4_ADDR_2ND 0xa0
#define AMR_DLC_DMR4_CR_ENABLE 0x40
#define AMR_DLC_12_15 0x90
#define AMR_DLC_ASR 0x91
#define AMR_DLC_EFCR 0x92
#define AMR_DLC_EFCR_EXTEND_FIFO 0x01
#define AMR_DLC_EFCR_SEC_PKT_INT 0x02
#define AMR_DSR1_VADDR 0x01
#define AMR_DSR1_EORP 0x02
#define AMR_DSR1_PKT_IP 0x04
#define AMR_DSR1_DECHO_ON 0x08
#define AMR_DSR1_DLOOP_ON 0x10
#define AMR_DSR1_DBACK_OFF 0x20
#define AMR_DSR1_EOTP 0x40
#define AMR_DSR1_CXMT_ABRT 0x80
#define AMR_DSR2_LBRP 0x01
#define AMR_DSR2_RBA 0x02
#define AMR_DSR2_RPLOST 0x04
#define AMR_DSR2_LAST_BYTE 0x08
#define AMR_DSR2_TBE 0x10
#define AMR_DSR2_MARK_IDLE 0x20
#define AMR_DSR2_FLAG_IDLE 0x40
#define AMR_DSR2_SECOND_PKT 0x80
#define AMR_DER_RABRT 0x01
#define AMR_DER_RFRAME 0x02
#define AMR_DER_COLLISION 0x04
#define AMR_DER_FCS 0x08
#define AMR_DER_OVFL 0x10
#define AMR_DER_UNFL 0x20
#define AMR_DER_OVRN 0x40
#define AMR_DER_UNRN 0x80
/* Peripheral Port */
#define AMR_PP_PPCR1 0xC0
#define AMR_PP_PPSR 0xC1
#define AMR_PP_PPIER 0xC2
#define AMR_PP_MTDR 0xC3
#define AMR_PP_MRDR 0xC3
#define AMR_PP_CITDR0 0xC4
#define AMR_PP_CIRDR0 0xC4
#define AMR_PP_CITDR1 0xC5
#define AMR_PP_CIRDR1 0xC5
#define AMR_PP_PPCR2 0xC8
#define AMR_PP_PPCR3 0xC9
typedef struct snd_amd7930 {
spinlock_t lock;
unsigned long regs;
u32 flags;
#define AMD7930_FLAG_PLAYBACK 0x00000001
#define AMD7930_FLAG_CAPTURE 0x00000002
struct amd7930_map map;
snd_card_t *card;
snd_pcm_t *pcm;
snd_pcm_substream_t *playback_substream;
snd_pcm_substream_t *capture_substream;
/* Playback/Capture buffer state. */
unsigned char *p_orig, *p_cur;
int p_left;
unsigned char *c_orig, *c_cur;
int c_left;
int rgain;
int pgain;
int mgain;
struct sbus_dev *sdev;
unsigned int irq;
unsigned int regs_size;
struct snd_amd7930 *next;
} amd7930_t;
#define chip_t amd7930_t
static amd7930_t *amd7930_list;
/* Idle the AMD7930 chip. The amd->lock is not held. */
static __inline__ void amd7930_idle(amd7930_t *amd)
{
unsigned long flags;
spin_lock_irqsave(&amd->lock, flags);
sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR);
sbus_writeb(0, amd->regs + AMD7930_DR);
spin_unlock_irqrestore(&amd->lock, flags);
}
/* Enable chip interrupts. The amd->lock is not held. */
static __inline__ void amd7930_enable_ints(amd7930_t *amd)
{
unsigned long flags;
spin_lock_irqsave(&amd->lock, flags);
sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR);
sbus_writeb(AM_INIT_ACTIVE, amd->regs + AMD7930_DR);
spin_unlock_irqrestore(&amd->lock, flags);
}
/* Disable chip interrupts. The amd->lock is not held. */
static __inline__ void amd7930_disable_ints(amd7930_t *amd)
{
unsigned long flags;
spin_lock_irqsave(&amd->lock, flags);
sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR);
sbus_writeb(AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS, amd->regs + AMD7930_DR);
spin_unlock_irqrestore(&amd->lock, flags);
}
/* Commit amd7930_map settings to the hardware.
* The amd->lock is held and local interrupts are disabled.
*/
static void __amd7930_write_map(amd7930_t *amd)
{
struct amd7930_map *map = &amd->map;
sbus_writeb(AMR_MAP_GX, amd->regs + AMD7930_CR);
sbus_writeb(((map->gx >> 0) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(((map->gx >> 8) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(AMR_MAP_GR, amd->regs + AMD7930_CR);
sbus_writeb(((map->gr >> 0) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(((map->gr >> 8) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(AMR_MAP_STGR, amd->regs + AMD7930_CR);
sbus_writeb(((map->stgr >> 0) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(((map->stgr >> 8) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(AMR_MAP_GER, amd->regs + AMD7930_CR);
sbus_writeb(((map->ger >> 0) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(((map->ger >> 8) & 0xff), amd->regs + AMD7930_DR);
sbus_writeb(AMR_MAP_MMR1, amd->regs + AMD7930_CR);
sbus_writeb(map->mmr1, amd->regs + AMD7930_DR);
sbus_writeb(AMR_MAP_MMR2, amd->regs + AMD7930_CR);
sbus_writeb(map->mmr2, amd->regs + AMD7930_DR);
}
/* gx, gr & stg gains. this table must contain 256 elements with
* the 0th being "infinity" (the magic value 9008). The remaining
* elements match sun's gain curve (but with higher resolution):
* -18 to 0dB in .16dB steps then 0 to 12dB in .08dB steps.
*/
static __const__ __u16 gx_coeff[256] = {
0x9008, 0x8b7c, 0x8b51, 0x8b45, 0x8b42, 0x8b3b, 0x8b36, 0x8b33,
0x8b32, 0x8b2a, 0x8b2b, 0x8b2c, 0x8b25, 0x8b23, 0x8b22, 0x8b22,
0x9122, 0x8b1a, 0x8aa3, 0x8aa3, 0x8b1c, 0x8aa6, 0x912d, 0x912b,
0x8aab, 0x8b12, 0x8aaa, 0x8ab2, 0x9132, 0x8ab4, 0x913c, 0x8abb,
0x9142, 0x9144, 0x9151, 0x8ad5, 0x8aeb, 0x8a79, 0x8a5a, 0x8a4a,
0x8b03, 0x91c2, 0x91bb, 0x8a3f, 0x8a33, 0x91b2, 0x9212, 0x9213,
0x8a2c, 0x921d, 0x8a23, 0x921a, 0x9222, 0x9223, 0x922d, 0x9231,
0x9234, 0x9242, 0x925b, 0x92dd, 0x92c1, 0x92b3, 0x92ab, 0x92a4,
0x92a2, 0x932b, 0x9341, 0x93d3, 0x93b2, 0x93a2, 0x943c, 0x94b2,
0x953a, 0x9653, 0x9782, 0x9e21, 0x9d23, 0x9cd2, 0x9c23, 0x9baa,
0x9bde, 0x9b33, 0x9b22, 0x9b1d, 0x9ab2, 0xa142, 0xa1e5, 0x9a3b,
0xa213, 0xa1a2, 0xa231, 0xa2eb, 0xa313, 0xa334, 0xa421, 0xa54b,
0xada4, 0xac23, 0xab3b, 0xaaab, 0xaa5c, 0xb1a3, 0xb2ca, 0xb3bd,
0xbe24, 0xbb2b, 0xba33, 0xc32b, 0xcb5a, 0xd2a2, 0xe31d, 0x0808,
0x72ba, 0x62c2, 0x5c32, 0x52db, 0x513e, 0x4cce, 0x43b2, 0x4243,
0x41b4, 0x3b12, 0x3bc3, 0x3df2, 0x34bd, 0x3334, 0x32c2, 0x3224,
0x31aa, 0x2a7b, 0x2aaa, 0x2b23, 0x2bba, 0x2c42, 0x2e23, 0x25bb,
0x242b, 0x240f, 0x231a, 0x22bb, 0x2241, 0x2223, 0x221f, 0x1a33,
0x1a4a, 0x1acd, 0x2132, 0x1b1b, 0x1b2c, 0x1b62, 0x1c12, 0x1c32,
0x1d1b, 0x1e71, 0x16b1, 0x1522, 0x1434, 0x1412, 0x1352, 0x1323,
0x1315, 0x12bc, 0x127a, 0x1235, 0x1226, 0x11a2, 0x1216, 0x0a2a,
0x11bc, 0x11d1, 0x1163, 0x0ac2, 0x0ab2, 0x0aab, 0x0b1b, 0x0b23,
0x0b33, 0x0c0f, 0x0bb3, 0x0c1b, 0x0c3e, 0x0cb1, 0x0d4c, 0x0ec1,
0x079a, 0x0614, 0x0521, 0x047c, 0x0422, 0x03b1, 0x03e3, 0x0333,
0x0322, 0x031c, 0x02aa, 0x02ba, 0x02f2, 0x0242, 0x0232, 0x0227,
0x0222, 0x021b, 0x01ad, 0x0212, 0x01b2, 0x01bb, 0x01cb, 0x01f6,
0x0152, 0x013a, 0x0133, 0x0131, 0x012c, 0x0123, 0x0122, 0x00a2,
0x011b, 0x011e, 0x0114, 0x00b1, 0x00aa, 0x00b3, 0x00bd, 0x00ba,
0x00c5, 0x00d3, 0x00f3, 0x0062, 0x0051, 0x0042, 0x003b, 0x0033,
0x0032, 0x002a, 0x002c, 0x0025, 0x0023, 0x0022, 0x001a, 0x0021,
0x001b, 0x001b, 0x001d, 0x0015, 0x0013, 0x0013, 0x0012, 0x0012,
0x000a, 0x000a, 0x0011, 0x0011, 0x000b, 0x000b, 0x000c, 0x000e,
};
static __const__ __u16 ger_coeff[] = {
0x431f, /* 5. dB */
0x331f, /* 5.5 dB */
0x40dd, /* 6. dB */
0x11dd, /* 6.5 dB */
0x440f, /* 7. dB */
0x411f, /* 7.5 dB */
0x311f, /* 8. dB */
0x5520, /* 8.5 dB */
0x10dd, /* 9. dB */
0x4211, /* 9.5 dB */
0x410f, /* 10. dB */
0x111f, /* 10.5 dB */
0x600b, /* 11. dB */
0x00dd, /* 11.5 dB */
0x4210, /* 12. dB */
0x110f, /* 13. dB */
0x7200, /* 14. dB */
0x2110, /* 15. dB */
0x2200, /* 15.9 dB */
0x000b, /* 16.9 dB */
0x000f /* 18. dB */
};
#define NR_GER_COEFFS (sizeof(ger_coeff) / sizeof(ger_coeff[0]))
/* Update amd7930_map settings and program them into the hardware.
* The amd->lock is held and local interrupts are disabled.
*/
static void __amd7930_update_map(amd7930_t *amd)
{
struct amd7930_map *map = &amd->map;
int level;
map->gx = gx_coeff[amd->rgain];
map->stgr = gx_coeff[amd->mgain];
level = (amd->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];
}
__amd7930_write_map(amd);
}
static void snd_amd7930_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
amd7930_t *amd = dev_id;
unsigned int elapsed;
u8 ir;
spin_lock(&amd->lock);
elapsed = 0;
ir = sbus_readb(amd->regs + AMD7930_IR);
if (ir & AMR_IR_BBUF) {
u8 byte;
if (amd->flags & AMD7930_FLAG_PLAYBACK) {
if (amd->p_left > 0) {
byte = *(amd->p_cur++);
amd->p_left--;
sbus_writeb(byte, amd->regs + AMD7930_BBTB);
if (amd->p_left == 0)
elapsed |= AMD7930_FLAG_PLAYBACK;
} else
sbus_writeb(0, amd->regs + AMD7930_BBTB);
} else if (amd->flags & AMD7930_FLAG_CAPTURE) {
byte = sbus_readb(amd->regs + AMD7930_BBRB);
if (amd->c_left > 0) {
*(amd->c_cur++) = byte;
amd->c_left--;
if (amd->c_left == 0)
elapsed |= AMD7930_FLAG_CAPTURE;
}
}
}
spin_unlock(&amd->lock);
if (elapsed & AMD7930_FLAG_PLAYBACK)
snd_pcm_period_elapsed(amd->playback_substream);
else
snd_pcm_period_elapsed(amd->capture_substream);
}
static int snd_amd7930_trigger(amd7930_t *amd, unsigned int flag, int cmd)
{
unsigned long flags;
int result = 0;
spin_lock_irqsave(&amd->lock, flags);
if (cmd == SNDRV_PCM_TRIGGER_START) {
if (!(amd->flags & flag)) {
amd->flags |= flag;
/* Enable B channel interrupts. */
sbus_writeb(AMR_MUX_MCR4, amd->regs + AMD7930_CR);
sbus_writeb(AM_MUX_MCR4_ENABLE_INTS, amd->regs + AMD7930_DR);
}
} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
if (amd->flags & flag) {
amd->flags &= ~flag;
/* Disable B channel interrupts. */
sbus_writeb(AMR_MUX_MCR4, amd->regs + AMD7930_CR);
sbus_writeb(0, amd->regs + AMD7930_DR);
}
} else {
result = -EINVAL;
}
spin_unlock_irqrestore(&amd->lock, flags);
return result;
}
static int snd_amd7930_playback_trigger(snd_pcm_substream_t * substream,
int cmd)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
return snd_amd7930_trigger(amd, AMD7930_FLAG_PLAYBACK, cmd);
}
static int snd_amd7930_capture_trigger(snd_pcm_substream_t * substream,
int cmd)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
return snd_amd7930_trigger(amd, AMD7930_FLAG_CAPTURE, cmd);
}
static int snd_amd7930_playback_prepare(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned long flags;
u8 new_mmr1;
spin_lock_irqsave(&amd->lock, flags);
amd->flags |= AMD7930_FLAG_PLAYBACK;
/* Setup the pseudo-dma transfer pointers. */
amd->p_orig = amd->p_cur = runtime->dma_area;
amd->p_left = size;
/* Put the chip into the correct encoding format. */
new_mmr1 = amd->map.mmr1;
if (runtime->format == SNDRV_PCM_FORMAT_A_LAW)
new_mmr1 |= AM_MAP_MMR1_ALAW;
else
new_mmr1 &= ~AM_MAP_MMR1_ALAW;
if (new_mmr1 != amd->map.mmr1) {
amd->map.mmr1 = new_mmr1;
__amd7930_update_map(amd);
}
spin_unlock_irqrestore(&amd->lock, flags);
return 0;
}
static int snd_amd7930_capture_prepare(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned long flags;
u8 new_mmr1;
spin_lock_irqsave(&amd->lock, flags);
amd->flags |= AMD7930_FLAG_CAPTURE;
/* Setup the pseudo-dma transfer pointers. */
amd->c_orig = amd->c_cur = runtime->dma_area;
amd->c_left = size;
/* Put the chip into the correct encoding format. */
new_mmr1 = amd->map.mmr1;
if (runtime->format == SNDRV_PCM_FORMAT_A_LAW)
new_mmr1 |= AM_MAP_MMR1_ALAW;
else
new_mmr1 &= ~AM_MAP_MMR1_ALAW;
if (new_mmr1 != amd->map.mmr1) {
amd->map.mmr1 = new_mmr1;
__amd7930_update_map(amd);
}
spin_unlock_irqrestore(&amd->lock, flags);
return 0;
}
static snd_pcm_uframes_t snd_amd7930_playback_pointer(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
size_t ptr;
if (!(amd->flags & AMD7930_FLAG_PLAYBACK))
return 0;
ptr = amd->p_cur - amd->p_orig;
return bytes_to_frames(substream->runtime, ptr);
}
static snd_pcm_uframes_t snd_amd7930_capture_pointer(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
size_t ptr;
if (!(amd->flags & AMD7930_FLAG_CAPTURE))
return 0;
ptr = amd->c_cur - amd->c_orig;
return bytes_to_frames(substream->runtime, ptr);
}
/* Playback and capture have identical properties. */
static snd_pcm_hardware_t snd_amd7930_pcm_hw =
{
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_HALF_DUPLEX),
.formats = SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
.rates = SNDRV_PCM_RATE_8000,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = (64*1024),
.period_bytes_min = 1,
.period_bytes_max = (64*1024),
.periods_min = 1,
.periods_max = 1024,
};
static int snd_amd7930_playback_open(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
amd->playback_substream = substream;
runtime->hw = snd_amd7930_pcm_hw;
return 0;
}
static int snd_amd7930_capture_open(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
amd->capture_substream = substream;
runtime->hw = snd_amd7930_pcm_hw;
return 0;
}
static int snd_amd7930_playback_close(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
amd->playback_substream = NULL;
return 0;
}
static int snd_amd7930_capture_close(snd_pcm_substream_t * substream)
{
amd7930_t *amd = snd_pcm_substream_chip(substream);
amd->capture_substream = NULL;
return 0;
}
static int snd_amd7930_hw_params(snd_pcm_substream_t * substream,
snd_pcm_hw_params_t * hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
static int snd_amd7930_hw_free(snd_pcm_substream_t * substream)
{
return snd_pcm_lib_free_pages(substream);
}
static snd_pcm_ops_t snd_amd7930_playback_ops = {
.open = snd_amd7930_playback_open,
.close = snd_amd7930_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_amd7930_hw_params,
.hw_free = snd_amd7930_hw_free,
.prepare = snd_amd7930_playback_prepare,
.trigger = snd_amd7930_playback_trigger,
.pointer = snd_amd7930_playback_pointer,
};
static snd_pcm_ops_t snd_amd7930_capture_ops = {
.open = snd_amd7930_capture_open,
.close = snd_amd7930_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_amd7930_hw_params,
.hw_free = snd_amd7930_hw_free,
.prepare = snd_amd7930_capture_prepare,
.trigger = snd_amd7930_capture_trigger,
.pointer = snd_amd7930_capture_pointer,
};
static void snd_amd7930_pcm_free(snd_pcm_t *pcm)
{
amd7930_t *amd = snd_magic_cast(amd7930_t, pcm->private_data, return);
amd->pcm = NULL;
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static int __init snd_amd7930_pcm(amd7930_t *amd)
{
snd_pcm_t *pcm;
int err;
if ((err = snd_pcm_new(amd->card,
/* ID */ "sun_amd7930",
/* device */ 0,
/* playback count */ 1,
/* capture count */ 1, &pcm)) < 0)
return err;
snd_assert(pcm != NULL, return -EINVAL);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_amd7930_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_amd7930_capture_ops);
pcm->private_data = amd;
pcm->private_free = snd_amd7930_pcm_free;
pcm->info_flags = 0;
strcpy(pcm->name, amd->card->shortname);
amd->pcm = pcm;
snd_pcm_lib_preallocate_pages_for_all(pcm, 64*1024, 64*1024, GFP_KERNEL);
return 0;
}
#define VOLUME_MONITOR 0
#define VOLUME_CAPTURE 1
#define VOLUME_PLAYBACK 2
static int snd_amd7930_info_volume(snd_kcontrol_t *kctl, snd_ctl_elem_info_t *uinfo)
{
int type = kctl->private_value;
snd_assert(type == VOLUME_MONITOR ||
type == VOLUME_CAPTURE ||
type == VOLUME_PLAYBACK, return -EINVAL);
(void) type;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 255;
return 0;
}
static int snd_amd7930_get_volume(snd_kcontrol_t *kctl, snd_ctl_elem_value_t *ucontrol)
{
amd7930_t *amd = snd_kcontrol_chip(kctl);
int type = kctl->private_value;
int *swval;
snd_assert(type == VOLUME_MONITOR ||
type == VOLUME_CAPTURE ||
type == VOLUME_PLAYBACK, return -EINVAL);
switch (type) {
case VOLUME_MONITOR:
swval = &amd->mgain;
break;
case VOLUME_CAPTURE:
swval = &amd->rgain;
break;
case VOLUME_PLAYBACK:
default:
swval = &amd->pgain;
break;
};
ucontrol->value.integer.value[0] = *swval;
return 0;
}
static int snd_amd7930_put_volume(snd_kcontrol_t *kctl, snd_ctl_elem_value_t *ucontrol)
{
amd7930_t *amd = snd_kcontrol_chip(kctl);
unsigned long flags;
int type = kctl->private_value;
int *swval, change;
snd_assert(type == VOLUME_MONITOR ||
type == VOLUME_CAPTURE ||
type == VOLUME_PLAYBACK, return -EINVAL);
switch (type) {
case VOLUME_MONITOR:
swval = &amd->mgain;
break;
case VOLUME_CAPTURE:
swval = &amd->rgain;
break;
case VOLUME_PLAYBACK:
default:
swval = &amd->pgain;
break;
};
spin_lock_irqsave(&amd->lock, flags);
if (*swval != ucontrol->value.integer.value[0]) {
*swval = ucontrol->value.integer.value[0];
__amd7930_update_map(amd);
change = 1;
} else
change = 0;
spin_unlock_irqrestore(&amd->lock, flags);
return change;
}
static snd_kcontrol_new_t amd7930_controls[] __initdata = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Monitor Volume",
.index = 0,
.info = snd_amd7930_info_volume,
.get = snd_amd7930_get_volume,
.put = snd_amd7930_put_volume,
.private_value = VOLUME_MONITOR,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Volume",
.index = 0,
.info = snd_amd7930_info_volume,
.get = snd_amd7930_get_volume,
.put = snd_amd7930_put_volume,
.private_value = VOLUME_CAPTURE,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Playback Volume",
.index = 0,
.info = snd_amd7930_info_volume,
.get = snd_amd7930_get_volume,
.put = snd_amd7930_put_volume,
.private_value = VOLUME_PLAYBACK,
},
};
#define NUM_AMD7930_CONTROLS (sizeof(amd7930_controls)/sizeof(snd_kcontrol_new_t))
static int __init snd_amd7930_mixer(amd7930_t *amd)
{
snd_card_t *card;
int idx, err;
snd_assert(amd != NULL && amd->card != NULL, return -EINVAL);
card = amd->card;
strcpy(card->mixername, card->shortname);
for (idx = 0; idx < NUM_AMD7930_CONTROLS; idx++) {
if ((err = snd_ctl_add(card,
snd_ctl_new1(&amd7930_controls[idx], amd))) < 0)
return err;
}
return 0;
}
static int snd_amd7930_free(amd7930_t *amd)
{
amd7930_idle(amd);
if (amd->irq)
free_irq(amd->irq, amd);
if (amd->regs)
sbus_iounmap(amd->regs, amd->regs_size);
snd_magic_kfree(amd);
return 0;
}
static int snd_amd7930_dev_free(snd_device_t *device)
{
amd7930_t *amd = snd_magic_cast(amd7930_t, device->device_data, return -ENXIO);
return snd_amd7930_free(amd);
}
static snd_device_ops_t snd_amd7930_dev_ops = {
.dev_free = snd_amd7930_dev_free,
};
static int __init snd_amd7930_create(snd_card_t *card,
struct sbus_dev *sdev,
struct resource *rp,
unsigned int reg_size,
struct linux_prom_irqs *irq_prop,
int dev,
amd7930_t **ramd)
{
unsigned long flags;
amd7930_t *amd;
int err;
*ramd = NULL;
amd = snd_magic_kcalloc(amd7930_t, 0, GFP_KERNEL);
if (amd == NULL)
return -ENOMEM;
spin_lock_init(&amd->lock);
amd->card = card;
amd->sdev = sdev;
amd->regs_size = reg_size;
amd->regs = sbus_ioremap(rp, 0, amd->regs_size, "amd7930");
if (!amd->regs) {
snd_printk("amd7930-%d: Unable to map chip registers.\n", dev);
return -EIO;
}
amd7930_idle(amd);
if (request_irq(irq_prop->pri, snd_amd7930_interrupt,
SA_INTERRUPT | SA_SHIRQ, "amd7930", amd)) {
snd_amd7930_free(amd);
snd_printk("amd7930-%d: Unable to grab IRQ %s\n",
dev,
__irq_itoa(irq_prop->pri));
return -EBUSY;
}
amd->irq = irq_prop->pri;
amd7930_enable_ints(amd);
spin_lock_irqsave(&amd->lock, flags);
amd->rgain = 128;
amd->pgain = 200;
amd->mgain = 0;
memset(&amd->map, 0, sizeof(amd->map));
amd->map.mmr1 = (AM_MAP_MMR1_GX | AM_MAP_MMR1_GER |
AM_MAP_MMR1_GR | AM_MAP_MMR1_STG);
amd->map.mmr2 = (AM_MAP_MMR2_LS | AM_MAP_MMR2_AINB);
__amd7930_update_map(amd);
/* Always MUX audio (Ba) to channel Bb. */
sbus_writeb(AMR_MUX_MCR1, amd->regs + AMD7930_CR);
sbus_writeb(AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bb << 4),
amd->regs + AMD7930_DR);
spin_unlock_irqrestore(&amd->lock, flags);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
amd, &snd_amd7930_dev_ops)) < 0) {
snd_amd7930_free(amd);
return err;
}
*ramd = amd;
return 0;
}
static int __init amd7930_attach(int prom_node, struct sbus_dev *sdev)
{
static int dev;
struct linux_prom_registers reg_prop;
struct linux_prom_irqs irq_prop;
struct resource res, *rp;
snd_card_t *card;
amd7930_t *amd;
int err;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!snd_enable[dev]) {
dev++;
return -ENOENT;
}
err = prom_getproperty(prom_node, "intr",
(char *) &irq_prop, sizeof(irq_prop));
if (err < 0) {
snd_printk("amd7930-%d: Firmware node lacks IRQ property.\n", dev);
return -ENODEV;
}
err = prom_getproperty(prom_node, "reg",
(char *) &reg_prop, sizeof(reg_prop));
if (err < 0) {
snd_printk("amd7930-%d: Firmware node lacks register property.\n", dev);
return -ENODEV;
}
if (sdev) {
rp = &sdev->resource[0];
} else {
rp = &res;
rp->start = reg_prop.phys_addr;
rp->end = rp->start + reg_prop.reg_size - 1;
rp->flags = IORESOURCE_IO | (reg_prop.which_io & 0xff);
}
card = snd_card_new(snd_index[dev], snd_id[dev], THIS_MODULE, 0);
if (card == NULL)
return -ENOMEM;
strcpy(card->driver, "AMD7930");
strcpy(card->shortname, "Sun AMD7930");
sprintf(card->longname, "%s at 0x%02lx:0x%08lx, irq %s",
card->shortname,
rp->flags & 0xffL,
rp->start,
__irq_itoa(irq_prop.pri));
if ((err = snd_amd7930_create(card, sdev, rp, reg_prop.reg_size,
&irq_prop, dev, &amd)) < 0)
goto out_err;
if ((err = snd_amd7930_pcm(amd)) < 0)
goto out_err;
if ((err = snd_amd7930_mixer(amd)) < 0)
goto out_err;
if ((err = snd_card_register(card)) < 0)
goto out_err;
amd->next = amd7930_list;
amd7930_list = amd;
dev++;
return 0;
out_err:
snd_card_free(card);
return err;
}
static int __init amd7930_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
int node, found;
found = 0;
/* Try to find the sun4c "audio" node first. */
node = prom_getchild(prom_root_node);
node = prom_searchsiblings(node, "audio");
if (node && amd7930_attach(node, NULL) == 0)
found++;
/* Probe each SBUS for amd7930 chips. */
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "audio")) {
if (amd7930_attach(sdev->prom_node, sdev) == 0)
found++;
}
}
return (found > 0) ? 0 : -EIO;
}
static void __exit amd7930_exit(void)
{
amd7930_t *p = amd7930_list;
while (p != NULL) {
amd7930_t *next = p->next;
snd_card_free(p->card);
p = next;
}
amd7930_list = NULL;
}
module_init(amd7930_init);
module_exit(amd7930_exit);
#ifndef MODULE
/* format is: snd-sun-amd7930=snd_index,snd_id,snd_enable */
static int __init alsa_card_sun_amd7930_setup(char *str)
{
static unsigned __initdata nr_dev = 0;
if (nr_dev >= SNDRV_CARDS)
return 0;
(void)(get_option(&str,&snd_index[nr_dev]) == 2 &&
get_option(&str,&snd_id[nr_dev]) == 2 &&
get_id(&str,&snd_enable[nr_dev]) == 2);
nr_dev++;
return 1;
}
__setup("snd-sun-amd7930=", alsa_card_sun_amd7930_setup);
#endif /* ifndef MODULE */
/*
* Driver for CS4231 sound chips found on Sparcs.
* Copyright (C) 2002 David S. Miller <davem@redhat.com>
*
* Based entirely upon drivers/sbus/audio/cs4231.c which is:
* Copyright (C) 1996, 1997, 1998, 1998 Derrick J Brashear (shadow@andrew.cmu.edu)
* and also sound/isa/cs423x/cs4231_lib.c which is:
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/timer.h>
#define SNDRV_GET_ID
#include <sound/initval.h>
#include <asm/io.h>
#include <asm/irq.h>
#ifdef CONFIG_SBUS
#define SBUS_SUPPORT
#endif
#ifdef SBUS_SUPPORT
#include <asm/sbus.h>
#endif
#if defined(CONFIG_PCI) && defined(CONFIG_SPARC64)
#define EBUS_SUPPORT
#endif
#ifdef EBUS_SUPPORT
#include <linux/pci.h>
#include <asm/ebus.h>
#endif
static int snd_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *snd_id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int snd_enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for Sun CS4231 soundcard.");
MODULE_PARM_SYNTAX(snd_index, SNDRV_INDEX_DESC);
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for Sun CS4231 soundcard.");
MODULE_PARM_SYNTAX(snd_id, SNDRV_ID_DESC);
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable Sun CS4231 soundcard.");
MODULE_PARM_SYNTAX(snd_enable, SNDRV_ENABLE_DESC);
MODULE_AUTHOR("Jaroslav Kysela, Derrick J. Brashear and David S. Miller");
MODULE_DESCRIPTION("Sun CS4231");
MODULE_LICENSE("GPL");
MODULE_CLASSES("{sound}");
MODULE_DEVICES("{{Sun,CS4231}}");
typedef struct snd_cs4231 {
spinlock_t lock;
unsigned long port;
unsigned long eb2c;
unsigned long eb2p;
u32 flags;
#define CS4231_FLAG_EBUS 0x00000001
#define CS4231_FLAG_PLAYBACK 0x00000002
#define CS4231_FLAG_CAPTURE 0x00000004
snd_card_t *card;
snd_pcm_t *pcm;
snd_pcm_substream_t *playback_substream;
snd_pcm_substream_t *capture_substream;
snd_timer_t *timer;
unsigned short mode;
#define CS4231_MODE_NONE 0x0000
#define CS4231_MODE_PLAY 0x0001
#define CS4231_MODE_RECORD 0x0002
#define CS4231_MODE_TIMER 0x0004
#define CS4231_MODE_OPEN (CS4231_MODE_PLAY|CS4231_MODE_RECORD|CS4231_MODE_TIMER)
unsigned char image[32]; /* registers image */
int mce_bit;
int calibrate_mute;
unsigned int p_dma_size;
unsigned int c_dma_size;
struct semaphore mce_mutex;
struct semaphore open_mutex;
union {
#ifdef SBUS_SUPPORT
struct sbus_dev *sdev;
#endif
#ifdef EBUS_SUPPORT
struct pci_dev *pdev;
#endif
} dev_u;
unsigned int irq[2];
unsigned int regs_size;
struct snd_cs4231 *next;
} cs4231_t;
#define chip_t cs4231_t
static cs4231_t *cs4231_list;
/* Eventually we can use sound/isa/cs423x/cs4231_lib.c directly, but for
* now.... -DaveM
*/
/* IO ports */
#define CS4231P(chip, x) ((chip)->port + c_d_c_CS4231##x)
#define c_d_c_CS4231REGSEL 0
#define c_d_c_CS4231REG 1
#define c_d_c_CS4231STATUS 2
#define c_d_c_CS4231PIO 3
/* codec registers */
#define CS4231_LEFT_INPUT 0x00 /* left input control */
#define CS4231_RIGHT_INPUT 0x01 /* right input control */
#define CS4231_AUX1_LEFT_INPUT 0x02 /* left AUX1 input control */
#define CS4231_AUX1_RIGHT_INPUT 0x03 /* right AUX1 input control */
#define CS4231_AUX2_LEFT_INPUT 0x04 /* left AUX2 input control */
#define CS4231_AUX2_RIGHT_INPUT 0x05 /* right AUX2 input control */
#define CS4231_LEFT_OUTPUT 0x06 /* left output control register */
#define CS4231_RIGHT_OUTPUT 0x07 /* right output control register */
#define CS4231_PLAYBK_FORMAT 0x08 /* clock and data format - playback - bits 7-0 MCE */
#define CS4231_IFACE_CTRL 0x09 /* interface control - bits 7-2 MCE */
#define CS4231_PIN_CTRL 0x0a /* pin control */
#define CS4231_TEST_INIT 0x0b /* test and initialization */
#define CS4231_MISC_INFO 0x0c /* miscellaneaous information */
#define CS4231_LOOPBACK 0x0d /* loopback control */
#define CS4231_PLY_UPR_CNT 0x0e /* playback upper base count */
#define CS4231_PLY_LWR_CNT 0x0f /* playback lower base count */
#define CS4231_ALT_FEATURE_1 0x10 /* alternate #1 feature enable */
#define CS4231_ALT_FEATURE_2 0x11 /* alternate #2 feature enable */
#define CS4231_LEFT_LINE_IN 0x12 /* left line input control */
#define CS4231_RIGHT_LINE_IN 0x13 /* right line input control */
#define CS4231_TIMER_LOW 0x14 /* timer low byte */
#define CS4231_TIMER_HIGH 0x15 /* timer high byte */
#define CS4231_LEFT_MIC_INPUT 0x16 /* left MIC input control register (InterWave only) */
#define CS4231_RIGHT_MIC_INPUT 0x17 /* right MIC input control register (InterWave only) */
#define CS4236_EXT_REG 0x17 /* extended register access */
#define CS4231_IRQ_STATUS 0x18 /* irq status register */
#define CS4231_LINE_LEFT_OUTPUT 0x19 /* left line output control register (InterWave only) */
#define CS4231_VERSION 0x19 /* CS4231(A) - version values */
#define CS4231_MONO_CTRL 0x1a /* mono input/output control */
#define CS4231_LINE_RIGHT_OUTPUT 0x1b /* right line output control register (InterWave only) */
#define CS4235_LEFT_MASTER 0x1b /* left master output control */
#define CS4231_REC_FORMAT 0x1c /* clock and data format - record - bits 7-0 MCE */
#define CS4231_PLY_VAR_FREQ 0x1d /* playback variable frequency */
#define CS4235_RIGHT_MASTER 0x1d /* right master output control */
#define CS4231_REC_UPR_CNT 0x1e /* record upper count */
#define CS4231_REC_LWR_CNT 0x1f /* record lower count */
/* definitions for codec register select port - CODECP( REGSEL ) */
#define CS4231_INIT 0x80 /* CODEC is initializing */
#define CS4231_MCE 0x40 /* mode change enable */
#define CS4231_TRD 0x20 /* transfer request disable */
/* definitions for codec status register - CODECP( STATUS ) */
#define CS4231_GLOBALIRQ 0x01 /* IRQ is active */
/* definitions for codec irq status */
#define CS4231_PLAYBACK_IRQ 0x10
#define CS4231_RECORD_IRQ 0x20
#define CS4231_TIMER_IRQ 0x40
#define CS4231_ALL_IRQS 0x70
#define CS4231_REC_UNDERRUN 0x08
#define CS4231_REC_OVERRUN 0x04
#define CS4231_PLY_OVERRUN 0x02
#define CS4231_PLY_UNDERRUN 0x01
/* definitions for CS4231_LEFT_INPUT and CS4231_RIGHT_INPUT registers */
#define CS4231_ENABLE_MIC_GAIN 0x20
#define CS4231_MIXS_LINE 0x00
#define CS4231_MIXS_AUX1 0x40
#define CS4231_MIXS_MIC 0x80
#define CS4231_MIXS_ALL 0xc0
/* definitions for clock and data format register - CS4231_PLAYBK_FORMAT */
#define CS4231_LINEAR_8 0x00 /* 8-bit unsigned data */
#define CS4231_ALAW_8 0x60 /* 8-bit A-law companded */
#define CS4231_ULAW_8 0x20 /* 8-bit U-law companded */
#define CS4231_LINEAR_16 0x40 /* 16-bit twos complement data - little endian */
#define CS4231_LINEAR_16_BIG 0xc0 /* 16-bit twos complement data - big endian */
#define CS4231_ADPCM_16 0xa0 /* 16-bit ADPCM */
#define CS4231_STEREO 0x10 /* stereo mode */
/* bits 3-1 define frequency divisor */
#define CS4231_XTAL1 0x00 /* 24.576 crystal */
#define CS4231_XTAL2 0x01 /* 16.9344 crystal */
/* definitions for interface control register - CS4231_IFACE_CTRL */
#define CS4231_RECORD_PIO 0x80 /* record PIO enable */
#define CS4231_PLAYBACK_PIO 0x40 /* playback PIO enable */
#define CS4231_CALIB_MODE 0x18 /* calibration mode bits */
#define CS4231_AUTOCALIB 0x08 /* auto calibrate */
#define CS4231_SINGLE_DMA 0x04 /* use single DMA channel */
#define CS4231_RECORD_ENABLE 0x02 /* record enable */
#define CS4231_PLAYBACK_ENABLE 0x01 /* playback enable */
/* definitions for pin control register - CS4231_PIN_CTRL */
#define CS4231_IRQ_ENABLE 0x02 /* enable IRQ */
#define CS4231_XCTL1 0x40 /* external control #1 */
#define CS4231_XCTL0 0x80 /* external control #0 */
/* definitions for test and init register - CS4231_TEST_INIT */
#define CS4231_CALIB_IN_PROGRESS 0x20 /* auto calibrate in progress */
#define CS4231_DMA_REQUEST 0x10 /* DMA request in progress */
/* definitions for misc control register - CS4231_MISC_INFO */
#define CS4231_MODE2 0x40 /* MODE 2 */
#define CS4231_IW_MODE3 0x6c /* MODE 3 - InterWave enhanced mode */
#define CS4231_4236_MODE3 0xe0 /* MODE 3 - CS4236+ enhanced mode */
/* definitions for alternate feature 1 register - CS4231_ALT_FEATURE_1 */
#define CS4231_DACZ 0x01 /* zero DAC when underrun */
#define CS4231_TIMER_ENABLE 0x40 /* codec timer enable */
#define CS4231_OLB 0x80 /* output level bit */
/* SBUS DMA register defines. */
#define APCCSR 0x10UL /* APC DMA CSR */
#define APCCVA 0x20UL /* APC Capture DMA Address */
#define APCCC 0x24UL /* APC Capture Count */
#define APCCNVA 0x28UL /* APC Capture DMA Next Address */
#define APCCNC 0x2cUL /* APC Capture Next Count */
#define APCPVA 0x30UL /* APC Play DMA Address */
#define APCPC 0x34UL /* APC Play Count */
#define APCPNVA 0x38UL /* APC Play DMA Next Address */
#define APCPNC 0x3cUL /* APC Play Next Count */
/* APCCSR bits */
#define APC_INT_PENDING 0x800000 /* Interrupt Pending */
#define APC_PLAY_INT 0x400000 /* Playback interrupt */
#define APC_CAPT_INT 0x200000 /* Capture interrupt */
#define APC_GENL_INT 0x100000 /* General interrupt */
#define APC_XINT_ENA 0x80000 /* General ext int. enable */
#define APC_XINT_PLAY 0x40000 /* Playback ext intr */
#define APC_XINT_CAPT 0x20000 /* Capture ext intr */
#define APC_XINT_GENL 0x10000 /* Error ext intr */
#define APC_XINT_EMPT 0x8000 /* Pipe empty interrupt (0 write to pva) */
#define APC_XINT_PEMP 0x4000 /* Play pipe empty (pva and pnva not set) */
#define APC_XINT_PNVA 0x2000 /* Playback NVA dirty */
#define APC_XINT_PENA 0x1000 /* play pipe empty Int enable */
#define APC_XINT_COVF 0x800 /* Cap data dropped on floor */
#define APC_XINT_CNVA 0x400 /* Capture NVA dirty */
#define APC_XINT_CEMP 0x200 /* Capture pipe empty (cva and cnva not set) */
#define APC_XINT_CENA 0x100 /* Cap. pipe empty int enable */
#define APC_PPAUSE 0x80 /* Pause the play DMA */
#define APC_CPAUSE 0x40 /* Pause the capture DMA */
#define APC_CDC_RESET 0x20 /* CODEC RESET */
#define APC_PDMA_READY 0x08 /* Play DMA Go */
#define APC_CDMA_READY 0x04 /* Capture DMA Go */
#define APC_CHIP_RESET 0x01 /* Reset the chip */
/* EBUS DMA register offsets */
#define EBDMA_CSR 0x00UL /* Control/Status */
#define EBDMA_ADDR 0x04UL /* DMA Address */
#define EBDMA_COUNT 0x08UL /* DMA Count */
/*
* Some variables
*/
static unsigned char freq_bits[14] = {
/* 5510 */ 0x00 | CS4231_XTAL2,
/* 6620 */ 0x0E | CS4231_XTAL2,
/* 8000 */ 0x00 | CS4231_XTAL1,
/* 9600 */ 0x0E | CS4231_XTAL1,
/* 11025 */ 0x02 | CS4231_XTAL2,
/* 16000 */ 0x02 | CS4231_XTAL1,
/* 18900 */ 0x04 | CS4231_XTAL2,
/* 22050 */ 0x06 | CS4231_XTAL2,
/* 27042 */ 0x04 | CS4231_XTAL1,
/* 32000 */ 0x06 | CS4231_XTAL1,
/* 33075 */ 0x0C | CS4231_XTAL2,
/* 37800 */ 0x08 | CS4231_XTAL2,
/* 44100 */ 0x0A | CS4231_XTAL2,
/* 48000 */ 0x0C | CS4231_XTAL1
};
static unsigned int rates[14] = {
5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
27042, 32000, 33075, 37800, 44100, 48000
};
static snd_pcm_hw_constraint_list_t hw_constraints_rates = {
count: 14,
list: rates,
mask: 0,
};
static int snd_cs4231_xrate(snd_pcm_runtime_t *runtime)
{
return snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&hw_constraints_rates);
}
static unsigned char snd_cs4231_original_image[32] =
{
0x00, /* 00/00 - lic */
0x00, /* 01/01 - ric */
0x9f, /* 02/02 - la1ic */
0x9f, /* 03/03 - ra1ic */
0x9f, /* 04/04 - la2ic */
0x9f, /* 05/05 - ra2ic */
0xbf, /* 06/06 - loc */
0xbf, /* 07/07 - roc */
0x20, /* 08/08 - pdfr */
CS4231_AUTOCALIB, /* 09/09 - ic */
0x00, /* 0a/10 - pc */
0x00, /* 0b/11 - ti */
CS4231_MODE2, /* 0c/12 - mi */
0xfc, /* 0d/13 - lbc */
0x00, /* 0e/14 - pbru */
0x00, /* 0f/15 - pbrl */
0x80, /* 10/16 - afei */
0x01, /* 11/17 - afeii */
0x9f, /* 12/18 - llic */
0x9f, /* 13/19 - rlic */
0x00, /* 14/20 - tlb */
0x00, /* 15/21 - thb */
0x00, /* 16/22 - la3mic/reserved */
0x00, /* 17/23 - ra3mic/reserved */
0x00, /* 18/24 - afs */
0x00, /* 19/25 - lamoc/version */
0xcf, /* 1a/26 - mioc */
0x00, /* 1b/27 - ramoc/reserved */
0x20, /* 1c/28 - cdfr */
0x00, /* 1d/29 - res4 */
0x00, /* 1e/30 - cbru */
0x00, /* 1f/31 - cbrl */
};
static u8 __cs4231_readb(cs4231_t *cp, unsigned long reg_addr)
{
#ifdef EBUS_SUPPORT
if (cp->flags & CS4231_FLAG_EBUS) {
return readb(reg_addr);
} else {
#endif
#ifdef SBUS_SUPPORT
return sbus_readb(reg_addr);
#endif
#ifdef EBUS_SUPPORT
}
#endif
}
static void __cs4231_writeb(cs4231_t *cp, u8 val, unsigned long reg_addr)
{
#ifdef EBUS_SUPPORT
if (cp->flags & CS4231_FLAG_EBUS) {
return writeb(val, reg_addr);
} else {
#endif
#ifdef SBUS_SUPPORT
return sbus_writeb(val, reg_addr);
#endif
#ifdef EBUS_SUPPORT
}
#endif
}
/*
* Basic I/O functions
*/
void snd_cs4231_outm(cs4231_t *chip, unsigned char reg,
unsigned char mask, unsigned char value)
{
int timeout;
unsigned char tmp;
for (timeout = 250;
timeout > 0 && (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT);
timeout--)
udelay(100);
#ifdef CONFIG_SND_DEBUG
if (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT)
snd_printk("outm: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
#endif
if (chip->calibrate_mute) {
chip->image[reg] &= mask;
chip->image[reg] |= value;
} else {
__cs4231_writeb(chip, chip->mce_bit | reg, CS4231P(chip, REGSEL));
mb();
tmp = (chip->image[reg] & mask) | value;
__cs4231_writeb(chip, tmp, CS4231P(chip, REG));
chip->image[reg] = tmp;
mb();
}
}
static void snd_cs4231_dout(cs4231_t *chip, unsigned char reg, unsigned char value)
{
int timeout;
for (timeout = 250;
timeout > 0 && (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT);
timeout--)
udelay(10);
__cs4231_writeb(chip, chip->mce_bit | reg, CS4231P(chip, REGSEL));
__cs4231_writeb(chip, value, CS4231P(chip, REG));
mb();
}
static void snd_cs4231_out(cs4231_t *chip, unsigned char reg, unsigned char value)
{
int timeout;
for (timeout = 250;
timeout > 0 && (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT);
timeout--)
udelay(100);
#ifdef CONFIG_SND_DEBUG
if (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT)
snd_printk("out: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
#endif
__cs4231_writeb(chip, chip->mce_bit | reg, CS4231P(chip, REGSEL));
__cs4231_writeb(chip, value, CS4231P(chip, REG));
chip->image[reg] = value;
mb();
#if 0
printk("codec out - reg 0x%x = 0x%x\n", chip->mce_bit | reg, value);
#endif
}
static unsigned char snd_cs4231_in(cs4231_t *chip, unsigned char reg)
{
int timeout;
for (timeout = 250;
timeout > 0 && (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT);
timeout--)
udelay(100);
#ifdef CONFIG_SND_DEBUG
if (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT)
snd_printk("in: auto calibration time out - reg = 0x%x\n", reg);
#endif
__cs4231_writeb(chip, chip->mce_bit | reg, CS4231P(chip, REGSEL));
mb();
return __cs4231_readb(chip, CS4231P(chip, REG));
}
#ifdef CONFIG_SND_DEBUG
void snd_cs4231_debug(cs4231_t *chip)
{
printk("CS4231 REGS: INDEX = 0x%02x ",
__cs4231_readb(chip, CS4231P(chip, REGSEL)));
printk(" STATUS = 0x%02x\n",
__cs4231_readb(chip, CS4231P(chip, STATUS)));
printk(" 0x00: left input = 0x%02x ", snd_cs4231_in(chip, 0x00));
printk(" 0x10: alt 1 (CFIG 2) = 0x%02x\n", snd_cs4231_in(chip, 0x10));
printk(" 0x01: right input = 0x%02x ", snd_cs4231_in(chip, 0x01));
printk(" 0x11: alt 2 (CFIG 3) = 0x%02x\n", snd_cs4231_in(chip, 0x11));
printk(" 0x02: GF1 left input = 0x%02x ", snd_cs4231_in(chip, 0x02));
printk(" 0x12: left line in = 0x%02x\n", snd_cs4231_in(chip, 0x12));
printk(" 0x03: GF1 right input = 0x%02x ", snd_cs4231_in(chip, 0x03));
printk(" 0x13: right line in = 0x%02x\n", snd_cs4231_in(chip, 0x13));
printk(" 0x04: CD left input = 0x%02x ", snd_cs4231_in(chip, 0x04));
printk(" 0x14: timer low = 0x%02x\n", snd_cs4231_in(chip, 0x14));
printk(" 0x05: CD right input = 0x%02x ", snd_cs4231_in(chip, 0x05));
printk(" 0x15: timer high = 0x%02x\n", snd_cs4231_in(chip, 0x15));
printk(" 0x06: left output = 0x%02x ", snd_cs4231_in(chip, 0x06));
printk(" 0x16: left MIC (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x16));
printk(" 0x07: right output = 0x%02x ", snd_cs4231_in(chip, 0x07));
printk(" 0x17: right MIC (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x17));
printk(" 0x08: playback format = 0x%02x ", snd_cs4231_in(chip, 0x08));
printk(" 0x18: IRQ status = 0x%02x\n", snd_cs4231_in(chip, 0x18));
printk(" 0x09: iface (CFIG 1) = 0x%02x ", snd_cs4231_in(chip, 0x09));
printk(" 0x19: left line out = 0x%02x\n", snd_cs4231_in(chip, 0x19));
printk(" 0x0a: pin control = 0x%02x ", snd_cs4231_in(chip, 0x0a));
printk(" 0x1a: mono control = 0x%02x\n", snd_cs4231_in(chip, 0x1a));
printk(" 0x0b: init & status = 0x%02x ", snd_cs4231_in(chip, 0x0b));
printk(" 0x1b: right line out = 0x%02x\n", snd_cs4231_in(chip, 0x1b));
printk(" 0x0c: revision & mode = 0x%02x ", snd_cs4231_in(chip, 0x0c));
printk(" 0x1c: record format = 0x%02x\n", snd_cs4231_in(chip, 0x1c));
printk(" 0x0d: loopback = 0x%02x ", snd_cs4231_in(chip, 0x0d));
printk(" 0x1d: var freq (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x1d));
printk(" 0x0e: ply upr count = 0x%02x ", snd_cs4231_in(chip, 0x0e));
printk(" 0x1e: ply lwr count = 0x%02x\n", snd_cs4231_in(chip, 0x1e));
printk(" 0x0f: rec upr count = 0x%02x ", snd_cs4231_in(chip, 0x0f));
printk(" 0x1f: rec lwr count = 0x%02x\n", snd_cs4231_in(chip, 0x1f));
}
#endif
/*
* CS4231 detection / MCE routines
*/
static void snd_cs4231_busy_wait(cs4231_t *chip)
{
int timeout;
/* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */
for (timeout = 5; timeout > 0; timeout--)
__cs4231_readb(chip, CS4231P(chip, REGSEL));
/* end of cleanup sequence */
for (timeout = 250;
timeout > 0 && (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT);
timeout--)
udelay(10);
}
static void snd_cs4231_mce_up(cs4231_t *chip)
{
unsigned long flags;
int timeout;
spin_lock_irqsave(&chip->lock, flags);
for (timeout = 250; timeout > 0 && (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT); timeout--)
udelay(100);
#ifdef CONFIG_SND_DEBUG
if (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT)
snd_printk("mce_up - auto calibration time out (0)\n");
#endif
chip->mce_bit |= CS4231_MCE;
timeout = __cs4231_readb(chip, CS4231P(chip, REGSEL));
if (timeout == 0x80)
snd_printk("mce_up [0x%lx]: serious init problem - codec still busy\n", chip->port);
if (!(timeout & CS4231_MCE))
__cs4231_writeb(chip, chip->mce_bit | (timeout & 0x1f), CS4231P(chip, REGSEL));
spin_unlock_irqrestore(&chip->lock, flags);
}
static void snd_cs4231_mce_down(cs4231_t *chip)
{
unsigned long flags;
int timeout;
signed long time;
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_busy_wait(chip);
#if 0
printk("(1) timeout = %i\n", timeout);
#endif
#ifdef CONFIG_SND_DEBUG
if (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT)
snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", CS4231P(chip, REGSEL));
#endif
chip->mce_bit &= ~CS4231_MCE;
timeout = __cs4231_readb(chip, CS4231P(chip, REGSEL));
__cs4231_writeb(chip, chip->mce_bit | (timeout & 0x1f), CS4231P(chip, REGSEL));
if (timeout == 0x80)
snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port);
if ((timeout & CS4231_MCE) == 0) {
spin_unlock_irqrestore(&chip->lock, flags);
return;
}
snd_cs4231_busy_wait(chip);
/* calibration process */
for (timeout = 500; timeout > 0 && (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0; timeout--)
udelay(10);
if ((snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0) {
snd_printd("cs4231_mce_down - auto calibration time out (1)\n");
spin_unlock_irqrestore(&chip->lock, flags);
return;
}
#if 0
printk("(2) timeout = %i, jiffies = %li\n", timeout, jiffies);
#endif
time = HZ / 4;
while (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) {
spin_unlock_irqrestore(&chip->lock, flags);
if (time <= 0) {
snd_printk("mce_down - auto calibration time out (2)\n");
return;
}
set_current_state(TASK_INTERRUPTIBLE);
time = schedule_timeout(time);
spin_lock_irqsave(&chip->lock, flags);
}
#if 0
printk("(3) jiffies = %li\n", jiffies);
#endif
time = HZ / 10;
while (__cs4231_readb(chip, CS4231P(chip, REGSEL)) & CS4231_INIT) {
spin_unlock_irqrestore(&chip->lock, flags);
if (time <= 0) {
snd_printk("mce_down - auto calibration time out (3)\n");
return;
}
set_current_state(TASK_INTERRUPTIBLE);
time = schedule_timeout(time);
spin_lock_irqsave(&chip->lock, flags);
}
spin_unlock_irqrestore(&chip->lock, flags);
#if 0
printk("(4) jiffies = %li\n", jiffies);
snd_printk("mce_down - exit = 0x%x\n", __cs4231_readb(chip, CS4231P(chip, REGSEL)));
#endif
}
static unsigned int snd_cs4231_get_count(unsigned char format, unsigned int size)
{
switch (format & 0xe0) {
case CS4231_LINEAR_16:
case CS4231_LINEAR_16_BIG:
size >>= 1;
break;
case CS4231_ADPCM_16:
return size >> 2;
}
if (format & CS4231_STEREO)
size >>= 1;
return size;
}
static int snd_cs4231_trigger(snd_pcm_substream_t *substream, int cmd)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
int result = 0;
#if 0
printk("codec trigger!!! - what = %i, enable = %i, status = 0x%x\n", what, enable, __cs4231_readb(chip, CS4231P(card, STATUS)));
#endif
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_STOP:
{
unsigned int what = 0;
snd_pcm_substream_t *s = substream;
do {
if (s == chip->playback_substream) {
what |= CS4231_PLAYBACK_ENABLE;
snd_pcm_trigger_done(s, substream);
} else if (s == chip->capture_substream) {
what |= CS4231_RECORD_ENABLE;
snd_pcm_trigger_done(s, substream);
}
s = s->link_next;
} while (s != substream);
spin_lock(&chip->lock);
if (cmd == SNDRV_PCM_TRIGGER_START)
chip->image[CS4231_IFACE_CTRL] |= what;
else
chip->image[CS4231_IFACE_CTRL] &= ~what;
snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
spin_unlock(&chip->lock);
break;
}
default:
result = -EINVAL;
break;
}
#if 0
snd_cs4231_debug(chip);
#endif
return result;
}
/*
* CODEC I/O
*/
static unsigned char snd_cs4231_get_rate(unsigned int rate)
{
int i;
for (i = 0; i < 14; i++)
if (rate == rates[i])
return freq_bits[i];
// snd_BUG();
return freq_bits[13];
}
static unsigned char snd_cs4231_get_format(cs4231_t *chip, int format, int channels)
{
unsigned char rformat;
rformat = CS4231_LINEAR_8;
switch (format) {
case SNDRV_PCM_FORMAT_MU_LAW: rformat = CS4231_ULAW_8; break;
case SNDRV_PCM_FORMAT_A_LAW: rformat = CS4231_ALAW_8; break;
case SNDRV_PCM_FORMAT_S16_LE: rformat = CS4231_LINEAR_16; break;
case SNDRV_PCM_FORMAT_S16_BE: rformat = CS4231_LINEAR_16_BIG; break;
case SNDRV_PCM_FORMAT_IMA_ADPCM: rformat = CS4231_ADPCM_16; break;
}
if (channels > 1)
rformat |= CS4231_STEREO;
#if 0
snd_printk("get_format: 0x%x (mode=0x%x)\n", format, mode);
#endif
return rformat;
}
static void snd_cs4231_calibrate_mute(cs4231_t *chip, int mute)
{
unsigned long flags;
mute = mute ? 1 : 0;
spin_lock_irqsave(&chip->lock, flags);
if (chip->calibrate_mute == mute) {
spin_unlock_irqrestore(&chip->lock, flags);
return;
}
if (!mute) {
snd_cs4231_dout(chip, CS4231_LEFT_INPUT,
chip->image[CS4231_LEFT_INPUT]);
snd_cs4231_dout(chip, CS4231_RIGHT_INPUT,
chip->image[CS4231_RIGHT_INPUT]);
snd_cs4231_dout(chip, CS4231_LOOPBACK,
chip->image[CS4231_LOOPBACK]);
}
snd_cs4231_dout(chip, CS4231_AUX1_LEFT_INPUT,
mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]);
snd_cs4231_dout(chip, CS4231_AUX1_RIGHT_INPUT,
mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]);
snd_cs4231_dout(chip, CS4231_AUX2_LEFT_INPUT,
mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]);
snd_cs4231_dout(chip, CS4231_AUX2_RIGHT_INPUT,
mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]);
snd_cs4231_dout(chip, CS4231_LEFT_OUTPUT,
mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]);
snd_cs4231_dout(chip, CS4231_RIGHT_OUTPUT,
mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]);
snd_cs4231_dout(chip, CS4231_LEFT_LINE_IN,
mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]);
snd_cs4231_dout(chip, CS4231_RIGHT_LINE_IN,
mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]);
snd_cs4231_dout(chip, CS4231_MONO_CTRL,
mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
chip->calibrate_mute = mute;
spin_unlock_irqrestore(&chip->lock, flags);
}
static void snd_cs4231_playback_format(cs4231_t *chip, snd_pcm_hw_params_t *params,
unsigned char pdfr)
{
unsigned long flags;
down(&chip->mce_mutex);
snd_cs4231_calibrate_mute(chip, 1);
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) ?
(pdfr & 0xf0) | (chip->image[CS4231_REC_FORMAT] & 0x0f) :
pdfr);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
snd_cs4231_calibrate_mute(chip, 0);
up(&chip->mce_mutex);
}
static void snd_cs4231_capture_format(cs4231_t *chip, snd_pcm_hw_params_t *params,
unsigned char cdfr)
{
unsigned long flags;
down(&chip->mce_mutex);
snd_cs4231_calibrate_mute(chip, 1);
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
((chip->image[CS4231_PLAYBK_FORMAT]) & 0xf0) |
(cdfr & 0x0f));
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
}
snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
snd_cs4231_calibrate_mute(chip, 0);
up(&chip->mce_mutex);
}
/*
* Timer interface
*/
static unsigned long snd_cs4231_timer_resolution(snd_timer_t *timer)
{
cs4231_t *chip = snd_timer_chip(timer);
return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
}
static int snd_cs4231_timer_start(snd_timer_t *timer)
{
unsigned long flags;
unsigned int ticks;
cs4231_t *chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->lock, flags);
ticks = timer->sticks;
if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 ||
(unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] ||
(unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) {
snd_cs4231_out(chip, CS4231_TIMER_HIGH,
chip->image[CS4231_TIMER_HIGH] =
(unsigned char) (ticks >> 8));
snd_cs4231_out(chip, CS4231_TIMER_LOW,
chip->image[CS4231_TIMER_LOW] =
(unsigned char) ticks);
snd_cs4231_out(chip, CS4231_ALT_FEATURE_1,
chip->image[CS4231_ALT_FEATURE_1] | CS4231_TIMER_ENABLE);
}
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static int snd_cs4231_timer_stop(snd_timer_t *timer)
{
unsigned long flags;
cs4231_t *chip = snd_timer_chip(timer);
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_ALT_FEATURE_1,
chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE);
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static void snd_cs4231_init(cs4231_t *chip)
{
unsigned long flags;
snd_cs4231_mce_down(chip);
#ifdef SNDRV_DEBUG_MCE
snd_printk("init: (1)\n");
#endif
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
CS4231_RECORD_ENABLE | CS4231_RECORD_PIO |
CS4231_CALIB_MODE);
chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB;
snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
#ifdef SNDRV_DEBUG_MCE
snd_printk("init: (2)\n");
#endif
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
#ifdef SNDRV_DEBUG_MCE
snd_printk("init: (3) - afei = 0x%x\n", chip->image[CS4231_ALT_FEATURE_1]);
#endif
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_ALT_FEATURE_2, chip->image[CS4231_ALT_FEATURE_2]);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT]);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
#ifdef SNDRV_DEBUG_MCE
snd_printk("init: (4)\n");
#endif
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
#ifdef SNDRV_DEBUG_MCE
snd_printk("init: (5)\n");
#endif
}
static int snd_cs4231_open(cs4231_t *chip, unsigned int mode)
{
unsigned long flags;
down(&chip->open_mutex);
if ((chip->mode & mode)) {
up(&chip->open_mutex);
return -EAGAIN;
}
if (chip->mode & CS4231_MODE_OPEN) {
chip->mode |= mode;
up(&chip->open_mutex);
return 0;
}
/* ok. now enable and ack CODEC IRQ */
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
CS4231_RECORD_IRQ |
CS4231_TIMER_IRQ);
snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS)); /* clear IRQ */
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS)); /* clear IRQ */
chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
CS4231_RECORD_IRQ |
CS4231_TIMER_IRQ);
snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
spin_unlock_irqrestore(&chip->lock, flags);
chip->mode = mode;
up(&chip->open_mutex);
return 0;
}
static void snd_cs4231_close(cs4231_t *chip, unsigned int mode)
{
unsigned long flags;
down(&chip->open_mutex);
chip->mode &= ~mode;
if (chip->mode & CS4231_MODE_OPEN) {
up(&chip->open_mutex);
return;
}
snd_cs4231_calibrate_mute(chip, 1);
/* disable IRQ */
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS)); /* clear IRQ */
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS)); /* clear IRQ */
chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
/* now disable record & playback */
if (chip->image[CS4231_IFACE_CTRL] &
(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) {
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_up(chip);
spin_lock_irqsave(&chip->lock, flags);
chip->image[CS4231_IFACE_CTRL] &=
~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_down(chip);
spin_lock_irqsave(&chip->lock, flags);
}
/* clear IRQ again */
snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS)); /* clear IRQ */
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS)); /* clear IRQ */
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_calibrate_mute(chip, 0);
chip->mode = 0;
up(&chip->open_mutex);
}
/*
* timer open/close
*/
static int snd_cs4231_timer_open(snd_timer_t *timer)
{
cs4231_t *chip = snd_timer_chip(timer);
snd_cs4231_open(chip, CS4231_MODE_TIMER);
return 0;
}
static int snd_cs4231_timer_close(snd_timer_t * timer)
{
cs4231_t *chip = snd_timer_chip(timer);
snd_cs4231_close(chip, CS4231_MODE_TIMER);
return 0;
}
static struct _snd_timer_hardware snd_cs4231_timer_table =
{
.flags = SNDRV_TIMER_HW_AUTO,
.resolution = 9945,
.ticks = 65535,
.open = snd_cs4231_timer_open,
.close = snd_cs4231_timer_close,
.c_resolution = snd_cs4231_timer_resolution,
.start = snd_cs4231_timer_start,
.stop = snd_cs4231_timer_stop,
};
/*
* ok.. exported functions..
*/
static int snd_cs4231_playback_hw_params(snd_pcm_substream_t *substream,
snd_pcm_hw_params_t *hw_params)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
unsigned char new_pdfr;
int err;
if ((err = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params))) < 0)
return err;
new_pdfr = snd_cs4231_get_format(chip, params_format(hw_params),
params_channels(hw_params)) |
snd_cs4231_get_rate(params_rate(hw_params));
snd_cs4231_playback_format(chip, hw_params, new_pdfr);
return 0;
}
static int snd_cs4231_playback_hw_free(snd_pcm_substream_t *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static int snd_cs4231_playback_prepare(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned long flags;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned int count = snd_pcm_lib_period_bytes(substream);
spin_lock_irqsave(&chip->lock, flags);
chip->p_dma_size = size;
chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE |
CS4231_PLAYBACK_PIO);
#ifdef EBUS_SUPPORT
if (chip->flags & CS4231_FLAG_EBUS) {
writel(EBUS_DCSR_RESET, chip->eb2p + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16, chip->eb2p + EBDMA_CSR);
writel(size, chip->eb2p + EBDMA_COUNT);
writel(runtime->dma_addr, chip->eb2p + EBDMA_ADDR);
writel((EBUS_DCSR_BURST_SZ_16 |
EBUS_DCSR_EN_DMA |
EBUS_DCSR_INT_EN), chip->eb2p + EBDMA_CSR);
} else {
#endif
#ifdef SBUS_SUPPORT
sbus_writel(runtime->dma_addr, chip->port + APCPNVA);
sbus_writel(size, chip->port + APCPNC);
sbus_writel(((sbus_readl(chip->port + APCCSR)
& ~APC_PPAUSE)
| APC_PDMA_READY), chip->port + APCCSR);
#endif
#ifdef EBUS_SUPPORT
}
#endif
count = snd_cs4231_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1;
snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
spin_unlock_irqrestore(&chip->lock, flags);
#if 0
snd_cs4231_debug(chip);
#endif
return 0;
}
static int snd_cs4231_capture_hw_params(snd_pcm_substream_t *substream,
snd_pcm_hw_params_t *hw_params)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
unsigned char new_cdfr;
int err;
if ((err = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params))) < 0)
return err;
new_cdfr = snd_cs4231_get_format(chip, params_format(hw_params),
params_channels(hw_params)) |
snd_cs4231_get_rate(params_rate(hw_params));
snd_cs4231_capture_format(chip, hw_params, new_cdfr);
return 0;
}
static int snd_cs4231_capture_hw_free(snd_pcm_substream_t *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static int snd_cs4231_capture_prepare(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned long flags;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned int count = snd_pcm_lib_period_bytes(substream);
spin_lock_irqsave(&chip->lock, flags);
chip->c_dma_size = size;
chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
#ifdef EBUS_SUPPORT
if (chip->flags & CS4231_FLAG_EBUS) {
writel(EBUS_DCSR_RESET, chip->eb2c + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16, chip->eb2c + EBDMA_CSR);
writel(size, chip->eb2c + EBDMA_COUNT);
writel(runtime->dma_addr, chip->eb2c + EBDMA_ADDR);
writel((EBUS_DCSR_BURST_SZ_16 |
EBUS_DCSR_EN_DMA |
EBUS_DCSR_WRITE |
EBUS_DCSR_INT_EN), chip->eb2c + EBDMA_CSR);
} else {
#endif
#ifdef SBUS_SUPPORT
sbus_writel(runtime->dma_addr, chip->port + APCCNVA);
sbus_writel(size, chip->port + APCCNC);
sbus_writel(((sbus_readl(chip->port + APCCSR)
& ~APC_CPAUSE) |
APC_CDMA_READY), chip->port + APCCSR);
#endif
#ifdef EBUS_SUPPORT
}
#endif
count = snd_cs4231_get_count(chip->image[CS4231_REC_FORMAT], count) - 1;
snd_cs4231_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count);
snd_cs4231_out(chip, CS4231_REC_UPR_CNT, (unsigned char) (count >> 8));
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static void snd_cs4231_overrange(cs4231_t *chip)
{
unsigned long flags;
unsigned char res;
spin_lock_irqsave(&chip->lock, flags);
res = snd_cs4231_in(chip, CS4231_TEST_INIT);
spin_unlock_irqrestore(&chip->lock, flags);
if (res & (0x08 | 0x02)) /* detect overrange only above 0dB; may be user selectable? */
chip->capture_substream->runtime->overrange++;
}
static void snd_cs4231_generic_interrupt(cs4231_t *chip)
{
unsigned char status;
status = snd_cs4231_in(chip, CS4231_IRQ_STATUS);
if (status & CS4231_TIMER_IRQ) {
if (chip->timer)
snd_timer_interrupt(chip->timer, chip->timer->sticks);
}
if (status & CS4231_PLAYBACK_IRQ)
snd_pcm_period_elapsed(chip->playback_substream);
if (status & CS4231_RECORD_IRQ) {
snd_cs4231_overrange(chip);
snd_pcm_period_elapsed(chip->capture_substream);
}
/* ACK the CS4231 interrupt. */
spin_lock(&chip->lock);
snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0);
spin_unlock(&chip->lock);
}
#ifdef SBUS_SUPPORT
static void snd_cs4231_sbus_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
cs4231_t *chip = snd_magic_cast(cs4231_t, dev_id, return);
u32 csr;
csr = sbus_readl(chip->port + APCCSR);
if (!(csr & (APC_INT_PENDING |
APC_PLAY_INT |
APC_CAPT_INT |
APC_GENL_INT |
APC_XINT_PEMP |
APC_XINT_CEMP)))
return;
/* ACK the APC interrupt. */
sbus_writel(csr, chip->port + APCCSR);
snd_cs4231_generic_interrupt(chip);
}
#endif
#ifdef EBUS_SUPPORT
static void snd_cs4231_ebus_play_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
cs4231_t *chip = snd_magic_cast(cs4231_t, dev_id, return);
u32 csr;
csr = readl(chip->eb2p + EBDMA_CSR);
if (!(csr & EBUS_DCSR_INT_PEND))
return;
/* ACK the EBUS playback DMA interrupt. */
writel(csr, chip->eb2p + EBDMA_CSR);
snd_cs4231_generic_interrupt(chip);
}
static void snd_cs4231_ebus_capture_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
cs4231_t *chip = snd_magic_cast(cs4231_t, dev_id, return);
u32 csr;
csr = readl(chip->eb2c + EBDMA_CSR);
if (!(csr & EBUS_DCSR_INT_PEND))
return;
/* ACK the EBUS capture DMA interrupt. */
writel(csr, chip->eb2c + EBDMA_CSR);
snd_cs4231_generic_interrupt(chip);
}
#endif
static snd_pcm_uframes_t snd_cs4231_playback_pointer(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
size_t ptr, residue;
if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE))
return 0;
#ifdef EBUS_SUPPORT
if (chip->flags & CS4231_FLAG_EBUS) {
residue = readl(chip->eb2p + EBDMA_COUNT);
} else {
#endif
#ifdef SBUS_SUPPORT
residue = sbus_readl(chip->port + APCPC);
#endif
#ifdef EBUS_SUPPORT
}
#endif
ptr = chip->p_dma_size - residue;
return bytes_to_frames(substream->runtime, ptr);
}
static snd_pcm_uframes_t snd_cs4231_capture_pointer(snd_pcm_substream_t * substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
size_t ptr, residue;
if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE))
return 0;
#ifdef EBUS_SUPPORT
if (chip->flags & CS4231_FLAG_EBUS) {
residue = readl(chip->eb2c + EBDMA_COUNT);
} else {
#endif
#ifdef SBUS_SUPPORT
residue = sbus_readl(chip->port + APCCC);
#endif
#ifdef EBUS_SUPPORT
}
#endif
ptr = chip->c_dma_size - residue;
return bytes_to_frames(substream->runtime, ptr);
}
/*
*/
static int snd_cs4231_probe(cs4231_t *chip)
{
unsigned long flags;
int i, id;
unsigned char *ptr;
#if 0
snd_cs4231_debug(chip);
#endif
id = 0;
for (i = 0; i < 50; i++) {
mb();
if (inb(CS4231P(chip, REGSEL)) & CS4231_INIT)
udelay(2000);
else {
spin_lock_irqsave(&chip->lock, flags);
snd_cs4231_out(chip, CS4231_MISC_INFO, CS4231_MODE2);
id = snd_cs4231_in(chip, CS4231_MISC_INFO) & 0x0f;
spin_unlock_irqrestore(&chip->lock, flags);
if (id == 0x0a)
break; /* this is valid value */
}
}
snd_printdd("cs4231: port = 0x%lx, id = 0x%x\n", chip->port, id);
if (id != 0x0a)
return -ENODEV; /* no valid device found */
spin_lock_irqsave(&chip->lock, flags);
/* Reset DMA engine. */
#ifdef EBUS_SUPPORT
if (chip->flags & CS4231_FLAG_EBUS) {
writel(EBUS_DCSR_RESET, chip->eb2p + EBDMA_CSR);
writel(EBUS_DCSR_RESET, chip->eb2c + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16 |
EBUS_DCSR_INT_EN, chip->eb2p + EBDMA_CSR);
writel(EBUS_DCSR_BURST_SZ_16 |
EBUS_DCSR_INT_EN, chip->eb2c + EBDMA_CSR);
} else {
#endif
#ifdef SBUS_SUPPORT
sbus_writel(APC_CHIP_RESET, chip->port + APCCSR);
sbus_writel(0x00, chip->port + APCCSR);
sbus_writel(sbus_readl(chip->port + APCCSR) | APC_CDC_RESET,
chip->port + APCCSR);
udelay(20);
sbus_writel(sbus_readl(chip->port + APCCSR) & ~APC_CDC_RESET,
chip->port + APCCSR);
sbus_writel(sbus_readl(chip->port + APCCSR) | (APC_XINT_ENA |
APC_XINT_PENA |
APC_XINT_CENA),
chip->port + APCCSR);
#endif
#ifdef EBUS_SUPPORT
}
#endif
__cs4231_readb(chip, CS4231P(chip, STATUS)); /* clear any pendings IRQ */
__cs4231_writeb(chip, 0, CS4231P(chip, STATUS));
mb();
spin_unlock_irqrestore(&chip->lock, flags);
chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
chip->image[CS4231_IFACE_CTRL] =
chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA;
chip->image[CS4231_ALT_FEATURE_1] = 0x80;
chip->image[CS4231_ALT_FEATURE_2] = 0x01;
ptr = (unsigned char *) &chip->image;
snd_cs4231_mce_down(chip);
spin_lock_irqsave(&chip->lock, flags);
for (i = 0; i < 32; i++) /* ok.. fill all CS4231 registers */
snd_cs4231_out(chip, i, *ptr++);
spin_unlock_irqrestore(&chip->lock, flags);
snd_cs4231_mce_up(chip);
snd_cs4231_mce_down(chip);
mdelay(2);
return 0; /* all things are ok.. */
}
static snd_pcm_hardware_t snd_cs4231_playback =
{
info: (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START),
formats: (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
SNDRV_PCM_FMTBIT_IMA_ADPCM |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S16_BE),
rates: SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
rate_min: 5510,
rate_max: 48000,
channels_min: 1,
channels_max: 2,
buffer_bytes_max: (128*1024),
period_bytes_min: 64,
period_bytes_max: (128*1024),
periods_min: 1,
periods_max: 1024,
fifo_size: 0,
};
static snd_pcm_hardware_t snd_cs4231_capture =
{
info: (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START),
formats: (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
SNDRV_PCM_FMTBIT_IMA_ADPCM |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S16_BE),
rates: SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
rate_min: 5510,
rate_max: 48000,
channels_min: 1,
channels_max: 2,
buffer_bytes_max: (128*1024),
period_bytes_min: 64,
period_bytes_max: (128*1024),
periods_min: 1,
periods_max: 1024,
fifo_size: 0,
};
static int snd_cs4231_playback_open(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int err;
runtime->hw = snd_cs4231_playback;
if ((err = snd_cs4231_open(chip, CS4231_MODE_PLAY)) < 0) {
snd_free_pages(runtime->dma_area, runtime->dma_bytes);
return err;
}
chip->playback_substream = substream;
snd_pcm_set_sync(substream);
snd_cs4231_xrate(runtime);
return 0;
}
static int snd_cs4231_capture_open(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int err;
runtime->hw = snd_cs4231_capture;
if ((err = snd_cs4231_open(chip, CS4231_MODE_RECORD)) < 0) {
snd_free_pages(runtime->dma_area, runtime->dma_bytes);
return err;
}
chip->capture_substream = substream;
snd_pcm_set_sync(substream);
snd_cs4231_xrate(runtime);
return 0;
}
static int snd_cs4231_playback_close(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
chip->playback_substream = NULL;
snd_cs4231_close(chip, CS4231_MODE_PLAY);
return 0;
}
static int snd_cs4231_capture_close(snd_pcm_substream_t *substream)
{
cs4231_t *chip = snd_pcm_substream_chip(substream);
chip->capture_substream = NULL;
snd_cs4231_close(chip, CS4231_MODE_RECORD);
return 0;
}
/* XXX We can do some power-management, in particular on EBUS using
* XXX the audio AUXIO register...
*/
static snd_pcm_ops_t snd_cs4231_playback_ops = {
.open = snd_cs4231_playback_open,
.close = snd_cs4231_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_cs4231_playback_hw_params,
.hw_free = snd_cs4231_playback_hw_free,
.prepare = snd_cs4231_playback_prepare,
.trigger = snd_cs4231_trigger,
.pointer = snd_cs4231_playback_pointer,
};
static snd_pcm_ops_t snd_cs4231_capture_ops = {
.open = snd_cs4231_capture_open,
.close = snd_cs4231_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_cs4231_capture_hw_params,
.hw_free = snd_cs4231_capture_hw_free,
.prepare = snd_cs4231_capture_prepare,
.trigger = snd_cs4231_trigger,
.pointer = snd_cs4231_capture_pointer,
};
static void snd_cs4231_pcm_free(snd_pcm_t *pcm)
{
cs4231_t *chip = snd_magic_cast(cs4231_t, pcm->private_data, return);
chip->pcm = NULL;
snd_pcm_lib_preallocate_free_for_all(pcm);
}
int snd_cs4231_pcm(cs4231_t *chip)
{
snd_pcm_t *pcm;
int err;
if ((err = snd_pcm_new(chip->card, "CS4231", 0, 1, 1, &pcm)) < 0)
return err;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs4231_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs4231_capture_ops);
/* global setup */
pcm->private_data = chip;
pcm->private_free = snd_cs4231_pcm_free;
pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
strcpy(pcm->name, "CS4231");
#ifdef EBUS_SUPPORT
if (chip->flags & CS4231_FLAG_EBUS) {
snd_pcm_lib_preallocate_pci_pages_for_all(chip->dev_u.pdev, pcm,
64*1024, 128*1024);
} else {
#endif
#ifdef SBUS_SUPPORT
snd_pcm_lib_preallocate_sbus_pages_for_all(chip->dev_u.sdev, pcm,
64*1024, 128*1024);
#endif
#ifdef EBUS_SUPPORT
}
#endif
chip->pcm = pcm;
return 0;
}
static void snd_cs4231_timer_free(snd_timer_t *timer)
{
cs4231_t *chip = snd_magic_cast(cs4231_t, timer->private_data, return);
chip->timer = NULL;
}
int snd_cs4231_timer(cs4231_t *chip)
{
snd_timer_t *timer;
snd_timer_id_t tid;
int err;
/* Timer initialization */
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.card = chip->card->number;
tid.device = 0;
tid.subdevice = 0;
if ((err = snd_timer_new(chip->card, "CS4231", &tid, &timer)) < 0)
return err;
strcpy(timer->name, "CS4231");
timer->private_data = chip;
timer->private_free = snd_cs4231_timer_free;
timer->hw = snd_cs4231_timer_table;
chip->timer = timer;
return 0;
}
/*
* MIXER part
*/
static int snd_cs4231_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
static char *texts[4] = {
"Line", "CD", "Mic", "Mix"
};
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
snd_assert(chip->card != NULL, return -EINVAL);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 2;
uinfo->value.enumerated.items = 4;
if (uinfo->value.enumerated.item > 3)
uinfo->value.enumerated.item = 3;
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_cs4231_get_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
spin_lock_irqsave(&chip->lock, flags);
ucontrol->value.enumerated.item[0] =
(chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6;
ucontrol->value.enumerated.item[1] =
(chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6;
spin_unlock_irqrestore(&chip->lock, flags);
return 0;
}
static int snd_cs4231_put_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
unsigned short left, right;
int change;
if (ucontrol->value.enumerated.item[0] > 3 ||
ucontrol->value.enumerated.item[1] > 3)
return -EINVAL;
left = ucontrol->value.enumerated.item[0] << 6;
right = ucontrol->value.enumerated.item[1] << 6;
spin_lock_irqsave(&chip->lock, flags);
left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left;
right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right;
change = left != chip->image[CS4231_LEFT_INPUT] ||
right != chip->image[CS4231_RIGHT_INPUT];
snd_cs4231_out(chip, CS4231_LEFT_INPUT, left);
snd_cs4231_out(chip, CS4231_RIGHT_INPUT, right);
spin_unlock_irqrestore(&chip->lock, flags);
return change;
}
int snd_cs4231_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
int mask = (kcontrol->private_value >> 16) & 0xff;
uinfo->type = (mask == 1) ?
SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
int snd_cs4231_get_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0xff;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
spin_lock_irqsave(&chip->lock, flags);
ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
spin_unlock_irqrestore(&chip->lock, flags);
if (invert)
ucontrol->value.integer.value[0] =
(mask - ucontrol->value.integer.value[0]);
return 0;
}
int snd_cs4231_put_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 8) & 0xff;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
int change;
unsigned short val;
val = (ucontrol->value.integer.value[0] & mask);
if (invert)
val = mask - val;
val <<= shift;
spin_lock_irqsave(&chip->lock, flags);
val = (chip->image[reg] & ~(mask << shift)) | val;
change = val != chip->image[reg];
snd_cs4231_out(chip, reg, val);
spin_unlock_irqrestore(&chip->lock, flags);
return change;
}
int snd_cs4231_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
int mask = (kcontrol->private_value >> 24) & 0xff;
uinfo->type = mask == 1 ?
SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
int snd_cs4231_get_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int shift_left = (kcontrol->private_value >> 16) & 0x07;
int shift_right = (kcontrol->private_value >> 19) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int invert = (kcontrol->private_value >> 22) & 1;
spin_lock_irqsave(&chip->lock, flags);
ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
spin_unlock_irqrestore(&chip->lock, flags);
if (invert) {
ucontrol->value.integer.value[0] =
(mask - ucontrol->value.integer.value[0]);
ucontrol->value.integer.value[1] =
(mask - ucontrol->value.integer.value[1]);
}
return 0;
}
int snd_cs4231_put_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
cs4231_t *chip = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int shift_left = (kcontrol->private_value >> 16) & 0x07;
int shift_right = (kcontrol->private_value >> 19) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int invert = (kcontrol->private_value >> 22) & 1;
int change;
unsigned short val1, val2;
val1 = ucontrol->value.integer.value[0] & mask;
val2 = ucontrol->value.integer.value[1] & mask;
if (invert) {
val1 = mask - val1;
val2 = mask - val2;
}
val1 <<= shift_left;
val2 <<= shift_right;
spin_lock_irqsave(&chip->lock, flags);
val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
snd_cs4231_out(chip, left_reg, val1);
snd_cs4231_out(chip, right_reg, val2);
spin_unlock_irqrestore(&chip->lock, flags);
return change;
}
#define CS4231_CONTROLS (sizeof(snd_cs4231_controls)/sizeof(snd_kcontrol_new_t))
#define CS4231_SINGLE(xname, xindex, reg, shift, mask, invert) \
{ iface: SNDRV_CTL_ELEM_IFACE_MIXER, name: xname, index: xindex, \
info: snd_cs4231_info_single, \
get: snd_cs4231_get_single, put: snd_cs4231_put_single, \
private_value: reg | (shift << 8) | (mask << 16) | (invert << 24) }
#define CS4231_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
{ iface: SNDRV_CTL_ELEM_IFACE_MIXER, name: xname, index: xindex, \
info: snd_cs4231_info_double, \
get: snd_cs4231_get_double, put: snd_cs4231_put_double, \
private_value: left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
static snd_kcontrol_new_t snd_cs4231_controls[] = {
CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
CS4231_DOUBLE("Line Playback Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
CS4231_DOUBLE("Line Playback Volume", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1),
CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
CS4231_DOUBLE("Aux Playback Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
CS4231_DOUBLE("Aux Playback Volume", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
CS4231_SINGLE("Mono Playback Switch", 0, CS4231_MONO_CTRL, 7, 1, 1),
CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
CS4231_SINGLE("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, 6, 1, 1),
CS4231_SINGLE("Mono Output Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0),
CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, 15, 0),
{
iface: SNDRV_CTL_ELEM_IFACE_MIXER,
name: "Capture Source",
info: snd_cs4231_info_mux,
get: snd_cs4231_get_mux,
put: snd_cs4231_put_mux,
},
CS4231_DOUBLE("Mic Boost", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0),
CS4231_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
CS4231_SINGLE("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1),
/* SPARC specific uses of XCTL{0,1} general purpose outputs. */
CS4231_SINGLE("Line Out Switch", 0, CS4231_PIN_CTRL, 6, 1, 1),
CS4231_SINGLE("Headphone Out Switch", 0, CS4231_PIN_CTRL, 7, 1, 1)
};
int snd_cs4231_mixer(cs4231_t *chip)
{
snd_card_t *card;
int err, idx;
snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL);
card = chip->card;
strcpy(card->mixername, chip->pcm->name);
for (idx = 0; idx < CS4231_CONTROLS; idx++) {
if ((err = snd_ctl_add(card,
snd_ctl_new1(&snd_cs4231_controls[idx],
chip))) < 0)
return err;
}
return 0;
}
static int dev;
static int cs4231_attach_begin(snd_card_t **rcard)
{
snd_card_t *card;
*rcard = NULL;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!snd_enable[dev]) {
dev++;
return -ENOENT;
}
card = snd_card_new(snd_index[dev], snd_id[dev], THIS_MODULE, 0);
if (card == NULL)
return -ENOMEM;
strcpy(card->driver, "CS4231");
strcpy(card->shortname, "Sun CS4231");
*rcard = card;
return 0;
}
static int cs4231_attach_finish(snd_card_t *card, cs4231_t *chip)
{
int err;
if ((err = snd_cs4231_pcm(chip)) < 0)
goto out_err;
if ((err = snd_cs4231_mixer(chip)) < 0)
goto out_err;
if ((err = snd_cs4231_timer(chip)) < 0)
goto out_err;
if ((err = snd_card_register(card)) < 0)
goto out_err;
chip->next = cs4231_list;
cs4231_list = chip;
dev++;
return 0;
out_err:
snd_card_free(card);
return err;
}
#ifdef SBUS_SUPPORT
static int snd_cs4231_sbus_free(cs4231_t *chip)
{
if (chip->irq[0])
free_irq(chip->irq[0], chip);
if (chip->port)
sbus_iounmap(chip->port, chip->regs_size);
if (chip->timer)
snd_device_free(chip->card, chip->timer);
snd_magic_kfree(chip);
return 0;
}
static int snd_cs4231_sbus_dev_free(snd_device_t *device)
{
cs4231_t *cp = snd_magic_cast(cs4231_t, device->device_data, return -ENXIO);
return snd_cs4231_sbus_free(cp);
}
static snd_device_ops_t snd_cs4231_sbus_dev_ops = {
.dev_free = snd_cs4231_sbus_dev_free,
};
static int __init snd_cs4231_sbus_create(snd_card_t *card,
struct sbus_dev *sdev,
int dev,
cs4231_t **rchip)
{
cs4231_t *chip;
int err;
*rchip = NULL;
chip = snd_magic_kcalloc(cs4231_t, 0, GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
spin_lock_init(&chip->lock);
init_MUTEX(&chip->mce_mutex);
init_MUTEX(&chip->open_mutex);
chip->card = card;
chip->dev_u.sdev = sdev;
chip->regs_size = sdev->reg_addrs[0].reg_size;
memcpy(&chip->image, &snd_cs4231_original_image,
sizeof(snd_cs4231_original_image));
chip->port = sbus_ioremap(&sdev->resource[0], 0,
chip->regs_size, "cs4231");
if (!chip->port) {
snd_printk("cs4231-%d: Unable to map chip registers.\n", dev);
return -EIO;
}
if (request_irq(sdev->irqs[0], snd_cs4231_sbus_interrupt,
SA_SHIRQ, "cs4231", chip)) {
snd_cs4231_sbus_free(chip);
snd_printk("cs4231-%d: Unable to grab SBUS IRQ %s\n",
dev,
__irq_itoa(sdev->irqs[0]));
return -EBUSY;
}
chip->irq[0] = sdev->irqs[0];
if (snd_cs4231_probe(chip) < 0) {
snd_cs4231_sbus_free(chip);
return -ENODEV;
}
snd_cs4231_init(chip);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
chip, &snd_cs4231_sbus_dev_ops)) < 0) {
snd_cs4231_sbus_free(chip);
return err;
}
*rchip = chip;
return 0;
}
static int cs4231_sbus_attach(struct sbus_dev *sdev)
{
struct resource *rp = &sdev->resource[0];
cs4231_t *cp;
snd_card_t *card;
int err;
err = cs4231_attach_begin(&card);
if (err)
return err;
sprintf(card->longname, "%s at 0x%02lx:0x%08lx, irq %s",
card->shortname,
rp->flags & 0xffL,
rp->start,
__irq_itoa(sdev->irqs[0]));
if ((err = snd_cs4231_sbus_create(card, sdev, dev, &cp)) < 0) {
snd_card_free(card);
return err;
}
return cs4231_attach_finish(card, cp);
}
#endif
#ifdef EBUS_SUPPORT
static int snd_cs4231_ebus_free(cs4231_t *chip)
{
if (chip->irq[0])
free_irq(chip->irq[0], chip);
if (chip->irq[1])
free_irq(chip->irq[1], chip);
if (chip->port)
iounmap(chip->port);
if (chip->eb2p)
iounmap(chip->eb2p);
if (chip->eb2c)
iounmap(chip->eb2c);
if (chip->timer)
snd_device_free(chip->card, chip->timer);
snd_magic_kfree(chip);
return 0;
}
static int snd_cs4231_ebus_dev_free(snd_device_t *device)
{
cs4231_t *cp = snd_magic_cast(cs4231_t, device->device_data, return -ENXIO);
return snd_cs4231_ebus_free(cp);
}
static snd_device_ops_t snd_cs4231_ebus_dev_ops = {
.dev_free = snd_cs4231_ebus_dev_free,
};
static int __init snd_cs4231_ebus_create(snd_card_t *card,
struct linux_ebus_device *edev,
int dev,
cs4231_t **rchip)
{
cs4231_t *chip;
int err;
*rchip = NULL;
chip = snd_magic_kcalloc(cs4231_t, 0, GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
spin_lock_init(&chip->lock);
init_MUTEX(&chip->mce_mutex);
init_MUTEX(&chip->open_mutex);
chip->card = card;
chip->dev_u.pdev = edev->bus->self;
memcpy(&chip->image, &snd_cs4231_original_image,
sizeof(snd_cs4231_original_image));
chip->port = (unsigned long) ioremap(&edev->resource[0].start, 0x10);
chip->eb2p = (unsigned long) ioremap(&edev->resource[1].start, 0x10);
chip->eb2c = (unsigned long) ioremap(&edev->resource[2].start, 0x10);
if (!chip->port || !chip->eb2p || !chip->eb2c) {
snd_cs4231_ebus_free(chip);
snd_printk("cs4231-%d: Unable to map chip registers.\n", dev);
return -EIO;
}
if (request_irq(edev->irqs[0], snd_cs4231_ebus_capture_interrupt,
SA_SHIRQ, "cs4231(capture)", chip)) {
snd_cs4231_ebus_free(chip);
snd_printk("cs4231-%d: Unable to grab EBUS capture IRQ %s\n",
dev,
__irq_itoa(edev->irqs[0]));
return -EBUSY;
}
chip->irq[0] = edev->irqs[0];
if (request_irq(edev->irqs[1], snd_cs4231_ebus_play_interrupt,
SA_SHIRQ, "cs4231(play)", chip)) {
snd_cs4231_ebus_free(chip);
snd_printk("cs4231-%d: Unable to grab EBUS play IRQ %s\n",
dev,
__irq_itoa(edev->irqs[0]));
return -EBUSY;
}
chip->irq[1] = edev->irqs[1];
if (snd_cs4231_probe(chip) < 0) {
snd_cs4231_ebus_free(chip);
return -ENODEV;
}
snd_cs4231_init(chip);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
chip, &snd_cs4231_ebus_dev_ops)) < 0) {
snd_cs4231_ebus_free(chip);
return err;
}
*rchip = chip;
return 0;
}
static int cs4231_ebus_attach(struct linux_ebus_device *edev)
{
snd_card_t *card;
cs4231_t *chip;
int err;
err = cs4231_attach_begin(&card);
if (err)
return err;
sprintf(card->longname, "%s at 0x%lx, irq %s",
card->shortname,
edev->resource[0].start,
__irq_itoa(edev->irqs[0]));
if ((err = snd_cs4231_ebus_create(card, edev, dev, &chip)) < 0) {
snd_card_free(card);
return err;
}
return cs4231_attach_finish(card, chip);
}
#endif
static int __init cs4231_init(void)
{
#ifdef SBUS_SUPPORT
struct sbus_bus *sbus;
struct sbus_dev *sdev;
#endif
#ifdef EBUS_SUPPORT
struct linux_ebus *ebus;
struct linux_ebus_device *edev;
#endif
int found;
found = 0;
#ifdef SBUS_SUPPORT
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "SUNW,CS4231")) {
if (cs4231_sbus_attach(sdev) == 0)
found++;
}
}
#endif
#ifdef EBUS_SUPPORT
for_each_ebus(ebus) {
for_each_ebusdev(edev, ebus) {
int match = 0;
if (!strcmp(edev->prom_name, "SUNW,CS4231")) {
match = 1;
} else {
char compat[16];
prom_getstring(edev->prom_node, "compatible",
compat, sizeof(compat));
compat[15] = '\0';
if (!strcmp(compat, "SUNW,CS4231"))
match = 1;
}
if (match &&
cs4231_ebus_attach(edev) == 0)
found++;
}
}
#endif
return (found > 0) ? 0 : -EIO;
}
static void __exit cs4231_exit(void)
{
cs4231_t *p = cs4231_list;
while (p != NULL) {
cs4231_t *next = p->next;
snd_card_free(p->card);
p = next;
}
cs4231_list = NULL;
}
module_init(cs4231_init);
module_exit(cs4231_exit);
#ifndef MODULE
/* format is: snd-sun-cs4231=snd_index,snd_id,snd_enable */
static int __init alsa_card_sun_cs4231_setup(char *str)
{
static unsigned __initdata nr_dev = 0;
if (nr_dev >= SNDRV_CARDS)
return 0;
(void)(get_option(&str,&snd_index[nr_dev]) == 2 &&
get_option(&str,&snd_id[nr_dev]) == 2 &&
get_id(&str,&snd_enable[nr_dev]) == 2);
nr_dev++;
return 1;
}
__setup("snd-sun-cs4231=", alsa_card_sun_cs4231_setup);
#endif /* ifndef MODULE */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment