diff options
Diffstat (limited to 'drivers/sbus/audio')
-rw-r--r-- | drivers/sbus/audio/Makefile | 20 | ||||
-rw-r--r-- | drivers/sbus/audio/amd7930.c | 1160 | ||||
-rw-r--r-- | drivers/sbus/audio/amd7930.h | 129 | ||||
-rw-r--r-- | drivers/sbus/audio/audio.c | 585 | ||||
-rw-r--r-- | drivers/sbus/audio/audio.h | 119 | ||||
-rw-r--r-- | drivers/sbus/audio/cs4215.h | 120 | ||||
-rw-r--r-- | drivers/sbus/audio/cs4231.c | 107 | ||||
-rw-r--r-- | drivers/sbus/audio/dbri.c | 715 | ||||
-rw-r--r-- | drivers/sbus/audio/dbri.h | 293 |
9 files changed, 3037 insertions, 211 deletions
diff --git a/drivers/sbus/audio/Makefile b/drivers/sbus/audio/Makefile index f870e0da5..3dcb06f82 100644 --- a/drivers/sbus/audio/Makefile +++ b/drivers/sbus/audio/Makefile @@ -16,37 +16,37 @@ O_OBJS := M_OBJS := ifeq ($(CONFIG_SPARCAUDIO),y) -M=y +SBUS_AUDIO=y else ifeq ($(CONFIG_SPARCAUDIO),m) - MM=y + SBUS_AUDIO_MODULE=y endif endif ifeq ($(CONFIG_SPARCAUDIO_AMD7930),y) -M=y -O_OBJS += amd7930.o +SBUS_AUDIO=y +OX_OBJS += amd7930.o else ifeq ($(CONFIG_SPARCAUDIO_AMD7930),m) - MM=y - M_OBJS += amd7930.o + SBUS_AUDIO_MODULE=y + MX_OBJS += amd7930.o endif endif ifeq ($(CONFIG_SPARCAUDIO_CS4231),y) -M=y +SBUS_AUDIO=y O_OBJS += cs4231.o else ifeq ($(CONFIG_SPARCAUDIO_CS4231),m) - MM=y + SBUS_AUDIO_MODULE=y M_OBJS += cs4231.o endif endif -ifdef M +ifdef SBUS_AUDIO OX_OBJS += audio.o else - ifdef MM + ifdef SBUS_AUDIO_MODULE MX_OBJS += audio.o endif endif diff --git a/drivers/sbus/audio/amd7930.c b/drivers/sbus/audio/amd7930.c index 1134eb74e..6e54cbf79 100644 --- a/drivers/sbus/audio/amd7930.c +++ b/drivers/sbus/audio/amd7930.c @@ -1,15 +1,20 @@ /* * drivers/sbus/audio/amd7930.c * - * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu) * * This is the lowlevel driver for the AMD7930 audio chip found on all * sun4c machines and some sun4m machines. * - * XXX Add note about the fun of getting the docs. + * The amd7930 is actually an ISDN chip which has a very simple + * integrated audio encoder/decoder. When Sun decided on what chip to + * use for audio, they had the brilliant idea of using the amd7930 and + * only connecting the audio encoder/decoder pins. + * + * Thanks to the AMD engineer who was able to get us the AMD79C30 + * databook which has all the programming information and gain tables. */ -#include <linux/config.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> @@ -29,26 +34,78 @@ #define MAX_DRIVERS 1 -/* Private information we store for each amd7930 chip. */ -struct amd7930_info { - /* Current buffer that the driver is playing. */ +static struct sparcaudio_driver drivers[MAX_DRIVERS]; +static int num_drivers; + +/* Each amd7930 chip has two bi-directional B channels and a D + * channel available to the uproc. This structure handles all + * the buffering needed to transmit and receive via a single channel. + */ + +#define CHANNEL_AVAILABLE 0x00 +#define CHANNEL_INUSE_AUDIO_IN 0x01 +#define CHANNEL_INUSE_AUDIO_OUT 0x02 +#define CHANNEL_INUSE_ISDN_B1 0x04 +#define CHANNEL_INUSE_ISDN_B2 0x08 +#define CHANNEL_INUSE 0xff + +struct amd7930_channel { + /* Channel status */ + unsigned char channel_status; + + /* Current buffer that the driver is playing on channel */ volatile __u8 * output_ptr; volatile unsigned long output_count; + unsigned char xmit_idle_char; - /* Current record buffer. */ + /* Callback routine (and argument) when output is done on */ + void (*output_callback)(); + void * output_callback_arg; + + /* Current buffer that the driver is recording on channel */ volatile __u8 * input_ptr; volatile unsigned long input_count; + volatile unsigned long input_limit; + + /* Callback routine (and argument) when input is done on */ + void (*input_callback)(); + void * input_callback_arg; +}; + +/* Private information we store for each amd7930 chip. */ +struct amd7930_info { + struct amd7930_channel D; + struct amd7930_channel Bb; + struct amd7930_channel Bc; + + /* Pointers to which B channels are being used for what + * These three fields (Baudio, Bisdn[0], and Bisdn[1]) will either + * be NULL or point to one of the Bb/Bc structures above. + */ + struct amd7930_channel *Baudio; + struct amd7930_channel *Bisdn[2]; /* Device registers information. */ struct amd7930 *regs; unsigned long regs_size; struct amd7930_map map; + /* Volume information. */ + int pgain, rgain, mgain; + /* Device interrupt information. */ int irq; volatile int ints_on; + + + /* Someone to signal when the ISDN LIU state changes */ + int liu_state; + void (*liu_callback)(void *); + void *liu_callback_arg; }; + + /* Output a 16-bit quantity in the order that the amd7930 expects. */ #define amd7930_out16(regs,v) ({ regs->dr = v & 0xFF; regs->dr = (v >> 8) & 0xFF; }) @@ -120,9 +177,8 @@ static __const__ __u16 ger_coeff[] = { #define NR_GER_COEFFS (sizeof(ger_coeff) / sizeof(ger_coeff[0])) /* Enable amd7930 interrupts atomically. */ -static __inline__ void amd7930_enable_ints(struct sparcaudio_driver *drv) +static __inline__ void amd7930_enable_ints(struct amd7930_info *info) { - struct amd7930_info *info = (struct amd7930_info *)drv->private; register unsigned long flags; if (info->ints_on) @@ -137,9 +193,8 @@ static __inline__ void amd7930_enable_ints(struct sparcaudio_driver *drv) } /* Disable amd7930 interrupts atomically. */ -static __inline__ void amd7930_disable_ints(struct sparcaudio_driver *drv) +static __inline__ void amd7930_disable_ints(struct amd7930_info *info) { - struct amd7930_info *info = (struct amd7930_info *)drv->private; register unsigned long flags; if (!info->ints_on) @@ -153,8 +208,24 @@ static __inline__ void amd7930_disable_ints(struct sparcaudio_driver *drv) info->ints_on = 0; } +/* Idle amd7930 (no interrupts, no audio, no data) */ +static __inline__ void amd7930_idle(struct amd7930_info *info) +{ + register unsigned long flags; + + if (!info->ints_on) + return; + + save_and_cli(flags); + info->regs->cr = AMR_INIT; + info->regs->dr = 0; + restore_flags(flags); + + info->ints_on = 0; +} + /* Commit the local copy of the MAP registers to the amd7930. */ -static void amd7930_commit_map(struct sparcaudio_driver *drv) +static void amd7930_write_map(struct sparcaudio_driver *drv) { struct amd7930_info *info = (struct amd7930_info *)drv->private; struct amd7930 *regs = info->regs; @@ -184,71 +255,257 @@ static void amd7930_commit_map(struct sparcaudio_driver *drv) restore_flags(flags); } -/* Interrupt handler (The chip takes only one byte per interrupt. Grrr!) */ -static void amd7930_interrupt(int irq, void *dev_id, struct pt_regs *intr_regs) +/* Update the MAP registers with new settings. */ +static void amd7930_update_map(struct sparcaudio_driver *drv) { - struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id; struct amd7930_info *info = (struct amd7930_info *)drv->private; - struct amd7930 *regs = info->regs; + struct amd7930_map *map = &info->map; + int level; + + map->gx = gx_coeff[info->rgain]; + map->stgr = gx_coeff[info->mgain]; + + level = (info->pgain * (256 + NR_GER_COEFFS)) >> 8; + if (level >= 256) { + map->ger = ger_coeff[level - 256]; + map->gr = gx_coeff[255]; + } else { + map->ger = ger_coeff[0]; + map->gr = gx_coeff[level]; + } + + /* force output to speaker for now */ + map->mmr2 |= AM_MAP_MMR2_LS; + + /* input from external microphone */ + map->mmr2 |= AM_MAP_MMR2_AINB; + + amd7930_write_map(drv); +} + +/* Bit of a hack here - if the HISAX ISDN driver has got INTSTAT debugging + * turned on, we send debugging characters to the ISDN driver: + * + * i# - Interrupt received - number from 0 to 7 is low three bits of IR + * > - Loaded a single char into the Dchan xmit FIFO + * + - Finished loading an xmit packet into the Dchan xmit FIFO + * < - Read a single char from the Dchan recv FIFO + * ! - Finished reading a packet from the Dchan recv FIFO + * + * This code needs to be removed if anything other than HISAX uses the ISDN + * driver, since D.output_callback_arg is assumed to be a certain struct ptr + */ + +#include "../../isdn/hisax/hisax.h" +#include "../../isdn/hisax/isdnl1.h" + +#ifdef L2FRAME_DEBUG + +inline void debug_info(struct amd7930_info *info, char c) { + struct IsdnCardState *cs; + + if (!info || !info->D.output_callback_arg) return; + + cs = (struct IsdnCardState *)info->D.output_callback_arg; + + if (!cs || !cs->status_write) return; + + if (cs->debug & L1_DEB_INTSTAT) { + *(cs->status_write++) = c; + if (cs->status_write > cs->status_end) + cs->status_write = cs->status_buf; + } +} + +#else + +#define debug_info(info,c) + +#endif + + +static void fill_D_xmit_fifo(struct amd7930_info *info) +{ + /* Send next byte(s) of outgoing data. */ + while (info->D.output_ptr && info->D.output_count > 0 && + (info->regs->dsr2 & AMR_DSR2_TBE)) { + + /* Send the next byte and advance buffer pointer. */ + info->regs->dctb = *(info->D.output_ptr); + info->D.output_ptr++; + info->D.output_count--; + + debug_info(info, '>'); + } +} + +static void transceive_Dchannel(struct amd7930_info *info) +{ __u8 dummy; + int lbrp=0; /* Last Byte of Received Packet (LBRP) */ + +#define D_XMIT_ERRORS (AMR_DER_COLLISION | AMR_DER_UNRN) +#define D_RECV_ERRORS (AMR_DER_RABRT | AMR_DER_RFRAME | AMR_DER_FCS | \ + AMR_DER_OVFL | AMR_DER_UNFL | AMR_DER_OVRN) + + /* Transmit if we can */ + fill_D_xmit_fifo(info); + + /* Done with the xmit buffer? Notify the midlevel driver. */ + if (info->D.output_ptr != NULL && info->D.output_count == 0) { + info->D.output_ptr = NULL; + info->D.output_count = 0; + debug_info(info, '+'); + if (info->D.output_callback) + (*info->D.output_callback) + (info->D.output_callback_arg, + info->regs->der); + /* info->regs->der & D_XMIT_ERRORS); */ + } - /* Clear the interrupt. */ - dummy = regs->ir; + /* Read the next byte(s) of incoming data. */ + + while (info->regs->dsr2 & AMR_DSR2_RBA) { + + if (info->D.input_ptr && + (info->D.input_count < info->D.input_limit)) { + + /* Get the next byte and advance buffer pointer. */ + + *(info->D.input_ptr) = info->regs->dcrb; + info->D.input_ptr++; + info->D.input_count++; + + } else { + /* Overflow - should be detected by chip via RBLR + * so we'll just consume data until we see LBRP + */ + + dummy = info->regs->dcrb; + + } + + debug_info(info, '<'); + + if (info->regs->dsr2 & AMR_DSR2_LBRP) { + + /* End of recv packet? Notify the midlevel driver. */ + + __u8 der; + + debug_info(info, '!'); + + info->D.input_ptr = NULL; + + der = info->regs->der & D_RECV_ERRORS; + + /* Read receive byte count - advances FIFOs */ + info->regs->cr = AMR_DLC_DRCR; + dummy = info->regs->dr; + dummy = info->regs->dr; + + if (info->D.input_callback) + (*info->D.input_callback) + (info->D.input_callback_arg, der, + info->D.input_count); + } + + } +} + +long amd7930_xmit_idles=0; + +static void transceive_Bchannel(struct amd7930_channel *channel, + __volatile__ __u8 *io_reg) +{ /* Send the next byte of outgoing data. */ - if (info->output_ptr && info->output_count > 0) { + if (channel->output_ptr && channel->output_count > 0) { + /* Send the next byte and advance buffer pointer. */ - regs->bbtb = *(info->output_ptr); - info->output_ptr++; - info->output_count--; + *io_reg = *(channel->output_ptr); + channel->output_ptr++; + channel->output_count--; /* Done with the buffer? Notify the midlevel driver. */ - if (info->output_count == 0) { - info->output_ptr = NULL; - info->output_count = 0; - sparcaudio_output_done(drv); + if (channel->output_count == 0) { + channel->output_ptr = NULL; + channel->output_count = 0; + if (channel->output_callback) + (*channel->output_callback) + (channel->output_callback_arg); } - } + } else { + *io_reg = channel->xmit_idle_char; + amd7930_xmit_idles++; + } /* Read the next byte of incoming data. */ - if (info->input_ptr && info->input_count > 0) { + if (channel->input_ptr && channel->input_count > 0) { + /* Get the next byte and advance buffer pointer. */ - *(info->input_ptr) = regs->bbrb; - info->input_ptr++; - info->input_count--; + *(channel->input_ptr) = *io_reg; + channel->input_ptr++; + channel->input_count--; /* Done with the buffer? Notify the midlevel driver. */ - if (info->input_count == 0) { - info->input_ptr = NULL; - info->input_count = 0; - sparcaudio_input_done(drv); + if (channel->input_count == 0) { + channel->input_ptr = NULL; + channel->input_count = 0; + if (channel->input_callback) + (*channel->input_callback) + (channel->input_callback_arg); } } } - -static int amd7930_open(struct inode * inode, struct file * file, - struct sparcaudio_driver *drv) +/* Interrupt handler (The chip takes only one byte per interrupt. Grrr!) */ +static void amd7930_interrupt(int irq, void *dev_id, struct pt_regs *intr_regs) { + struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id; struct amd7930_info *info = (struct amd7930_info *)drv->private; - int level; + struct amd7930 *regs = info->regs; + __u8 ir; + __u8 lsr; - /* Set the default audio parameters. */ - info->map.gx = gx_coeff[128]; - info->map.stgr = gx_coeff[0]; + /* Clear the interrupt. */ + ir = regs->ir; - level = (128 * (256 + NR_GER_COEFFS)) >> 8; - if (level >= 256) { - info->map.ger = ger_coeff[level-256]; - info->map.gr = gx_coeff[255]; - } else { - info->map.ger = ger_coeff[0]; - info->map.gr = gx_coeff[level]; + if (ir & AMR_IR_BBUF) { + if (info->Bb.channel_status == CHANNEL_INUSE) + transceive_Bchannel(&info->Bb, &info->regs->bbtb); + if (info->Bc.channel_status == CHANNEL_INUSE) + transceive_Bchannel(&info->Bc, &info->regs->bctb); + } + + if (ir & (AMR_IR_DRTHRSH | AMR_IR_DTTHRSH | AMR_IR_DSRI)) { + debug_info(info, 'i'); + debug_info(info, '0' + (ir&7)); + transceive_Dchannel(info); } - info->map.mmr2 |= AM_MAP_MMR2_LS; + if (ir & AMR_IR_LSRI) { + regs->cr = AMR_LIU_LSR; + lsr = regs->dr; - amd7930_commit_map(drv); + 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; + + /* Set the default audio parameters. */ + info->rgain = 128; + info->pgain = 200; + info->mgain = 0; + amd7930_update_map(drv); MOD_INC_USE_COUNT; @@ -258,29 +515,87 @@ static int amd7930_open(struct inode * inode, struct file * file, static void amd7930_release(struct inode * inode, struct file * file, struct sparcaudio_driver *drv) { - amd7930_disable_ints(drv); + /* amd7930_disable_ints(drv->private); */ MOD_DEC_USE_COUNT; } +static void request_Baudio(struct amd7930_info *info) +{ + if (info->Bb.channel_status == CHANNEL_AVAILABLE) { + + info->Bb.channel_status = CHANNEL_INUSE; + info->Baudio = &info->Bb; + + /* Multiplexor map - audio (Ba) to Bb */ + info->regs->cr = AMR_MUX_MCR1; + info->regs->dr = AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bb << 4); + + /* Enable B channel interrupts */ + info->regs->cr = AMR_MUX_MCR4; + info->regs->dr = AM_MUX_MCR4_ENABLE_INTS; + + } else if (info->Bc.channel_status == CHANNEL_AVAILABLE) { + + info->Bc.channel_status = CHANNEL_INUSE; + info->Baudio = &info->Bc; + + /* Multiplexor map - audio (Ba) to Bc */ + info->regs->cr = AMR_MUX_MCR1; + info->regs->dr = AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bc << 4); + + /* Enable B channel interrupts */ + info->regs->cr = AMR_MUX_MCR4; + info->regs->dr = AM_MUX_MCR4_ENABLE_INTS; + + } +} + +static void release_Baudio(struct amd7930_info *info) +{ + if (info->Baudio) { + info->Baudio->channel_status = CHANNEL_AVAILABLE; + info->regs->cr = AMR_MUX_MCR1; + info->regs->dr = 0; + info->Baudio = NULL; + + if (info->Bb.channel_status == CHANNEL_AVAILABLE && + info->Bc.channel_status == CHANNEL_AVAILABLE) { + + /* Disable B channel interrupts */ + info->regs->cr = AMR_MUX_MCR4; + info->regs->dr = 0; + } + } +} + static void amd7930_start_output(struct sparcaudio_driver *drv, __u8 * buffer, unsigned long count) { struct amd7930_info *info = (struct amd7930_info *)drv->private; - info->output_ptr = buffer; - info->output_count = count; - amd7930_enable_ints(drv); + if (! info->Baudio) { + request_Baudio(info); + } + + if (info->Baudio) { + info->Baudio->output_ptr = buffer; + info->Baudio->output_count = count; + info->Baudio->output_callback = (void *) &sparcaudio_output_done; + info->Baudio->output_callback_arg = (void *) drv; + info->Baudio->xmit_idle_char = 0; + } } static void amd7930_stop_output(struct sparcaudio_driver *drv) { struct amd7930_info *info = (struct amd7930_info *)drv->private; - info->output_ptr = NULL; - info->output_count = 0; - - if (!info->input_ptr) - amd7930_disable_ints(drv); + if (info->Baudio) { + info->Baudio->output_ptr = NULL; + info->Baudio->output_count = 0; + if (! info->Baudio->input_ptr) + release_Baudio(info); + } } static void amd7930_start_input(struct sparcaudio_driver *drv, @@ -288,20 +603,29 @@ static void amd7930_start_input(struct sparcaudio_driver *drv, { struct amd7930_info *info = (struct amd7930_info *)drv->private; - info->input_ptr = buffer; - info->input_count = count; - amd7930_enable_ints(drv); + if (! info->Baudio) { + request_Baudio(info); + } + + if (info->Baudio) { + info->Baudio->input_ptr = buffer; + info->Baudio->input_count = count; + info->Baudio->input_callback = (void *) &sparcaudio_input_done; + info->Baudio->input_callback_arg = (void *) drv; + } } static void amd7930_stop_input(struct sparcaudio_driver *drv) { struct amd7930_info *info = (struct amd7930_info *)drv->private; - info->input_ptr = NULL; - info->input_count = 0; + if (info->Baudio) { + info->Baudio->input_ptr = NULL; + info->Baudio->input_count = 0; + if (! info->Baudio->output_ptr) + release_Baudio(info); + } - if (!info->output_ptr) - amd7930_disable_ints(drv); } static void amd7930_sunaudio_getdev(struct sparcaudio_driver *drv, @@ -312,23 +636,686 @@ static void amd7930_sunaudio_getdev(struct sparcaudio_driver *drv, strncpy(audinfo->config, "audio", sizeof(audinfo->config) - 1); } +static int amd7930_sunaudio_getdev_sunos(struct sparcaudio_driver *drv) +{ + return AUDIO_DEV_AMD; +} + +static int amd7930_set_output_volume(struct sparcaudio_driver *drv, int vol) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + + info->pgain = vol; + amd7930_update_map(drv); + return 0; +} + +static int amd7930_get_output_volume(struct sparcaudio_driver *drv) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + + return info->pgain; +} + +static int amd7930_set_input_volume(struct sparcaudio_driver *drv, int vol) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + + info->rgain = vol; + amd7930_update_map(drv); + return 0; +} + +static int amd7930_get_input_volume(struct sparcaudio_driver *drv) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + + return info->rgain; +} + +static int amd7930_set_monitor_volume(struct sparcaudio_driver *drv, int vol) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + + info->mgain = vol; + amd7930_update_map(drv); + return 0; +} + +/* Cheats. The amd has the minimum capabilities we support */ +static int amd7930_get_output_balance(struct sparcaudio_driver *drv) +{ + return AUDIO_MID_BALANCE; +} + +static int amd7930_get_input_balance(struct sparcaudio_driver *drv) +{ + return AUDIO_MID_BALANCE; +} + +static int amd7930_get_output_channels(struct sparcaudio_driver *drv) +{ + return AUDIO_MIN_PLAY_CHANNELS; +} + +static int amd7930_get_input_channels(struct sparcaudio_driver *drv) +{ + return AUDIO_MIN_REC_CHANNELS; +} + +static int amd7930_get_output_precision(struct sparcaudio_driver *drv) +{ + return AUDIO_MIN_PLAY_PRECISION; +} + +static int amd7930_get_input_precision(struct sparcaudio_driver *drv) +{ + return AUDIO_MIN_REC_PRECISION; +} + +/* This should eventually be made to DTRT, whatever that ends up */ +static int amd7930_get_output_port(struct sparcaudio_driver *drv) +{ + return AUDIO_SPEAKER; /* some of these have only HEADPHONE */ +} + +/* Only a microphone here, so no troubles */ +static int amd7930_get_input_port(struct sparcaudio_driver *drv) +{ + return AUDIO_MICROPHONE; +} + +/* This chip also supports AUDIO_ENCODING_ALAW, add support later */ +static int amd7930_get_output_encoding(struct sparcaudio_driver *drv) +{ + return AUDIO_ENCODING_ULAW; +} + +static int amd7930_get_input_encoding(struct sparcaudio_driver *drv) +{ + return AUDIO_ENCODING_ULAW; +} + +/* This is what you get. Take it or leave it */ +static int amd7930_get_output_rate(struct sparcaudio_driver *drv) +{ + return AMD7930_RATE; +} + +static int amd7930_get_input_rate(struct sparcaudio_driver *drv) +{ + return AMD7930_RATE; +} + +static int amd7930_get_output_muted(struct sparcaudio_driver *drv) +{ + return 0; +} + +static int amd7930_get_output_ports(struct sparcaudio_driver *drv) +{ + return AUDIO_SPEAKER | AUDIO_HEADPHONE; +} + +static int amd7930_get_input_ports(struct sparcaudio_driver *drv) +{ + return AUDIO_MICROPHONE; +} + +static int amd7930_get_monitor_volume(struct sparcaudio_driver *drv) +{ + struct amd7930_info *info = (struct amd7930_info *)drv->private; + + return info->mgain; +} + /* - * Device detection and initialization. + * ISDN operations + * + * Many of these routines take an "int dev" argument, which is simply + * an index into the drivers[] array. Currently, we only support a + * single AMD 7930 chip, so the value should always be 0. B channel + * operations require an "int chan", which should be 0 for channel B1 + * and 1 for channel B2 + * + * int amd7930_get_irqnum(int dev) + * + * returns the interrupt number being used by the chip. ISDN4linux + * uses this number to watch the interrupt during initialization and + * make sure something is happening. + * + * int amd7930_get_liu_state(int dev) + * + * returns the current state of the ISDN Line Interface Unit (LIU) + * as a number between 2 (state F2) and 7 (state F7). 0 may also be + * returned if the chip doesn't exist or the LIU hasn't been + * activated. The meanings of the states are defined in I.430, ISDN + * BRI Physical Layer Interface. The most important two states are + * F3 (shutdown) and F7 (syncronized). + * + * void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg) + * + * initializes the LIU and optionally registers a callback to be + * signaled upon a change of LIU state. The callback will be called + * with a single opaque callback_arg Once the callback has been + * triggered, amd7930_get_liu_state can be used to determine the LIU + * current state. + * + * void amd7930_liu_activate(int dev, int priority) + * + * requests LIU activation at a given D-channel priority. + * Successful activatation is achieved upon entering state F7, which + * will trigger any callback previously registered with + * amd7930_liu_init. + * + * void amd7930_liu_deactivate(int dev) + * + * deactivates LIU. Outstanding D and B channel transactions are + * terminated rudely and without callback notification. LIU change + * of state callback will be triggered, however. + * + * void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count, + * void (*callback)(void *, int), void *callback_arg) + * + * transmits a packet - specified with buffer, count - over the D-channel + * interface. Buffer should begin with the LAPD address field and + * end with the information field. FCS and flag sequences should not + * be included, nor is bit-stuffing required - all these functions are + * performed by the chip. The callback function will be called + * DURING THE TOP HALF OF AN INTERRUPT HANDLER and will be passed + * both the arbitrary callback_arg and an integer error indication: + * + * 0 - successful transmission; ready for next packet + * non-0 - error value from chip's DER (D-Channel Error Register): + * 4 - collision detect + * 128 - underrun; irq routine didn't service chip fast enough + * + * The callback routine should defer any time-consuming operations + * to a bottom-half handler; however, amd7930_dxmit may be called + * from within the callback to request back-to-back transmission of + * a second packet (without repeating the priority/collision mechanism) + * + * A comment about the "collision detect" error, which is signalled + * whenever the echoed D-channel data didn't match the transmitted + * data. This is part of ISDN's normal multi-drop T-interface + * operation, indicating that another device has attempted simultaneous + * transmission, but can also result from line noise. An immediate + * requeue via amd7930_dxmit is suggested, but repeated collision + * errors may indicate a more serious problem. + * + * void amd7930_drecv(int dev, __u8 *buffer, unsigned int size, + * void (*callback)(void *, int, unsigned int), + * void *callback_arg) + * + * register a buffer - buffer, size - into which a D-channel packet + * can be received. The callback function will be called DURING + * THE TOP HALF OF AN INTERRUPT HANDLER and will be passed an + * arbitrary callback_arg, an integer error indication and the length + * of the received packet, which will start with the address field, + * end with the information field, and not contain flag or FCS + * bytes. Bit-stuffing will already have been corrected for. + * Possible values of second callback argument "error": + * + * 0 - successful reception + * non-0 - error value from chip's DER (D-Channel Error Register): + * 1 - recieved packet abort + * 2 - framing error; non-integer number of bytes received + * 8 - FCS error; CRC sequence indicated corrupted data + * 16 - overflow error; packet exceeded size of buffer + * 32 - underflow error; packet smaller than required five bytes + * 64 - overrun error; irq routine didn't service chip fast enough + * + * int amd7930_bopen(int dev, int chan, u_char xmit_idle_char) + * + * This function should be called before any other operations on a B + * channel. In addition to arranging for interrupt handling and + * channel multiplexing, it sets the xmit_idle_char which is + * transmitted on the interface when no data buffer is available. + * Suggested values are: 0 for ISDN audio; FF for HDLC mark idle; 7E + * for HDLC flag idle. Returns 0 on a successful open; -1 on error, + * which is quite possible if audio and the other ISDN channel are + * already in use, since the Am7930 can only send two of the three + * channels to the processor + * + * void amd7930_bclose(int dev, int chan) + * + * Shuts down a B channel when no longer in use. + * + * void amd7930_bxmit(int dev, int chan, __u8 *buffer, unsigned int count, + * void (*callback)(void *), void *callback_arg) + * + * transmits a raw data block - specified with buffer, count - over + * the B channel interface specified by dev/chan. The callback + * function will be called DURING THE TOP HALF OF AN INTERRUPT + * HANDLER and will be passed the arbitrary callback_arg + * + * The callback routine should defer any time-consuming operations + * to a bottom-half handler; however, amd7930_bxmit may be called + * from within the callback to request back-to-back transmission of + * another data block + * + * void amd7930_brecv(int dev, int chan, __u8 *buffer, unsigned int size, + * void (*callback)(void *), void *callback_arg) + * + * receive a raw data block - specified with buffer, size - over the + * B channel interface specified by dev/chan. The callback function + * will be called DURING THE TOP HALF OF AN INTERRUPT HANDLER and + * will be passed the arbitrary callback_arg + * + * The callback routine should defer any time-consuming operations + * to a bottom-half handler; however, amd7930_brecv may be called + * from within the callback to register another buffer and ensure + * continuous B channel reception without loss of data + * */ -static struct sparcaudio_driver drivers[MAX_DRIVERS]; -static int num_drivers; + +int amd7930_get_irqnum(int dev) +{ + struct amd7930_info *info; + + if (dev > num_drivers) { + return(0); + } + + info = (struct amd7930_info *) drivers[dev].private; + + return info->irq; +} + +int amd7930_get_liu_state(int dev) +{ + struct amd7930_info *info; + + if (dev > num_drivers) { + return(0); + } + + info = (struct amd7930_info *) drivers[dev].private; + + return info->liu_state; +} + +void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg) +{ + struct amd7930_info *info; + register unsigned long flags; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + /* Set callback for LIU state change */ + info->liu_callback = callback; + info->liu_callback_arg = callback_arg; + + /* De-activate the ISDN Line Interface Unit (LIU) */ + info->regs->cr = AMR_LIU_LMR1; + info->regs->dr = 0; + + /* Request interrupt when LIU changes state from/to F3/F7/F8 */ + info->regs->cr = AMR_LIU_LMR2; + info->regs->dr = AM_LIU_LMR2_EN_F3_INT | + AM_LIU_LMR2_EN_F7_INT | AM_LIU_LMR2_EN_F8_INT; + + /* amd7930_enable_ints(info); */ + + /* Activate the ISDN Line Interface Unit (LIU) */ + info->regs->cr = AMR_LIU_LMR1; + info->regs->dr = AM_LIU_LMR1_LIU_ENABL; + + restore_flags(flags); +} + +void amd7930_liu_activate(int dev, int priority) +{ + struct amd7930_info *info; + register unsigned long flags; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + /* Set D-channel access priority + * + * I.430 defines a priority mechanism based on counting 1s + * in the echo channel before transmitting + * + * Priority 0 is eight 1s; priority 1 is ten 1s; etc + */ + + info->regs->cr = AMR_LIU_LPR; + info->regs->dr = priority & 0x0f; + + /* request LIU activation */ + + info->regs->cr = AMR_LIU_LMR1; + info->regs->dr = AM_LIU_LMR1_LIU_ENABL | AM_LIU_LMR1_REQ_ACTIV; + + restore_flags(flags); +} + +void amd7930_liu_deactivate(int dev) +{ + struct amd7930_info *info; + register unsigned long flags; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + /* deactivate LIU */ + + info->regs->cr = AMR_LIU_LMR1; + info->regs->dr = 0; + + restore_flags(flags); +} + +void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count, + void (*callback)(void *, int), void *callback_arg) +{ + struct amd7930_info *info; + register unsigned long flags; + __u8 dmr1; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + if (info->D.output_ptr) { + restore_flags(flags); + printk("amd7930_dxmit: transmitter in use\n"); + return; + } + + info->D.output_ptr = buffer; + info->D.output_count = count; + info->D.output_callback = callback; + info->D.output_callback_arg = callback_arg; + + /* Enable D-channel Transmit Threshold interrupt; disable addressing */ + info->regs->cr = AMR_DLC_DMR1; + dmr1 = info->regs->dr; + dmr1 |= AMR_DLC_DMR1_DTTHRSH_INT; + dmr1 &= ~AMR_DLC_DMR1_EN_ADDRS; + info->regs->dr = dmr1; + + /* Begin xmit by setting D-channel Transmit Byte Count Reg (DTCR) */ + info->regs->cr = AMR_DLC_DTCR; + info->regs->dr = count & 0xff; + info->regs->dr = (count >> 8) & 0xff; + + /* Prime xmit FIFO */ + /* fill_D_xmit_fifo(info); */ + transceive_Dchannel(info); + + restore_flags(flags); +} + +void amd7930_drecv(int dev, __u8 *buffer, unsigned int size, + void (*callback)(void *, int, unsigned int), + void *callback_arg) +{ + struct amd7930_info *info; + register unsigned long flags; + __u8 dmr1; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + if (info->D.input_ptr) { + restore_flags(flags); + printk("amd7930_drecv: receiver already has buffer!\n"); + return; + } + + info->D.input_ptr = buffer; + info->D.input_count = 0; + info->D.input_limit = size; + info->D.input_callback = callback; + info->D.input_callback_arg = callback_arg; + + /* Enable D-channel Receive Threshold interrupt; + * Enable D-channel End of Receive Packet interrupt; + * Disable address recognition + */ + info->regs->cr = AMR_DLC_DMR1; + dmr1 = info->regs->dr; + dmr1 |= AMR_DLC_DMR1_DRTHRSH_INT | AMR_DLC_DMR1_EORP_INT; + dmr1 &= ~AMR_DLC_DMR1_EN_ADDRS; + info->regs->dr = dmr1; + + /* Set D-channel Receive Byte Count Limit Register */ + info->regs->cr = AMR_DLC_DRCR; + info->regs->dr = size & 0xff; + info->regs->dr = (size >> 8) & 0xff; + + restore_flags(flags); +} + +int amd7930_bopen(int dev, int chan, u_char xmit_idle_char) +{ + struct amd7930_info *info; + register unsigned long flags; + + if (dev > num_drivers || chan<0 || chan>1) { + return -1; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + if (info->Bb.channel_status == CHANNEL_AVAILABLE) { + + info->Bb.channel_status = CHANNEL_INUSE; + info->Bb.xmit_idle_char = xmit_idle_char; + info->Bisdn[chan] = &info->Bb; + + /* Multiplexor map - isdn (B1/2) to Bb */ + info->regs->cr = AMR_MUX_MCR2 + chan; + info->regs->dr = (AM_MUX_CHANNEL_B1 + chan) | + (AM_MUX_CHANNEL_Bb << 4); + + } else if (info->Bc.channel_status == CHANNEL_AVAILABLE) { + + info->Bc.channel_status = CHANNEL_INUSE; + info->Bc.xmit_idle_char = xmit_idle_char; + info->Bisdn[chan] = &info->Bc; + + /* Multiplexor map - isdn (B1/2) to Bc */ + info->regs->cr = AMR_MUX_MCR2 + chan; + info->regs->dr = (AM_MUX_CHANNEL_B1 + chan) | + (AM_MUX_CHANNEL_Bc << 4); + + } else { + restore_flags(flags); + return (-1); + } + + /* Enable B channel transmit */ + info->regs->cr = AMR_LIU_LMR1; + info->regs->dr |= AM_LIU_LMR1_B1_ENABL + chan; + + /* Enable B channel interrupts */ + info->regs->cr = AMR_MUX_MCR4; + info->regs->dr = AM_MUX_MCR4_ENABLE_INTS | AM_MUX_MCR4_REVERSE_Bb | AM_MUX_MCR4_REVERSE_Bc; + + restore_flags(flags); + return 0; +} + +void amd7930_bclose(int dev, int chan) +{ + struct amd7930_info *info; + register unsigned long flags; + + if (dev > num_drivers || chan<0 || chan>1) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + + save_and_cli(flags); + + if (info->Bisdn[chan]) { + info->Bisdn[chan]->channel_status = CHANNEL_AVAILABLE; + info->regs->cr = AMR_MUX_MCR2 + chan; + info->regs->dr = 0; + info->Bisdn[chan] = NULL; + + /* Disable B channel transmit */ + info->regs->cr = AMR_LIU_LMR1; + info->regs->dr &= ~(AM_LIU_LMR1_B1_ENABL + chan); + + if (info->Bb.channel_status == CHANNEL_AVAILABLE && + info->Bc.channel_status == CHANNEL_AVAILABLE) { + + /* Disable B channel interrupts */ + info->regs->cr = AMR_MUX_MCR4; + info->regs->dr = 0; + } + } + + restore_flags(flags); +} + +void amd7930_bxmit(int dev, int chan, __u8 * buffer, unsigned long count, + void (*callback)(void *), void *callback_arg) +{ + struct amd7930_info *info; + struct amd7930_channel *Bchan; + register unsigned long flags; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + Bchan = info->Bisdn[chan]; + + if (Bchan) { + save_and_cli(flags); + + Bchan->output_ptr = buffer; + Bchan->output_count = count; + Bchan->output_callback = (void *) callback; + Bchan->output_callback_arg = callback_arg; + + restore_flags(flags); + } +} + +void amd7930_brecv(int dev, int chan, __u8 * buffer, unsigned long size, + void (*callback)(void *), void *callback_arg) +{ + struct amd7930_info *info; + struct amd7930_channel *Bchan; + register unsigned long flags; + + if (dev > num_drivers) { + return; + } + + info = (struct amd7930_info *) drivers[dev].private; + Bchan = info->Bisdn[chan]; + + if (Bchan) { + save_and_cli(flags); + + Bchan->input_ptr = buffer; + Bchan->input_count = size; + Bchan->input_callback = (void *) callback; + Bchan->input_callback_arg = callback_arg; + + restore_flags(flags); + } +} + +EXPORT_SYMBOL(amd7930_get_irqnum); +EXPORT_SYMBOL(amd7930_get_liu_state); +EXPORT_SYMBOL(amd7930_liu_init); +EXPORT_SYMBOL(amd7930_liu_activate); +EXPORT_SYMBOL(amd7930_liu_deactivate); +EXPORT_SYMBOL(amd7930_dxmit); +EXPORT_SYMBOL(amd7930_drecv); +EXPORT_SYMBOL(amd7930_bopen); +EXPORT_SYMBOL(amd7930_bclose); +EXPORT_SYMBOL(amd7930_bxmit); +EXPORT_SYMBOL(amd7930_brecv); + + +/* + * Device detection and initialization. + */ static struct sparcaudio_operations amd7930_ops = { amd7930_open, amd7930_release, - NULL, /* amd7930_ioctl */ + NULL, /* amd7930_ioctl */ amd7930_start_output, amd7930_stop_output, amd7930_start_input, amd7930_stop_input, amd7930_sunaudio_getdev, + amd7930_set_output_volume, + amd7930_get_output_volume, + amd7930_set_input_volume, + amd7930_get_input_volume, + amd7930_set_monitor_volume, + amd7930_get_monitor_volume, + NULL, /* amd7930_set_output_balance */ + amd7930_get_output_balance, + NULL, /* amd7930_set_input_balance */ + amd7930_get_input_balance, + NULL, /* amd7930_set_output_channels */ + amd7930_get_output_channels, + NULL, /* amd7930_set_input_channels */ + amd7930_get_input_channels, + NULL, /* amd7930_set_output_precision */ + amd7930_get_output_precision, + NULL, /* amd7930_set_input_precision */ + amd7930_get_input_precision, + NULL, /* amd7930_set_output_port */ + amd7930_get_output_port, + NULL, /* amd7930_set_input_port */ + amd7930_get_input_port, + NULL, /* amd7930_set_output_encoding */ + amd7930_get_output_encoding, + NULL, /* amd7930_set_input_encoding */ + amd7930_get_input_encoding, + NULL, /* amd7930_set_output_rate */ + amd7930_get_output_rate, + NULL, /* amd7930_set_input_rate */ + amd7930_get_input_rate, + amd7930_sunaudio_getdev_sunos, + amd7930_get_output_ports, + amd7930_get_input_ports, + NULL, /* amd7930_set_output_muted */ + amd7930_get_output_muted, }; /* Attach to an amd7930 chip given its PROM node. */ @@ -348,8 +1335,10 @@ static int amd7930_attach(struct sparcaudio_driver *drv, int node, /* Point at the information structure and initialize it. */ drv->ops = &amd7930_ops; info = (struct amd7930_info *)drv->private; - info->output_ptr = info->input_ptr = NULL; - info->output_count = info->input_count = 0; + info->Bb.output_ptr = info->Bb.input_ptr = NULL; + info->Bb.output_count = info->Bb.input_count = 0; + info->Bc.output_ptr = info->Bc.input_ptr = NULL; + info->Bc.output_count = info->Bc.input_count = 0; info->ints_on = 1; /* force disable below */ /* Map the registers into memory. */ @@ -365,15 +1354,14 @@ static int amd7930_attach(struct sparcaudio_driver *drv, int node, return -EIO; } - /* Disable amd7930 interrupt generation. */ - amd7930_disable_ints(drv); + /* Put amd7930 in idle mode (interrupts disabled) */ + amd7930_idle(info); - /* Initialize the MUX unit to connect the MAP to the CPU. */ - info->regs->cr = AMR_MUX_1_4; - info->regs->dr = (AM_MUX_CHANNEL_Bb << 4) | AM_MUX_CHANNEL_Ba; - info->regs->dr = 0; - info->regs->dr = 0; - info->regs->dr = AM_MUX_MCR4_ENABLE_INTS; + /* Enable extended FIFO operation on D-channel */ + info->regs->cr = AMR_DLC_EFCR; + info->regs->dr = AMR_DLC_EFCR_EXTEND_FIFO; + info->regs->cr = AMR_DLC_DMR4; + info->regs->dr = /* AMR_DLC_DMR4_RCV_30 | */ AMR_DLC_DMR4_XMT_14; /* Attach the interrupt handler to the audio interrupt. */ prom_getproperty(node, "intr", (char *)&irq, sizeof(irq)); @@ -381,6 +1369,7 @@ static int amd7930_attach(struct sparcaudio_driver *drv, int node, request_irq(info->irq, amd7930_interrupt, SA_INTERRUPT, "amd7930", drv); enable_irq(info->irq); + amd7930_enable_ints(info); /* Initalize the local copy of the MAP registers. */ memset(&info->map, 0, sizeof(info->map)); @@ -413,7 +1402,7 @@ static void amd7930_detach(struct sparcaudio_driver *drv) struct amd7930_info *info = (struct amd7930_info *)drv->private; unregister_sparcaudio_driver(drv); - amd7930_disable_ints(drv); + amd7930_idle(info); disable_irq(info->irq); free_irq(info->irq, drv); sparc_free_io(info->regs, info->regs_size); @@ -421,7 +1410,6 @@ static void amd7930_detach(struct sparcaudio_driver *drv) } #endif - /* Probe for the amd7930 chip and then attach the driver. */ #ifdef MODULE int init_module(void) @@ -433,12 +1421,6 @@ __initfunc(int amd7930_init(void)) struct linux_sbus_device *sdev; int node; -#if 0 -#ifdef MODULE - register_symtab(0); -#endif -#endif - /* Try to find the sun4c "audio" node first. */ node = prom_getchild(prom_root_node); node = prom_searchsiblings(node, "audio"); diff --git a/drivers/sbus/audio/amd7930.h b/drivers/sbus/audio/amd7930.h index be860d10c..8df197061 100644 --- a/drivers/sbus/audio/amd7930.h +++ b/drivers/sbus/audio/amd7930.h @@ -15,6 +15,26 @@ #include <linux/types.h> +/* Exported ISDN functions */ + +int amd7930_get_irqnum(int dev); +int amd7930_get_liu_state(int dev); +void amd7930_liu_init(int dev, void (*callback)(), void *callback_arg); +void amd7930_liu_activate(int dev, int priority); +void amd7930_liu_deactivate(int dev); +void amd7930_dxmit(int dev, __u8 *buffer, unsigned int count, + void (*callback)(void *, int), void *callback_arg); +void amd7930_drecv(int dev, __u8 *buffer, unsigned int size, + void (*callback)(void *, int, unsigned int), + void *callback_arg); +int amd7930_bopen(int dev, int chan, u_char xmit_idle_char); +void amd7930_bclose(int dev, int chan); +void amd7930_bxmit(int dev, int chan, __u8 * buffer, unsigned long count, + void (*callback)(void *), void *callback_arg); +void amd7930_brecv(int dev, int chan, __u8 * buffer, unsigned long size, + void (*callback)(void *), void *callback_arg); + + /* Register interface presented to the CPU by the amd7930. */ struct amd7930 { @@ -48,6 +68,20 @@ struct amd7930_map { }; +/* 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 @@ -67,9 +101,29 @@ struct amd7930_map { /* 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 @@ -134,7 +188,24 @@ struct amd7930_map { #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 @@ -142,10 +213,66 @@ struct amd7930_map { #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 @@ -160,4 +287,6 @@ struct amd7930_map { #define AMR_PP_PPCR2 0xC8 #define AMR_PP_PPCR3 0xC9 +/* Give this chip a "default" sample rate */ +#define AMD7930_RATE (8000) #endif diff --git a/drivers/sbus/audio/audio.c b/drivers/sbus/audio/audio.c index ae77b1455..ba9563449 100644 --- a/drivers/sbus/audio/audio.c +++ b/drivers/sbus/audio/audio.c @@ -1,7 +1,7 @@ /* * drivers/sbus/audio/audio.c * - * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu) * * This is the audio midlayer that sits between the VFS character * devices and the low-level audio hardware device drivers. @@ -19,6 +19,8 @@ #include <linux/malloc.h> #include <linux/interrupt.h> #include <linux/init.h> +#include <linux/soundcard.h> +#include <asm/uaccess.h> #include "audio.h" @@ -42,33 +44,47 @@ int register_sparcaudio_driver(struct sparcaudio_driver *drv) if (!drv->ops || !drv->ops->start_output || !drv->ops->stop_output) return -EINVAL; - /* Setup the circular queue of output buffers. */ + /* Setup the circular queues of output and input buffers + * + * Each buffer is a single page, but output buffers might + * be partially filled (by a write with count < PAGE_SIZE), + * so each output buffer also has a paired output size. + * + * Input buffers, on the other hand, always fill completely, + * so we don't need input counts - each contains PAGE_SIZE + * bytes of audio data. + * + * TODO: Make number of input/output buffers tunable parameters + */ + drv->num_output_buffers = 32; drv->output_front = 0; drv->output_rear = 0; drv->output_count = 0; drv->output_active = 0; - drv->output_buffers = kmalloc(32 * sizeof(__u8 *), GFP_KERNEL); - drv->output_sizes = kmalloc(32 * sizeof(size_t), GFP_KERNEL); - if (!drv->output_buffers || !drv->output_sizes) { - if (drv->output_buffers) - kfree(drv->output_buffers); - if (drv->output_sizes) - kfree(drv->output_sizes); - return -ENOMEM; - } + drv->output_buffers = kmalloc(drv->num_output_buffers * sizeof(__u8 *), GFP_KERNEL); + drv->output_sizes = kmalloc(drv->num_output_buffers * sizeof(size_t), GFP_KERNEL); + if (!drv->output_buffers || !drv->output_sizes) goto kmalloc_failed1; /* Allocate the pages for each output buffer. */ for (i = 0; i < drv->num_output_buffers; i++) { drv->output_buffers[i] = (void *) __get_free_page(GFP_KERNEL); - if (!drv->output_buffers[i]) { - int j; - for (j = 0; j < i; j++) - free_page((unsigned long) drv->output_buffers[j]); - kfree(drv->output_buffers); - kfree(drv->output_sizes); - return -ENOMEM; - } + if (!drv->output_buffers[i]) goto kmalloc_failed2; + } + + /* Setup the circular queue of input buffers. */ + drv->num_input_buffers = 32; + drv->input_front = 0; + drv->input_rear = 0; + drv->input_count = 0; + drv->input_active = 0; + drv->input_buffers = kmalloc(drv->num_input_buffers * sizeof(__u8 *), GFP_KERNEL); + if (!drv->input_buffers) goto kmalloc_failed3; + + /* Allocate the pages for each input buffer. */ + for (i = 0; i < drv->num_input_buffers; i++) { + drv->input_buffers[i] = (void *) __get_free_page(GFP_KERNEL); + if (!drv->input_buffers[i]) goto kmalloc_failed4; } /* Ensure that the driver is marked as not being open. */ @@ -78,6 +94,28 @@ int register_sparcaudio_driver(struct sparcaudio_driver *drv) driver = drv; return 0; + + +kmalloc_failed4: + for (i--; i >= 0; i--) + free_page((unsigned long) drv->input_buffers[i]); + +kmalloc_failed3: + if (drv->input_buffers) + kfree(drv->input_buffers); + i = drv->num_output_buffers; + +kmalloc_failed2: + for (i--; i >= 0; i--) + free_page((unsigned long) drv->output_buffers[i]); + +kmalloc_failed1: + if (drv->output_buffers) + kfree(drv->output_buffers); + if (drv->output_sizes) + kfree(drv->output_sizes); + + return -ENOMEM; } int unregister_sparcaudio_driver(struct sparcaudio_driver *drv) @@ -94,6 +132,11 @@ int unregister_sparcaudio_driver(struct sparcaudio_driver *drv) kfree(driver->output_buffers); kfree(driver->output_sizes); + /* Deallocate the queue of input buffers. */ + for (i = 0; i < driver->num_input_buffers; i++) + free_page((unsigned long) driver->input_buffers[i]); + kfree(driver->input_buffers); + MOD_DEC_USE_COUNT; driver = NULL; @@ -116,6 +159,7 @@ static void sparcaudio_output_done_task(void * arg) void sparcaudio_output_done(struct sparcaudio_driver * drv) { /* Point the queue after the "done" buffer. */ + drv->output_size -= drv->output_sizes[drv->output_front]; drv->output_front = (drv->output_front + 1) % drv->num_output_buffers; drv->output_count--; @@ -146,7 +190,23 @@ void sparcaudio_output_done(struct sparcaudio_driver * drv) void sparcaudio_input_done(struct sparcaudio_driver * drv) { - /* XXX Implement! */ + /* Point the queue after the "done" buffer. */ + drv->input_front = (drv->input_front + 1) % drv->num_input_buffers; + drv->input_count++; + + /* If the input queue is full, shutdown the driver. */ + if (drv->input_count == drv->num_input_buffers) { + /* Stop the lowlevel driver from inputing. */ + drv->ops->stop_input(drv); + drv->input_active = 0; + } else { + /* Otherwise, give the driver the next buffer. */ + drv->ops->start_input(drv, drv->input_buffers[drv->input_front], + PAGE_SIZE); + } + + /* Wake up any tasks that are waiting. */ + wake_up_interruptible(&drv->input_read_wait); } @@ -155,34 +215,85 @@ void sparcaudio_input_done(struct sparcaudio_driver * drv) * VFS layer interface */ -static int sparcaudio_lseek(struct inode * inode, struct file * file, - off_t offset, int origin) +static loff_t sparcaudio_llseek(struct file * file, loff_t offset, int origin) { return -ESPIPE; } -static int sparcaudio_read(struct inode * inode, struct file * file, - char *buf, int count) +static ssize_t sparcaudio_read(struct file * file, + char *buf, size_t count, loff_t *ppos) { - /* XXX Implement me! */ - return -EINVAL; + int bytes_to_copy; + + if (! file->f_mode & FMODE_READ) + return -EINVAL; + + if (driver->input_count == 0) { + interruptible_sleep_on(&driver->input_read_wait); + if (signal_pending(current)) + return -EINTR; + } + + bytes_to_copy = PAGE_SIZE - driver->input_offset; + if (bytes_to_copy > count) + bytes_to_copy = count; + + copy_to_user_ret(buf, driver->input_buffers[driver->input_rear]+driver->input_offset, + bytes_to_copy, -EFAULT); + driver->input_offset += bytes_to_copy; + + if (driver->input_offset >= PAGE_SIZE) { + driver->input_rear = (driver->input_rear + 1) % driver->num_input_buffers; + driver->input_count--; + driver->input_offset = 0; + } + + return bytes_to_copy; +} + +static void sparcaudio_reorganize_buffers(struct sparcaudio_driver * driver) +{ + /* It may never matter but if it does this routine will pack */ + /* buffers to free space for more data */ } -static int sparcaudio_write(struct inode * inode, struct file * file, - const char *buf, int count) +static void sparcaudio_sync_output(struct sparcaudio_driver * driver) { unsigned long flags; - int bytes_written = 0, bytes_to_copy, err; + + /* If the low-level driver is not active, activate it. */ + save_and_cli(flags); + if (! driver->output_active) { + driver->ops->start_output(driver, + driver->output_buffers[driver->output_front], + driver->output_sizes[driver->output_front]); + driver->output_active = 1; + } + restore_flags(flags); +} + +static ssize_t sparcaudio_write(struct file * file, const char *buf, + size_t count, loff_t *ppos) +{ + int bytes_written = 0, bytes_to_copy; /* Ensure that we have something to write. */ - if (count < 1) + if (count < 1) { + sparcaudio_sync_output(driver); return 0; + } /* Loop until all output is written to device. */ while (count > 0) { /* Check to make sure that an output buffer is available. */ + /* If not, make valiant attempt */ + if (driver->output_count == driver->num_output_buffers) + sparcaudio_reorganize_buffers(driver); + if (driver->output_count == driver->num_output_buffers) { - interruptible_sleep_on(&driver->output_write_wait); + /* We need buffers, so... */ + sparcaudio_sync_output(driver); + interruptible_sleep_on(&driver->output_write_wait); if (signal_pending(current)) return bytes_written > 0 ? bytes_written : -EINTR; } @@ -202,15 +313,11 @@ static int sparcaudio_write(struct inode * inode, struct file * file, driver->output_sizes[driver->output_rear] = bytes_to_copy; driver->output_rear = (driver->output_rear + 1) % driver->num_output_buffers; driver->output_count++; + driver->output_size += bytes_to_copy; - /* If the low-level driver is not active, activate it. */ - save_and_cli(flags); - if (! driver->output_active) { - driver->ops->start_output(driver, driver->output_buffers[driver->output_front], - driver->output_sizes[driver->output_front]); - driver->output_active = 1; - } - restore_flags(flags); + /* Activate the driver if more than page of data is waiting. */ + if (driver->output_size > 4096) + sparcaudio_sync_output(driver); } /* Return the number of bytes written to the caller. */ @@ -221,8 +328,10 @@ static int sparcaudio_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { int retval = 0; + struct audio_info ainfo; switch (cmd) { + case SNDCTL_DSP_SYNC: case AUDIO_DRAIN: if (driver->output_count > 0) { interruptible_sleep_on(&driver->output_drain_wait); @@ -239,18 +348,381 @@ static int sparcaudio_ioctl(struct inode * inode, struct file * file, copy_to_user_ret((audio_device_t *)arg, &tmp, sizeof(tmp), -EFAULT); } else retval = -EINVAL; + + printk(KERN_INFO "sparcaudio_ioctl: AUDIO_GETDEV\n"); break; + case AUDIO_GETDEV_SUNOS: + if (driver->ops->sunaudio_getdev_sunos) { + int tmp=driver->ops->sunaudio_getdev_sunos(driver); + + copy_to_user_ret((int *)arg, &tmp, sizeof(tmp), -EFAULT); + } else + retval = -EINVAL; + + printk(KERN_INFO "sparcaudio_ioctl: AUDIO_GETDEV_SUNOS\n"); + break; + + case AUDIO_GETINFO: + + AUDIO_INITINFO(&ainfo); + + if (driver->ops->get_input_rate) + ainfo.record.sample_rate = + driver->ops->get_input_rate(driver); + if (driver->ops->get_input_channels) + ainfo.record.channels = + driver->ops->get_input_channels(driver); + if (driver->ops->get_input_precision) + ainfo.record.precision = + driver->ops->get_input_precision(driver); + if (driver->ops->get_input_encoding) + ainfo.record.encoding = + driver->ops->get_input_encoding(driver); + if (driver->ops->get_input_volume) + ainfo.record.gain = + driver->ops->get_input_volume(driver); + if (driver->ops->get_input_port) + ainfo.record.port = + driver->ops->get_input_port(driver); + if (driver->ops->get_input_ports) + ainfo.record.avail_ports = + driver->ops->get_input_ports(driver); + ainfo.record.buffer_size = PAGE_SIZE; + ainfo.record.samples = 0; + ainfo.record.eof = 0; + ainfo.record.pause = 0; + ainfo.record.error = 0; + ainfo.record.waiting = 0; + if (driver->ops->get_input_balance) + ainfo.record.balance = + driver->ops->get_input_balance(driver); + ainfo.record.minordev = 4; + ainfo.record.open = 1; + ainfo.record.active = 0; + + if (driver->ops->get_output_rate) + ainfo.play.sample_rate = + driver->ops->get_output_rate(driver); + if (driver->ops->get_output_channels) + ainfo.play.channels = + driver->ops->get_output_channels(driver); + if (driver->ops->get_output_precision) + ainfo.play.precision = + driver->ops->get_output_precision(driver); + if (driver->ops->get_output_encoding) + ainfo.play.encoding = + driver->ops->get_output_encoding(driver); + if (driver->ops->get_output_volume) + ainfo.play.gain = + driver->ops->get_output_volume(driver); + if (driver->ops->get_output_port) + ainfo.play.port = + driver->ops->get_output_port(driver); + if (driver->ops->get_output_ports) + ainfo.play.avail_ports = + driver->ops->get_output_ports(driver); + ainfo.play.buffer_size = PAGE_SIZE; + ainfo.play.samples = 0; + ainfo.play.eof = 0; + ainfo.play.pause = 0; + ainfo.play.error = 0; + ainfo.play.waiting = waitqueue_active(&driver->open_wait); + if (driver->ops->get_output_balance) + ainfo.play.balance = + driver->ops->get_output_balance(driver); + ainfo.play.minordev = 4; + ainfo.play.open = 1; + ainfo.play.active = driver->output_active; + + if (driver->ops->get_monitor_volume) + ainfo.monitor_gain = + driver->ops->get_monitor_volume(driver); + + if (driver->ops->get_output_muted) + ainfo.output_muted = + driver->ops->get_output_muted(driver); + + printk("sparcaudio_ioctl: AUDIO_GETINFO\n"); + + copy_to_user_ret((struct audio_info *)arg, &ainfo, + sizeof(ainfo), -EFAULT); + + break; + + case AUDIO_SETINFO: + { + audio_info_t curinfo; + + copy_from_user_ret(&ainfo, (audio_info_t *) arg, sizeof(audio_info_t), -EFAULT); + + /* Without these there's no point in trying */ + if (!driver->ops->get_input_precision || + !driver->ops->get_input_channels || + !driver->ops->get_input_rate || + !driver->ops->get_input_encoding || + !driver->ops->get_output_precision || + !driver->ops->get_output_channels || + !driver->ops->get_output_rate || + !driver->ops->get_output_encoding) + { + retval = -EINVAL; + break; + } + + /* Do bounds checking for things which always apply. + * Follow with enforcement of basic tenets of certain + * encodings. Everything over and above generic is + * enforced by the driver, which can assume that + * Martian cases are taken care of here. */ + if (Modify(ainfo.play.gain) && + ((ainfo.play.gain > AUDIO_MAX_GAIN) || + (ainfo.play.gain < AUDIO_MIN_GAIN))) { + /* Need to differentiate this from e.g. the above error */ + retval = -EINVAL; + break; + } + if (Modify(ainfo.record.gain) && + ((ainfo.record.gain > AUDIO_MAX_GAIN) || + (ainfo.record.gain < AUDIO_MIN_GAIN))) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.monitor_gain) && + ((ainfo.monitor_gain > AUDIO_MAX_GAIN) || + (ainfo.monitor_gain < AUDIO_MIN_GAIN))) { + retval = -EINVAL; + break; + } + /* Don't need to check less than zero on these */ + if (Modifyc(ainfo.play.balance) && + (ainfo.play.balance > AUDIO_RIGHT_BALANCE)) { + retval = -EINVAL; + break; + } + if (Modifyc(ainfo.record.balance) && + (ainfo.record.balance > AUDIO_RIGHT_BALANCE)) { + retval = -EINVAL; + break; + } + + /* If any of these changed, record them all, then make + * changes atomically. If something fails, back it all out. */ + if (Modify(ainfo.record.precision) || + Modify(ainfo.record.sample_rate) || + Modify(ainfo.record.channels) || + Modify(ainfo.record.encoding) || + Modify(ainfo.play.precision) || + Modify(ainfo.play.sample_rate) || + Modify(ainfo.play.channels) || + Modify(ainfo.play.encoding)) + { + /* If they're trying to change something we + * have no routine for, they lose */ + if ((!driver->ops->set_input_encoding && + Modify(ainfo.record.encoding)) || + (!driver->ops->set_input_rate && + Modify(ainfo.record.sample_rate)) || + (!driver->ops->set_input_precision && + Modify(ainfo.record.precision)) || + (!driver->ops->set_input_channels && + Modify(ainfo.record.channels))) { + retval = -EINVAL; + break; + } + + curinfo.record.encoding = (Modify(ainfo.record.encoding) ? + ainfo.record.encoding : + driver->ops->get_input_encoding(driver)); + curinfo.record.sample_rate = (Modify(ainfo.record.sample_rate) ? + ainfo.record.sample_rate : + driver->ops->get_input_rate(driver)); + curinfo.record.precision = (Modify(ainfo.record.precision) ? + ainfo.record.precision : + driver->ops->get_input_precision(driver)); + curinfo.record.channels = (Modify(ainfo.record.channels) ? + ainfo.record.channels : + driver->ops->get_input_channels(driver)); + switch (curinfo.record.encoding) { + case AUDIO_ENCODING_ALAW: + case AUDIO_ENCODING_ULAW: + if (Modify(ainfo.record.precision) && + ainfo.record.precision != 8) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.record.channels) && + ainfo.record.channels != 1) { + retval = -EINVAL; + break; + } + break; + case AUDIO_ENCODING_LINEAR: + case AUDIO_ENCODING_LINEARLE: + if (Modify(ainfo.record.precision) && + ainfo.record.precision != 16) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.record.channels) && + (ainfo.record.channels != 1 && + ainfo.record.channels != 2)) + { + retval = -EINVAL; + break; + } + break; + case AUDIO_ENCODING_LINEAR8: + if (Modify(ainfo.record.precision) && + ainfo.record.precision != 8) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.record.channels) && + (ainfo.record.channels != 1 && + ainfo.record.channels != 2)) + { + retval = -EINVAL; + break; + } + } + + if (retval < 0) + break; + + /* If they're trying to change something we + * have no routine for, they lose */ + if ((!driver->ops->set_output_encoding && + Modify(ainfo.play.encoding)) || + (!driver->ops->set_output_rate && + Modify(ainfo.play.sample_rate)) || + (!driver->ops->set_output_precision && + Modify(ainfo.play.precision)) || + (!driver->ops->set_output_channels && + Modify(ainfo.play.channels))) { + retval = -EINVAL; + break; + } + + curinfo.play.encoding = (Modify(ainfo.play.encoding) ? + ainfo.play.encoding : + driver->ops->get_output_encoding(driver)); + curinfo.play.sample_rate = (Modify(ainfo.play.sample_rate) ? + ainfo.play.sample_rate : + driver->ops->get_output_rate(driver)); + curinfo.play.precision = (Modify(ainfo.play.precision) ? + ainfo.play.precision : + driver->ops->get_output_precision(driver)); + curinfo.play.channels = (Modify(ainfo.play.channels) ? + ainfo.play.channels : + driver->ops->get_output_channels(driver)); + switch (curinfo.play.encoding) { + case AUDIO_ENCODING_ALAW: + case AUDIO_ENCODING_ULAW: + if (Modify(ainfo.play.precision) && + ainfo.play.precision != 8) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.play.channels) && + ainfo.play.channels != 1) { + retval = -EINVAL; + break; + } + break; + case AUDIO_ENCODING_LINEAR: + case AUDIO_ENCODING_LINEARLE: + if (Modify(ainfo.play.precision) && + ainfo.play.precision != 16) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.play.channels) && + (ainfo.play.channels != 1 && + ainfo.play.channels != 2)) + { + retval = -EINVAL; + break; + } + break; + case AUDIO_ENCODING_LINEAR8: + if (Modify(ainfo.play.precision) && + ainfo.play.precision != 8) { + retval = -EINVAL; + break; + } + if (Modify(ainfo.play.channels) && + (ainfo.play.channels != 1 && + ainfo.play.channels != 2)) + { + retval = -EINVAL; + break; + } + } + + if (retval < 0) + break; + + /* If we got this far, we're at least sane with + * respect to generics. Try the changes. */ + if ((driver->ops->set_input_precision(driver, ainfo.record.precision) < 0) || + (driver->ops->set_output_precision(driver, ainfo.play.precision) < 0) || + (driver->ops->set_input_channels(driver, ainfo.record.channels) < 0) || + (driver->ops->set_output_channels(driver, ainfo.play.channels) < 0) || + (driver->ops->set_input_rate(driver, ainfo.record.sample_rate) < 0) || + (driver->ops->set_output_rate(driver, ainfo.play.sample_rate) < 0) || + (driver->ops->set_input_encoding(driver, ainfo.record.encoding) < 0) || + (driver->ops->set_output_encoding(driver, ainfo.play.encoding) < 0)) + { + /* Pray we can set it all back. If not, uh... */ + driver->ops->set_input_precision(driver, curinfo.record.precision); + driver->ops->set_output_precision(driver, curinfo.play.precision); + driver->ops->set_input_channels(driver, curinfo.record.channels); + driver->ops->set_output_channels(driver, curinfo.play.channels); + driver->ops->set_input_rate(driver, curinfo.record.sample_rate); + driver->ops->set_output_rate(driver, curinfo.play.sample_rate); + driver->ops->set_input_encoding(driver, curinfo.record.encoding); + driver->ops->set_output_encoding(driver, curinfo.play.encoding); + } + + } + + printk("sparcaudio_ioctl: AUDIO_SETINFO\n"); + break; + } + default: if (driver->ops->ioctl) retval = driver->ops->ioctl(inode,file,cmd,arg,driver); - else + else { retval = -EINVAL; + + printk("sparcaudio_ioctl: 0x%x\n", cmd); + } } return retval; } +static int sparcaudioctl_release(struct inode * inode, struct file * file) +{ + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct file_operations sparcaudioctl_fops = { + NULL, + NULL, + NULL, + NULL, /* sparcaudio_readdir */ + NULL, /* sparcaudio_select */ + sparcaudio_ioctl, + NULL, /* sparcaudio_mmap */ + NULL, + sparcaudioctl_release +}; + static int sparcaudio_open(struct inode * inode, struct file * file) { int err; @@ -259,6 +731,15 @@ static int sparcaudio_open(struct inode * inode, struct file * file) if (!driver) return -ENODEV; + if (MINOR(inode->i_rdev) == 5) { + + file->f_op = &sparcaudioctl_fops; + + MOD_INC_USE_COUNT; + + return 0; + } + /* We only support minor #4 (/dev/audio) right now. */ if (MINOR(inode->i_rdev) != 4) return -ENXIO; @@ -285,9 +766,18 @@ static int sparcaudio_open(struct inode * inode, struct file * file) } /* Mark the driver as locked for read and/or write. */ - if (file->f_mode & FMODE_READ) + if (file->f_mode & FMODE_READ) { + driver->input_offset = 0; + driver->input_front = 0; + driver->input_rear = 0; + driver->input_count = 0; + driver->ops->start_input(driver, driver->input_buffers[driver->input_front], + PAGE_SIZE); + driver->input_active = 1; driver->flags |= SDF_OPEN_READ; + } if (file->f_mode & FMODE_WRITE) { + driver->output_size = 0; driver->output_front = 0; driver->output_rear = 0; driver->output_count = 0; @@ -308,8 +798,15 @@ static int sparcaudio_open(struct inode * inode, struct file * file) return 0; } -static void sparcaudio_release(struct inode * inode, struct file * file) +static int sparcaudio_release(struct inode * inode, struct file * file) { + /* Anything in the queue? */ + sparcaudio_sync_output(driver); + + /* Stop input */ + driver->ops->stop_input(driver); + driver->input_active = 0; + /* Wait for any output still in the queue to be played. */ if (driver->output_count > 0) interruptible_sleep_on(&driver->output_drain_wait); @@ -331,10 +828,12 @@ static void sparcaudio_release(struct inode * inode, struct file * file) MOD_DEC_USE_COUNT; wake_up_interruptible(&driver->open_wait); + + return 0; } static struct file_operations sparcaudio_fops = { - sparcaudio_lseek, + sparcaudio_llseek, sparcaudio_read, sparcaudio_write, NULL, /* sparcaudio_readdir */ diff --git a/drivers/sbus/audio/audio.h b/drivers/sbus/audio/audio.h index 127700ab5..5884aeaab 100644 --- a/drivers/sbus/audio/audio.h +++ b/drivers/sbus/audio/audio.h @@ -82,12 +82,13 @@ typedef struct audio_info { /* * Audio encoding types */ -#define AUDIO_ENCODING_NONE (0) /* no encoding assigned */ -#define AUDIO_ENCODING_ULAW (1) /* u-law encoding */ -#define AUDIO_ENCODING_ALAW (2) /* A-law encoding */ -#define AUDIO_ENCODING_LINEAR (3) /* Linear PCM encoding */ -#define AUDIO_ENCODING_DVI (104) /* DVI ADPCM */ -#define AUDIO_ENCODING_LINEAR8 (105) /* 8 bit UNSIGNED */ +#define AUDIO_ENCODING_NONE (0) /* no encoding assigned */ +#define AUDIO_ENCODING_ULAW (1) /* u-law encoding */ +#define AUDIO_ENCODING_ALAW (2) /* A-law encoding */ +#define AUDIO_ENCODING_LINEAR (3) /* Linear PCM encoding */ +#define AUDIO_ENCODING_DVI (104) /* DVI ADPCM */ +#define AUDIO_ENCODING_LINEAR8 (105) /* 8 bit UNSIGNED */ +#define AUDIO_ENCODING_LINEARLE (106) /* Linear PCM LE encoding */ /* * These ranges apply to record, play, and monitor gain values @@ -136,6 +137,8 @@ typedef struct audio_info { #define AUDIO_LINE_IN 0x02 /* input from line in */ #define AUDIO_CD 0x04 /* input from on-board CD inputs */ #define AUDIO_INTERNAL_CD_IN AUDIO_CD /* input from internal CDROM */ +/* Supposedly an undocumented feature of the 4231 */ +#define AUDIO_ANALOG_LOOPBACK 0x40 /* @@ -152,6 +155,14 @@ typedef struct audio_info { /* + * These allow testing for what the user wants to set + */ +#define AUD_INITVALUE (~0) +#define Modify(X) ((unsigned int)(X) != AUD_INITVALUE) +#define Modifys(X) ((X) != (unsigned short)AUD_INITVALUE) +#define Modifyc(X) ((X) != (unsigned char)AUD_INITVALUE) + +/* * Parameter for the AUDIO_GETDEV ioctl to determine current * audio devices. */ @@ -192,6 +203,16 @@ typedef struct audio_device { #define AUDIO_SETINFO _IOWR('A', 2, audio_info_t) #define AUDIO_DRAIN _IO('A', 3) #define AUDIO_GETDEV _IOR('A', 4, audio_device_t) +#define AUDIO_GETDEV_SUNOS _IOR('A', 4, int) + +/* Define possible audio hardware configurations for + * old SunOS-style AUDIO_GETDEV ioctl */ + +#define AUDIO_DEV_UNKNOWN (0) /* not defined */ +#define AUDIO_DEV_AMD (1) /* audioamd device */ +#define AUDIO_DEV_SPEAKERBOX (2) /* dbri device with speakerbox */ +#define AUDIO_DEV_CODEC (3) /* dbri device (internal speaker) */ +#define AUDIO_DEV_CS4231 (5) /* cs4231 device */ /* * The following ioctl sets the audio device into an internal loopback mode, @@ -214,8 +235,6 @@ struct audtrace_hdr { }; #endif - - /* * Linux kernel internal implementation. */ @@ -245,10 +264,17 @@ struct sparcaudio_driver /* Support for a circular queue of output buffers. */ __u8 **output_buffers; - size_t *output_sizes; + size_t *output_sizes, output_size; int num_output_buffers, output_front, output_rear; int output_count, output_active; struct wait_queue *output_write_wait, *output_drain_wait; + + /* Support for a circular queue of input buffers. */ + __u8 **input_buffers; + int input_offset; + int num_input_buffers, input_front, input_rear; + int input_count, input_active; + struct wait_queue *input_read_wait; }; struct sparcaudio_operations @@ -272,6 +298,77 @@ struct sparcaudio_operations /* 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 *); }; extern int register_sparcaudio_driver(struct sparcaudio_driver *); @@ -282,6 +379,6 @@ extern int sparcaudio_init(void); extern int amd7930_init(void); extern int cs4231_init(void); -#endif +#endif /* __KERNEL__ */ -#endif +#endif /* _AUDIO_H */ diff --git a/drivers/sbus/audio/cs4215.h b/drivers/sbus/audio/cs4215.h new file mode 100644 index 000000000..966339a02 --- /dev/null +++ b/drivers/sbus/audio/cs4215.h @@ -0,0 +1,120 @@ +/* + * 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 */ + __volatile__ struct dbri_mem td; + __volatile__ struct dbri_mem rd; + __u8 version; + __u8 onboard; +}; + + +/* + * 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, (0<<3) }, + { 16000, 1, (1<<3) }, + { 27429, 1, (2<<3) }, /* Actually 24428.57 */ + { 32000, 1, (3<<3) }, + /* { NA, 1, (4<<3) }, */ + /* { NA, 1, (5<<3) }, */ + { 48000, 1, (6<<3) }, + { 9600, 1, (7<<3) }, + { 5513, 2, (0<<3) }, /* Actually 5512.5 */ + { 11025, 2, (1<<3) }, + { 18900, 2, (2<<3) }, + { 22050, 2, (3<<3) }, + { 37800, 2, (4<<3) }, + { 44100, 2, (5<<3) }, + { 33075, 2, (6<<3) }, + { 6615, 2, (7<<3) }, + { 0, 0, 0 } +}; +#define CS4215_HPF (1<<7) /* High Pass Filter, 1: Enabled */ + +#define CS4215_12_MASK 0xfcbf /* Mask off reseved 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) /* Line Out 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 occured */ +#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 diff --git a/drivers/sbus/audio/cs4231.c b/drivers/sbus/audio/cs4231.c index 5c361a0b0..346f07185 100644 --- a/drivers/sbus/audio/cs4231.c +++ b/drivers/sbus/audio/cs4231.c @@ -8,7 +8,6 @@ * sun4m machines. */ -#include <linux/config.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> @@ -44,8 +43,6 @@ static int cs4231_recintr(struct sparcaudio_driver *drv); static void cs4231_output_muted(struct sparcaudio_driver *drv, unsigned int value); static void cs4231_mute(struct sparcaudio_driver *drv); static void cs4231_pollinput(struct sparcaudio_driver *drv); -static int cs4231_attach(struct sparcaudio_driver *drv, int node, - struct linux_sbus *sbus); #define CHIP_BUG udelay(100); cs4231_ready(drv); udelay(1000); @@ -623,56 +620,16 @@ static struct sparcaudio_operations cs4231_ops = { cs4231_audio_getdev, }; -/* Probe for the cs4231 chip and then attach the driver. */ -#ifdef MODULE -int init_module(void) -#else -__initfunc(int cs4231_init(void)) -#endif -{ - struct linux_sbus *bus; - struct linux_sbus_device *sdev; - int cs4231_node; - - /* Find the PROM CS4231 node. */ - /* There's an easier way, and I should FIXME */ - cs4231_node = prom_getchild(prom_root_node); - cs4231_node = prom_searchsiblings(cs4231_node,"iommu"); - cs4231_node = prom_getchild(cs4231_node); - cs4231_node = prom_searchsiblings(cs4231_node,"sbus"); - cs4231_node = prom_getchild(cs4231_node); - cs4231_node = prom_searchsiblings(cs4231_node,"SUNW,CS4231"); - - if (cs4231_node && cs4231_attach(&drivers[0], cs4231_node, NULL) == 0) - num_drivers = 1; - else - num_drivers = 0; - - /* Probe each SBUS for cs4231 chips. */ - for_all_sbusdev(sdev,bus) { - 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->prom_node, sdev->my_bus) == 0) - num_drivers++; - } - } - - /* Only return success if we found some cs4231 chips. */ - return (num_drivers > 0) ? 0 : -EIO; -} - /* Attach to an cs4231 chip given its PROM node. */ -static int cs4231_attach(struct sparcaudio_driver *drv, int node, - struct linux_sbus *sbus) +static inline int +cs4231_attach(struct sparcaudio_driver *drv, struct linux_sbus_device *sdev) { - struct linux_prom_registers regs; - struct linux_prom_irqs irq; struct cs4231_chip *cs4231_chip; int err; + struct linux_sbus *sbus = sdev->my_bus; +#ifdef __sparc_v9__ + struct devid_cookie dcookie; +#endif /* Allocate our private information structure. */ drv->private = kmalloc(sizeof(struct cs4231_chip), GFP_KERNEL); @@ -690,12 +647,11 @@ static int cs4231_attach(struct sparcaudio_driver *drv, int node, #endif /* Map the registers into memory. */ - prom_getproperty(node, "reg", (char *)®s, sizeof(regs)); - if (sbus) - prom_apply_sbus_ranges(sbus, ®s, 1); - cs4231_chip->regs_size = regs.reg_size; - cs4231_chip->pioregs = sparc_alloc_io(regs.phys_addr, 0, regs.reg_size, - "cs4231", regs.which_io, 0); + prom_apply_sbus_ranges(sbus, sdev->reg_addrs, 1, sdev); + cs4231_chip->regs_size = sdev->reg_addrs[0].reg_size; + cs4231_chip->pioregs = sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0, + sdev->reg_addrs[0].reg_size, + "cs4231", sdev->reg_addrs[0].which_io, 0); if (!cs4231_chip->pioregs) { printk(KERN_ERR "cs4231: could not allocate registers\n"); kfree(drv->private); @@ -706,9 +662,18 @@ static int cs4231_attach(struct sparcaudio_driver *drv, int node, cs4231_reset(drv); /* Attach the interrupt handler to the audio interrupt. */ - prom_getproperty(node, "intr", (char *)&irq, sizeof(irq)); - cs4231_chip->irq = irq.pri; - request_irq(cs4231_chip->irq, cs4231_interrupt, SA_INTERRUPT, "cs4231", NULL); + cs4231_chip->irq = sdev->irqs[0].pri; + +#ifndef __sparc_v9__ + request_irq(cs4231_chip->irq, cs4231_interrupt, SA_SHIRQ, "cs4231", drv); +#else + dcookie.real_dev_id = s; + dcookie.imap = dcookie.iclr = 0; + dcookie.pil = -1; + dcookie.bus_cookie = sdev->my_bus; + request_irq (cs4231_chip->irq, cs4231_interrupt, (SA_SHIRQ | SA_SBUS | SA_DCOOKIE), "cs4231", drv); + cs4231_chip->irq = dcookie.ret_ino; +#endif enable_irq(cs4231_chip->irq); /* Register ourselves with the midlevel audio driver. */ @@ -730,6 +695,32 @@ static int cs4231_attach(struct sparcaudio_driver *drv, int node, return 0; } +/* Probe for the cs4231 chip and then attach the driver. */ +#ifdef MODULE +int init_module(void) +#else +__initfunc(int cs4231_init(void)) +#endif +{ + struct linux_sbus *bus; + struct linux_sbus_device *sdev; + + /* Probe each SBUS for cs4231 chips. */ + for_all_sbusdev(sdev,bus) { + 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++; + } + } + + /* Only return success if we found some cs4231 chips. */ + return (num_drivers > 0) ? 0 : -EIO; +} + #ifdef MODULE /* Detach from an cs4231 chip given the device structure. */ static void cs4231_detach(struct sparcaudio_driver *drv) diff --git a/drivers/sbus/audio/dbri.c b/drivers/sbus/audio/dbri.c new file mode 100644 index 000000000..0456282ef --- /dev/null +++ b/drivers/sbus/audio/dbri.c @@ -0,0 +1,715 @@ +/* + * drivers/sbus/audio/dbri.c + * + * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de) + * The SparcLinux interface was adopted from the CS4231 driver. + * + * This is the lowlevel driver for the DBRI & MMCODEC duo used for ISDN & AUDIO + * on Sun SPARCstation 10, 20, LX and Voyager models. + * NOTE: This driver only supports audio for now, there is NO SUPPORT for ISDN. + * + * - 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. + * - 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/malloc.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/sbus.h> + +#include "audio.h" +#include "dbri.h" + + + +#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) + +static int dbri_debug = D_GEN|D_INT|D_CMD|D_MM|D_USR; +static char *cmds[] = { + "WAIT", "PAUSE", "JUMP", "IIQ", "REX", "SDP", "CDP", "DTS", + "SSP", "CHI", "NT", "TE", "CDEC", "TEST", "CDM", "RESRV" +}; + +/* Bit hunting */ +#define dumpcmd {int i; for(i=0; i<n; i++) printk("DBRI: %x\n", dbri->cmd[i]); } + +#else + +#define dprintk(a, x) +#define dumpcmd + +#endif /* DBRI_DEBUG */ + + + +#define MAX_DRIVERS 2 /* Increase this if need more than 2 DBRI's */ + +#define WAIT_INTR1 0xbe +#define WAIT_INTR2 0xbf + +static struct sparcaudio_driver drivers[MAX_DRIVERS]; +static char drv_name[] = "DBRI/audio"; +static int num_drivers; +static int dbri_cmdlocked = 0; + + +/* + * Make sure, that we can send a command to the dbri + */ +static int dbri_cmdlock(struct dbri *dbri) +{ + unsigned long flags; + int was_sleeping = 0; + + save_flags(flags); + cli(); + + if(dbri_cmdlocked) { + interruptible_sleep_on(&dbri->wait); + was_sleeping = 1; + } + if(dbri_cmdlocked) + return -EINTR; + dbri_cmdlocked = 1; + + restore_flags(flags); + + if(was_sleeping) + dprintk(D_INT, ("DBRI: Just woke up\n")); + return 0; +} + +static void dummy() +{ +} + +static struct sparcaudio_operations dbri_ops = { + dummy, /* dbri_open, */ + dummy, /* dbri_release, */ + dummy, /* dbri_ioctl, */ + dummy, /* dbri_start_output, */ + dummy, /* dbri_stop_output, */ + dummy, /* dbri_start_input, */ + dummy, /* dbri_stop_input, */ + dummy, /* dbri_audio_getdev, */ +}; + +static void dbri_reset(struct sparcaudio_driver *drv) +{ + struct dbri *dbri = (struct dbri *)drv->private; + int i; + + dprintk(D_GEN, ("DBRI: reset 0:%x 2:%x 8:%x 9:%x\n", + dbri->regs->reg0, dbri->regs->reg2, + dbri->regs->reg8, dbri->regs->reg9)); + + dbri->regs->reg0 = D_R; /* Soft Reset */ + for(i = 0; (dbri->regs->reg0 & D_R) && i < 10; i++) + udelay(10); +} + +static void dbri_detach(struct sparcaudio_driver *drv) +{ + struct dbri *info = (struct dbri *)drv->private; + + dbri_reset(drv); + unregister_sparcaudio_driver(drv); + free_irq(info->irq, drv); + sparc_free_io(info->regs, info->regs_size); + kfree(drv->private); +} + + +static void dbri_init(struct sparcaudio_driver *drv) +{ + struct dbri *dbri = (struct dbri *)drv->private; + int n; + + dbri_reset(drv); + dbri->wait = NULL; + + dprintk(D_GEN, ("DBRI: init: cmd: %x, int: %x\n", + (int)dbri->cmd, (int)dbri->intr)); + + /* + * Initialize the interrupt ringbuffer. + */ + for(n = 0; n < DBRI_NO_INTS-1; n++) + dbri->intr[n * DBRI_INT_BLK] = + (int)(&dbri->intr[(n+1)*DBRI_INT_BLK]); + dbri->intr[n * DBRI_INT_BLK] = (int)(dbri->intr); + dbri->dbri_irqp = 1; + + dbri->regs->reg0 |= (D_G|D_S|D_E); + + /* + * Set up the interrupt queue + */ + (void)dbri_cmdlock(dbri); + + n = 0; + dbri->cmd[n++] = DBRI_CMD(D_IIQ, 0, 0); + dbri->cmd[n++] = (int)(dbri->intr); + dbri->cmd[n++] = DBRI_CMD(D_WAIT, 1, WAIT_INTR1); + dbri->regs->reg8 = (int)dbri->cmd; +} + + + +/* + * Short data pipes transmit LSB first. The CS4215 receives MSB first. Grrr. + * So we have to reverse the bits. Note: only 1, 2 or 4 bytes are supported. + */ +static __u32 reverse_bytes(__u32 b, int len) +{ + switch(len) { + case 4: b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16); + case 2: b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8); + case 1: b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4); + b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2); + b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1); + } + return b; +} + + + +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; + 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, 1 CHI device (64bit), clock 1 + * 3: Tests disabled + */ + mm->ctrl[0] = CS4215_RSRVD_1; + mm->ctrl[1] = CS4215_DFR_ULAW | CS4215_FREQ[0].csval; + mm->ctrl[2] = CS4215_XEN | CS4215_XCLK | + CS4215_BSEL_128 | CS4215_FREQ[0].xtal; + mm->ctrl[3] = 0; +} + +static void mmcodec_init_data(struct dbri *dbri) +{ + int val, n = 0; + + dbri_cmdlock(dbri); + + /* + * Data mode: + * Pipe 4: Send timeslots 1-4 (audio data) + * Pipe 17: Send timeslots 5-8 (part of ctrl data) + * Pipe 6: Receive timeslots 1-4 (audio data) + * Pipe 19: 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). + */ + + /* Transmit & Receive Memory setup */ + dbri->mm.td.flags = DBRI_TD_F|DBRI_TD_D|DBRI_TD_CNT(0); + dbri->mm.td.ba = 0; + dbri->mm.td.nda = (__u32)&dbri->mm.td; + dbri->mm.td.status = 0; + + dbri->mm.td.flags = DBRI_RD_BCNT(0); + dbri->mm.td.ba = 0; + dbri->mm.td.nda = (__u32)&dbri->mm.rd; + dbri->mm.td.status = 0; + + /* Pipe 4: SDP + DTS */ + val = D_SDP_MEM|D_SDP_TO_SER|D_SDP_C|D_SDP_MSB|D_PIPE(D_P_4); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = (__u32)&dbri->mm.td; + + val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(D_P_16) | D_PIPE(D_P_4); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = 0; + dbri->cmd[n++] = D_TS_LEN(8) | D_TS_CYCLE(0)| D_TS_NEXT(D_P_16); + + + /* Pipe 17: SDP + DTS + SSP */ + val = D_SDP_FIXED|D_SDP_TO_SER|D_SDP_C|D_PIPE(D_P_17); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = 0; /* Fixed data */ + + val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(D_P_4) | D_PIPE(D_P_17); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = 0; + dbri->cmd[n++] = D_TS_LEN(32) | D_TS_CYCLE(32) | D_TS_NONCONTIG | + D_TS_MON(D_P_4) | D_TS_NEXT(D_P_16); + + dbri->cmd[n++] = DBRI_CMD(D_SSP, 0, D_PIPE(D_P_17)); + dbri->cmd[n++] = reverse_bytes(*(int *)dbri->mm.data, 4); + + + /* Pipe 6: SDP + DTS */ + val=D_SDP_MEM|D_SDP_FROM_SER|D_SDP_C|D_SDP_MSB|D_PIPE(D_P_6); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = (__u32)&dbri->mm.rd; + + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(D_P_16) | D_PIPE(D_P_6); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = D_TS_LEN(8) | D_TS_CYCLE(0)| D_TS_NEXT(D_P_16); + dbri->cmd[n++] = 0; + + + /* Pipe 19: SDP + DTS */ + val = D_SDP_FIXED|D_SDP_FROM_SER|D_SDP_P|D_SDP_C|D_PIPE(D_P_19); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = 0; /* Fixed data */ + + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(D_P_6) | D_PIPE(D_P_19); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = D_TS_LEN(16) | D_TS_CYCLE(40) | D_TS_NONCONTIG | + D_TS_MON(D_P_6) | D_TS_NEXT(D_P_16); + dbri->cmd[n++] = 0; + + dbri->cmd[n++] = DBRI_CMD(D_WAIT, 0, WAIT_INTR1); + + dbri->regs->reg8 = (int)dbri->cmd; +} + + +/* + * Send the control information (i.e. audio format) + */ +static void mmcodec_setctrl(struct dbri *dbri) +{ + int n = 0, val; + + /* + * Enable Command mode: Set PIO3 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); + dbri->regs->reg2 = val; + udelay(34); + + dbri_cmdlock(dbri); + + /* + * 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). + */ + + /* Set CHI Anchor: Pipe 16. This should take care of the rest. */ + val = D_DTS_VI | D_DTS_VO | D_DTS_INS | + D_DTS_PRVIN(D_P_16) | D_DTS_PRVOUT(D_P_16) | D_PIPE(D_P_16); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = D_TS_ANCHOR | D_TS_NEXT(D_P_16); + dbri->cmd[n++] = D_TS_ANCHOR | D_TS_NEXT(D_P_16); + + + /* Setup the pipes first */ + val = D_SDP_FIXED|D_SDP_TO_SER|D_SDP_P|D_SDP_C|D_PIPE(D_P_17); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = 0; + + val = D_SDP_FIXED|D_SDP_CHANGE|D_SDP_P|D_SDP_C|D_PIPE(D_P_18); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = 0; + + val = D_SDP_FIXED|D_SDP_CHANGE|D_SDP_P|D_SDP_C|D_PIPE(D_P_19); + dbri->cmd[n++] = DBRI_CMD(D_SDP, 0, val); + dbri->cmd[n++] = 0; + + /* Fill in the data to send */ + dbri->mm.ctrl[0] &= ~CS4215_CLB; + dbri->cmd[n++] = DBRI_CMD(D_SSP, 0, D_PIPE(D_P_17)); + dbri->cmd[n++] = reverse_bytes(*(int *)dbri->mm.ctrl, 4); + + dbri->cmd[n++] = DBRI_CMD(D_PAUSE, 0, 0); + + + + /* Link the timeslots */ + val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(D_P_16) | D_PIPE(D_P_17); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = 0; + dbri->cmd[n++] = D_TS_LEN(32) | D_TS_CYCLE(256) | D_TS_NEXT(D_P_16); + + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(D_P_16) | D_PIPE(D_P_18); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = D_TS_LEN(16) | D_TS_CYCLE(0) | D_TS_NEXT(D_P_16); + dbri->cmd[n++] = 0; + + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(D_P_18) | D_PIPE(D_P_19); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = D_TS_LEN(8) | D_TS_CYCLE(48) | D_TS_NEXT(D_P_16); + /* + * According to the manual we should also specify + * D_TS_NONCONTIG | D_TS_MON(D_P_18), but the machine freezes + * if we do that. Can somebody explain me why? + */ + dbri->cmd[n++] = 0; + + + /* Setup DBRI for CHI Master */ + dbri->cmd[n++] = DBRI_CMD(D_CDM, 0, D_CDM_XCE|D_CDM_REN); + dbri->cmd[n++] = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(6) | D_CHI_FD | + D_CHI_IR | D_CHI_EN); + dbri->cmd[n++] = DBRI_CMD(D_PAUSE, 0, 0); + dbri->cmd[n++] = DBRI_CMD(D_CDM, 0, D_CDM_XCE|D_CDM_XEN|D_CDM_REN); + + dbri->cmd[n++] = DBRI_CMD(D_WAIT, 0, 0); + dbri->regs->reg8 = (int)dbri->cmd; + + /* Wait for the data from the CS4215 */ + interruptible_sleep_on(&dbri->int_wait); +printk("Woke up (1) reg2: %x\n", dbri->regs->reg2); + + + /* Now switch back to data mode */ + n = 0; + /* CHI Anchor: Stop Send/Receive */ + val = D_DTS_VI | D_DTS_VO | D_DTS_INS | + D_DTS_PRVIN(D_P_16) | D_DTS_PRVOUT(D_P_16) | D_PIPE(D_P_16); + dbri->cmd[n++] = DBRI_CMD(D_DTS, 0, val); + dbri->cmd[n++] = D_TS_ANCHOR | D_TS_NEXT(D_P_16); + dbri->cmd[n++] = D_TS_ANCHOR | D_TS_NEXT(D_P_16); + + dbri->cmd[n++] = DBRI_CMD(D_WAIT, 0, 0x17); + dbri->regs->reg8 = (int)dbri->cmd; + +#if 0 + dbri->mm.ctrl[0] |= CS4215_CLB; + dbri->cmd[n++] = DBRI_CMD(D_SSP, 1, D_PIPE(D_P_17)); + dbri->cmd[n++] = reverse_bytes(*(int *)dbri->mm.ctrl, 4); + + /* Setup DBRI for CHI Slave */ + dbri->cmd[n++] = DBRI_CMD(D_CDM, 1, D_CDM_XCE|D_CDM_REN); + dbri->cmd[n++] = DBRI_CMD(D_CHI, 1, D_CHI_CHICM(1) | D_CHI_FD | + D_CHI_IR | D_CHI_EN); + dbri->cmd[n++] = DBRI_CMD(D_PAUSE, 1, 0x16); + dbri->cmd[n++] = DBRI_CMD(D_CDM, 0, D_CDM_XCE|D_CDM_XEN|D_CDM_REN); + + dbri->cmd[n++] = DBRI_CMD(D_WAIT, 1, 0x17); + dbri->regs->reg8 = (int)dbri->cmd; + + dbri->regs->reg2 = D_ENPIO | D_PIO3 | + (dbri->mm.onboard ? D_PIO0 : D_PIO2); +#endif + + /* We are ready */ + dbri_cmdlocked = 0; + wake_up(&dbri->wait); +} + +static int mmcodec_init(struct sparcaudio_driver *drv) +{ + struct dbri *dbri = (struct dbri *)drv->private; + int reg2 = 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"); + dbri->regs->reg2 = D_ENPIO2; + dbri->mm.onboard = 0; + } + if( !(reg2 & (D_PIO0|D_PIO2)) ) { + printk("DBRI: no mmcodec found.\n"); + return -EIO; + } + + + /* Now talk to our baby */ + dbri->regs->reg0 |= D_C; /* Enable CHI */ + + mmcodec_default(&dbri->mm); + + dbri->mm.version = 0xff; + mmcodec_setctrl(dbri); + if(dbri->mm.version == 0xff) + return -EIO; + + /* + mmcodec_init_data(dbri, &n); + */ + + return 0; +} + +void dbri_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct sparcaudio_driver *drv = (struct sparcaudio_driver *)dev_id; + struct dbri *dbri = (struct dbri *)drv->private; + int x, val; + static int numint = 0; + + /* + * Read it, so the interrupt goes away. + */ + x = dbri->regs->reg1; + if(numint++ > 20) { + dbri->regs->reg0 = D_R; /* Soft Reset */ + numint = 0; + printk("Soft reset\n"); + } + + if ( x & (D_MRR|D_MLE|D_LBG|D_MBE) ) { + /* + * What should I do here ? + */ + 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"); + } + + if (!(x & D_IR)) /* Not for us */ + return; + + x = dbri->intr[dbri->dbri_irqp]; + while (x != 0) { + dbri->intr[dbri->dbri_irqp] = 0; + + if(D_INTR_GETCHAN(x) == D_INTR_CMD) { + dprintk(D_INT,("DBRI: INTR: Command: %-5s Value:%d\n", + cmds[D_INTR_GETCMD(x)], D_INTR_GETVAL(x))); + } else { + dprintk(D_INT,("DBRI: INTR: Chan:%d Code:%d Val:%#x\n", + D_INTR_GETCHAN(x), D_INTR_GETCODE(x), + D_INTR_GETRVAL(x))); + } + + val = D_INTR_GETVAL(x); + + switch(D_INTR_GETCHAN(x)) { + case D_INTR_CMD: + if(D_INTR_GETCMD(x) == D_WAIT) + if(val == WAIT_INTR1) { + dbri_cmdlocked = 0; + wake_up(&dbri->wait); + } + if(val == WAIT_INTR2) + wake_up(&dbri->int_wait); + break; + case D_P_18: + if(val != 0) { + x = reverse_bytes(val,2)&CS4215_12_MASK; +printk("Comparing int: %x with hi(%x)\n", x, *(int *)dbri->mm.ctrl); + if(x == (*(int *)dbri->mm.ctrl >> 16)) +{ +printk("Comp ok\n"); + wake_up(&dbri->int_wait); +} + } + break; + case D_P_19: + if(val != 0) { + dbri->mm.version = + reverse_bytes(val, 1) & 0xf; + } + break; + } + + 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++; + x = dbri->intr[dbri->dbri_irqp]; + } +} + + + + + +static int dbri_attach(struct sparcaudio_driver *drv, + struct linux_sbus_device *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) + return -ENOMEM; + dbri = (struct dbri *)drv->private; + + memset(dbri, 0, sizeof(*dbri)); + + dbri->dbri_version = sdev->prom_name[9]; + + /* Map the registers into memory. */ + prom_apply_sbus_ranges(sdev->my_bus, &sdev->reg_addrs[0], + sdev->num_registers, sdev); + dbri->regs_size = sdev->reg_addrs[0].reg_size; + dbri->regs = sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0, + sdev->reg_addrs[0].reg_size, + drv_name, sdev->reg_addrs[0].which_io, 0); + if (!dbri->regs) { + printk(KERN_ERR "DBRI: could not allocate registers\n"); + 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", drv); + if (err) { + printk(KERN_ERR "DBRI: Can't get irq %d\n", dbri->irq); + sparc_free_io(dbri->regs, dbri->regs_size); + kfree(drv->private); + return err; + } + + /* Register ourselves with the midlevel audio driver. */ + err = register_sparcaudio_driver(drv); + if (err) { + printk(KERN_ERR "DBRI: unable to register audio\n"); + free_irq(dbri->irq, drv); + sparc_free_io(dbri->regs, dbri->regs_size); + kfree(drv->private); + return err; + } + + dbri_init(drv); + err = mmcodec_init(drv); + if(err) { + dbri_detach(drv); + 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, (unsigned long)dbri->regs, + dbri->irq, dbri->dbri_version, dbri->mm.version); + + return 0; +} + +/* Probe for the dbri chip and then attach the driver. */ +#ifdef MODULE +int init_module(void) +#else +__initfunc(int dbri_init(void)) +#endif +{ + struct linux_sbus *bus; + struct linux_sbus_device *sdev; + + num_drivers = 0; + + /* Probe each SBUS for the DBRI chip(s). */ + for_all_sbusdev(sdev,bus) { + /* + * 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; +} + +#ifdef MODULE +void cleanup_module(void) +{ + register int i; + + for (i = 0; i < num_drivers; i++) { + dbri_detach(&drivers[i]); + num_drivers--; + } +} +#endif diff --git a/drivers/sbus/audio/dbri.h b/drivers/sbus/audio/dbri.h new file mode 100644 index 000000000..098616ac8 --- /dev/null +++ b/drivers/sbus/audio/dbri.h @@ -0,0 +1,293 @@ +/* + * 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> + +struct dbri_regs { + __volatile__ __u32 reg0; /* Status & Control */ + __volatile__ __u32 reg1; /* Mode & Interrupt */ + __volatile__ __u32 reg2; /* Parallel IO */ + __volatile__ __u32 reg3; /* Test */ + __volatile__ __u32 unused[4]; + __volatile__ __u32 reg8; /* Command Queue Pointer */ + __volatile__ __u32 reg9; /* Interrupt Queue Pointer */ +}; + +#define DBRI_NO_CMDS 64 +#define DBRI_NO_INTS 2 +#define DBRI_INT_BLK 64 + +#define DBRI_MM_ONB 1 +#define DBRI_MM_SB 2 + +struct dbri_mem { + __u32 flags; + __u32 ba; /* Transmit/Receive Buffer Address */ + __u32 nda; /* Next Descriptor Address */ + __u32 status; +}; + +#include "cs4215.h" + +/* This structure holds the information for both chips (DBRI & CS4215) */ +struct dbri { + int regs_size, irq; /* Needed for unload */ + + struct dbri_regs *regs; /* dbri HW regs */ + int dbri_version; /* 'e' and up is OK */ + int dbri_irqp; /* intr queue pointer */ + __volatile__ int cmd[DBRI_NO_CMDS]; /* Place for commands */ + __volatile__ int intr[DBRI_NO_INTS * DBRI_INT_BLK]; /* Interrupt field */ + + struct cs4215 mm; /* mmcodec special info */ + + struct wait_queue *wait, *int_wait; /* Where to sleep if busy */ + struct audio_info perchip_info; +}; + + +/* 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 */ + +#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | (intr << 27) | value) + + +/* 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_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(v) (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) (1<<5) /* Remote Loopback */ +#define D_NT_LLB(v) (1<<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<<31) /* 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 */ + +/* 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_CNT(v) (v<<16) /* Number of valid bytes in the buffer */ +#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 recieved */ +#define DBRI_RD_ABT (1<<5) /* Abort: frame aborted */ +#define DBRI_RD_OVRN (1<<3) /* Overrun: data lost */ + +#endif /* _DBRI_H_ */ |