1618 lines
40 KiB
C
1618 lines
40 KiB
C
#ident "@(#) audio_79C30.c 1.1@(#) Copyright (c) 1989-92 Sun Microsystems, Inc."
|
|
|
|
/*
|
|
* AUDIO Chip driver - for AMD AM79C30A
|
|
*
|
|
* The heart of this driver is its ability to convert sound to a
|
|
* bit stream and back to sound. The chip itself supports lots of
|
|
* telephony functions, but the driver doesn't (yet).
|
|
*
|
|
* The basic facts:
|
|
* - The chip has a built in 8 bit DAC and ADC
|
|
* - When it is active, it interrupts every 125us,
|
|
* or 8000 times a second.
|
|
* - The digital representation is u-law by default.
|
|
* The high order bit is a sign bit, the low order seven bits
|
|
* encode amplitude, and the entire 8 bits are inverted.
|
|
* - The driver does not currently support A-law encoding.
|
|
* - There are some additional registers which let you do ISDN control
|
|
* and telephone style things like touch tone and ringers.
|
|
* - We use the Bb channel for both reading and writing.
|
|
*/
|
|
|
|
#include <sys/errno.h>
|
|
#include <sys/param.h>
|
|
#include <sys/user.h>
|
|
#include <sys/types.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/stream.h>
|
|
#include <sys/stropts.h>
|
|
#include <sys/file.h>
|
|
#include <sys/debug.h>
|
|
#include <machine/intreg.h>
|
|
#include <sundev/mbvar.h>
|
|
#include <machine/cpu.h>
|
|
|
|
#define AMD_CHIP /* include AUDIOGETREG/AUDIOSETREG defs */
|
|
|
|
/*
|
|
* Make the C version of the interrupt handler the default except on sun4c
|
|
* machines where the SPARC assembly version can be used
|
|
*/
|
|
#if !defined(sun4c)
|
|
#undef AMD_C_TRAP
|
|
#define AMD_C_TRAP /* use C interrupt routine */
|
|
#endif
|
|
|
|
#include <sun/audioio.h>
|
|
#include <sbusdev/audiovar.h>
|
|
#include <sbusdev/audio_79C30.h>
|
|
#if !defined(STANDALONE)
|
|
#include <sbusdev/aclone.h>
|
|
#endif
|
|
|
|
#include "audiodebug.h"
|
|
|
|
|
|
/* External routines */
|
|
extern addr_t map_regs();
|
|
extern void report_dev();
|
|
extern int splaudio();
|
|
|
|
/* Local routines */
|
|
static int amd_identify();
|
|
static int amd_attach();
|
|
static int amd_open();
|
|
static void amd_close();
|
|
static aud_return_t amd_ioctl();
|
|
static void amd_start();
|
|
static void amd_stop();
|
|
static unsigned int amd_setflag();
|
|
static aud_return_t amd_setinfo();
|
|
static void amd_queuecmd();
|
|
static void amd_flushcmd();
|
|
static int amd_intr4();
|
|
static void amd_devinit();
|
|
static unsigned int amd_outport();
|
|
static unsigned int amd_play_gain();
|
|
static unsigned int amd_gx_gain();
|
|
|
|
|
|
/* Record and monitor gain use the same lookup table */
|
|
#define amd_record_gain(chip, val) \
|
|
amd_gx_gain(chip, val, AUDIO_UNPACK_REG(AUDIO_MAP_GX))
|
|
#define amd_monitor_gain(chip, val) \
|
|
amd_gx_gain(chip, val, AUDIO_UNPACK_REG(AUDIO_MAP_STG))
|
|
|
|
/* Level 13 interrupt handler, either in C (below) or in amd_intr.s */
|
|
extern int amd_intr(/* */);
|
|
|
|
/* Local declarations */
|
|
amd_dev_t *amd_devices; /* device ctrlr array */
|
|
static unsigned int devcnt; /* number of devices found */
|
|
|
|
/* This is the size of the STREAMS buffers we send down the read side */
|
|
int amd_bsize = AUD_79C30_BSIZE;
|
|
|
|
|
|
/* Declare audio ops vector for AMD79C30 support routines */
|
|
static struct aud_ops amd_ops = {
|
|
amd_close,
|
|
amd_ioctl,
|
|
amd_start,
|
|
amd_stop,
|
|
amd_setflag,
|
|
amd_setinfo,
|
|
amd_queuecmd,
|
|
amd_flushcmd,
|
|
};
|
|
|
|
/*
|
|
* Declare ops vector for auto configuration.
|
|
* It must be named audioamd_ops, since the name is constructed from the
|
|
* device name when config writes ioconf.c.
|
|
*/
|
|
struct dev_ops audioamd_ops = {
|
|
1, /* revision */
|
|
amd_identify, /* identify routine */
|
|
amd_attach, /* attach routine */
|
|
};
|
|
|
|
/*
|
|
* Streams declarations
|
|
*/
|
|
|
|
static struct module_info amd_modinfo = {
|
|
0x6175, /* module ID number */
|
|
"audioamd", /* module name */
|
|
0, /* min packet size accepted */
|
|
INFPSZ, /* max packet size accepted */
|
|
AUD_79C30_HIWATER, /* hi-water mark */
|
|
AUD_79C30_LOWATER, /* lo-water mark */
|
|
};
|
|
|
|
/*
|
|
* Queue information structure for read queue
|
|
*/
|
|
static struct qinit amd_rinit = {
|
|
(int(*)())audio_rput, /* put procedure */
|
|
(int(*)())audio_rsrv, /* service procedure */
|
|
amd_open, /* called on startup */
|
|
(int(*)())audio_close, /* called on finish */
|
|
NULL, /* for 3bnet only */
|
|
&amd_modinfo, /* module information structure */
|
|
NULL, /* module statistics structure */
|
|
};
|
|
|
|
/*
|
|
* Queue information structure for write queue
|
|
*/
|
|
static struct qinit amd_winit = {
|
|
(int(*)())audio_wput, /* put procedure */
|
|
(int(*)())audio_wsrv, /* service procedure */
|
|
NULL, /* called on startup */
|
|
NULL, /* called on finish */
|
|
NULL, /* for 3bnet only */
|
|
&amd_modinfo, /* module information structure */
|
|
NULL, /* module statistics structure */
|
|
};
|
|
|
|
static char *amd_modules[] = {
|
|
0, /* no modules defined yet */
|
|
};
|
|
|
|
struct streamtab audio_79C30_info = {
|
|
&amd_rinit, /* qinit for read side */
|
|
&amd_winit, /* qinit for write side */
|
|
NULL, /* mux qinit for read */
|
|
NULL, /* mux qinit for write */
|
|
amd_modules, /* list of modules to be pushed */
|
|
};
|
|
|
|
|
|
/*
|
|
* Loadable module wrapper
|
|
*/
|
|
#if defined(AMD_LOADABLE) || defined(VDDRV)
|
|
#include <sys/conf.h>
|
|
#include <sun/autoconf.h>
|
|
#include <sun/vddrv.h>
|
|
|
|
static struct cdevsw amd_cdevsw = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, &audio_79C30_info, 0
|
|
};
|
|
|
|
static struct vdldrv amd_modldrv = {
|
|
VDMAGIC_DRV, /* Type of module */
|
|
"AMD79C30 audio driver", /* Descriptive name */
|
|
&audioamd_ops, /* Address of dev_ops */
|
|
NULL, /* bdevsw */
|
|
&amd_cdevsw, /* cdevsw */
|
|
0, /* Drv_blockmajor, 0 means let system choose */
|
|
0, /* Drv_charmajor, 0 means let system choose */
|
|
};
|
|
|
|
|
|
/*
|
|
* Called by modload/modunload.
|
|
*/
|
|
int
|
|
amd_init(function_code, vdp, vdi, vds)
|
|
uint function_code;
|
|
struct vddrv *vdp;
|
|
addr_t vdi;
|
|
struct vdstat *vds;
|
|
{
|
|
#ifdef lint
|
|
(void) amd_init(0, vdp, vdi, vds);
|
|
#endif
|
|
|
|
switch (function_code) {
|
|
case VDLOAD: /* Load module */
|
|
/* Set pointer to loadable driver structure */
|
|
vdp->vdd_vdtab = (struct vdlinkage *)&amd_modldrv;
|
|
|
|
return (0);
|
|
|
|
case VDUNLOAD: { /* Unload module */
|
|
amd_dev_t *amdp;
|
|
unsigned int i;
|
|
int s;
|
|
|
|
#if !defined(STANDALONE)
|
|
/* Unregister our aclone mappings */
|
|
aclone_unregister((caddr_t)&audio_79C30_info);
|
|
#endif
|
|
|
|
/* Cannot unload driver if any minor device is open */
|
|
s = splaudio();
|
|
for (i = 0; i < devcnt; i++) {
|
|
amdp = &(amd_devices[i]);
|
|
if (amdp->play.as.openflag ||
|
|
amdp->rec.as.openflag ||
|
|
amdp->control.as.openflag) {
|
|
(void) splx(s);
|
|
return (EBUSY);
|
|
}
|
|
}
|
|
(void) splx(s);
|
|
|
|
/*
|
|
* Remove device from interrupt queues.
|
|
* XXX - assumes a single device interrupt priority
|
|
*/
|
|
#if defined(AMD_C_TRAP)
|
|
(void) remintr(amdp->amd_dev->devi_intr->int_pri,
|
|
amd_intr);
|
|
#else /* !AMD_C_TRAP */
|
|
if (!settrap(amdp->amd_dev->devi_intr->int_pri, (int(*)())NULL))
|
|
panic("audio unload: Cannot reset trap vector");
|
|
#endif /* !AMD_C_TRAP */
|
|
(void) remintr(4, amd_intr4);
|
|
|
|
/*
|
|
* Deallocate structures allocated in attach
|
|
* routine
|
|
*/
|
|
for (i = 0; i < devcnt; i++) {
|
|
kmem_free(amd_devices[i].play.as.cmdlist.memory,
|
|
(u_int)amd_devices[i].play.as.cmdlist.size);
|
|
kmem_free(amd_devices[i].rec.as.cmdlist.memory,
|
|
(u_int)amd_devices[i].rec.as.cmdlist.size);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
case VDSTAT:
|
|
return (0);
|
|
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
#endif /* VDDRV */
|
|
|
|
/*
|
|
* Called by match_driver() and add_drv_layer() in autoconf.c
|
|
* Returns TRUE if the given string refers to this driver, else FALSE.
|
|
*/
|
|
|
|
static int
|
|
amd_identify(name)
|
|
char *name;
|
|
{
|
|
|
|
if (strcmp(name, "audio") == 0) {
|
|
devcnt++;
|
|
return (TRUE);
|
|
}
|
|
return (FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* Attach to the device.
|
|
*/
|
|
static int
|
|
amd_attach(dev)
|
|
struct dev_info *dev;
|
|
{
|
|
aud_stream_t *as, *play_as, *record_as;
|
|
aud_state_t *v;
|
|
amd_dev_t *amdv;
|
|
int i;
|
|
struct aud_cmd *pool;
|
|
#if !defined(STANDALONE)
|
|
dev_t amddev;
|
|
#endif
|
|
|
|
ATRACEINIT();
|
|
ATRACEPRINT("audio: debugging version of audio driver\n");
|
|
|
|
ASSERT(dev != NULL);
|
|
|
|
/*
|
|
* Each unit has a 'aud_state_t' that contains generic audio
|
|
* device state information. Also, each unit has a
|
|
* 'amd_dev_t' that contains device-specific data.
|
|
* Allocate storage for them here.
|
|
*/
|
|
if (amd_devices == NULL) {
|
|
amd_devices = ((amd_dev_t *)
|
|
new_kmem_zalloc((u_int)
|
|
(devcnt * sizeof (amd_dev_t)),
|
|
KMEM_SLEEP));
|
|
} else {
|
|
/* XXX - We only support one audio device at this time */
|
|
(void) printf("audioamd: cannot support multiple audio devices\n");
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Identify the audio device and assign a unit number. Get the
|
|
* address of this unit's audio device state structure.
|
|
*
|
|
* XXX - With the sun4c architecture, we know that exactly one
|
|
* device is there; we should probe anyway, to be safe.
|
|
*/
|
|
dev->devi_unit = 0;
|
|
|
|
v = (aud_state_t *)&amd_devices[dev->devi_unit];
|
|
ASSERT(v != NULL);
|
|
v->devdata = (void *)&amd_devices[dev->devi_unit];
|
|
|
|
amdv = (amd_dev_t *)v;
|
|
|
|
amdv->amd_dev = dev;
|
|
amdv->control.as.control_as = &amdv->control.as;
|
|
amdv->control.as.play_as = &amdv->play.as;
|
|
amdv->control.as.record_as = &amdv->rec.as;
|
|
amdv->play.as.control_as = &amdv->control.as;
|
|
amdv->play.as.play_as = &amdv->play.as;
|
|
amdv->play.as.record_as = &amdv->rec.as;
|
|
amdv->rec.as.control_as = &amdv->control.as;
|
|
amdv->rec.as.play_as = &amdv->play.as;
|
|
amdv->rec.as.record_as = &amdv->rec.as;
|
|
|
|
as = &amdv->control.as;
|
|
play_as = as->play_as;
|
|
record_as = as->record_as;
|
|
|
|
ASSERT(as != NULL);
|
|
ASSERT(play_as != NULL);
|
|
ASSERT(record_as != NULL);
|
|
|
|
/* Initialize the play info struct */
|
|
play_as->info.gain = AUD_79C30_DEFAULT_GAIN;
|
|
play_as->info.sample_rate = AUD_79C30_SAMPLERATE;
|
|
play_as->info.channels = AUD_79C30_CHANNELS;
|
|
play_as->info.precision = AUD_79C30_PRECISION;
|
|
play_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
play_as->info.minordev = AMD_MINOR_RW;
|
|
play_as->info.balance = AUDIO_MID_BALANCE;
|
|
|
|
/* Copy play struct to record struct and init ports */
|
|
record_as->info = play_as->info;
|
|
|
|
#if defined(CPU_SUN4M_690)
|
|
/*
|
|
* XXX - This only works when compiled on *a* sun4m machine because
|
|
* <machine/cpu.h> points to the correct sun4c/4m cpu.h. Ugh.
|
|
*/
|
|
if (cpu == CPU_SUN4M_690) {
|
|
/*
|
|
* XXX - The SPARCserver 600 series does not have a built-in
|
|
* speaker. Send the audio out the headphone jack by default.
|
|
*/
|
|
play_as->info.port = AUDIO_HEADPHONE;
|
|
} else {
|
|
play_as->info.port = AUDIO_SPEAKER;
|
|
}
|
|
#else
|
|
play_as->info.port = AUDIO_SPEAKER;
|
|
#endif /* CPU_SUN4M_690 */
|
|
|
|
play_as->info.avail_ports = AUDIO_SPEAKER;
|
|
|
|
record_as->info.port = AUDIO_MICROPHONE;
|
|
record_as->info.minordev = AMD_MINOR_RO;
|
|
record_as->info.avail_ports = AUDIO_MICROPHONE;
|
|
|
|
/* Control stream info */
|
|
as->info.minordev = AMD_MINOR_CTL;
|
|
|
|
/*
|
|
* Set types and modes
|
|
*/
|
|
play_as->type = AUDTYPE_DATA;
|
|
record_as->type = AUDTYPE_DATA;
|
|
as->type = AUDTYPE_CONTROL;
|
|
play_as->mode = AUDMODE_AUDIO;
|
|
record_as->mode = AUDMODE_AUDIO;
|
|
as->mode = AUDMODE_NONE;
|
|
|
|
/* Init the ops vector and back-pointers to the audio struct */
|
|
v->ops = &amd_ops;
|
|
as->play_as->v = v;
|
|
as->record_as->v = v;
|
|
as->control_as->v = v;
|
|
|
|
/*
|
|
* Initialize virtual chained DMA command block free lists.
|
|
* Reserve a couple of command blocks for record buffers.
|
|
* Then allocate the rest for play buffers.
|
|
*/
|
|
pool = (struct aud_cmd *)
|
|
new_kmem_zalloc(AUD_79C30_CMDPOOL * sizeof (struct aud_cmd),
|
|
KMEM_SLEEP);
|
|
ASSERT(pool != NULL);
|
|
|
|
for (i = 0; i < AUD_79C30_CMDPOOL; i++) {
|
|
struct aud_cmdlist *list;
|
|
|
|
list = (i < AUD_79C30_RECBUFS) ? &as->record_as->cmdlist
|
|
: &as->play_as->cmdlist;
|
|
pool->next = list->free;
|
|
list->free = pool++;
|
|
}
|
|
|
|
/*
|
|
* Map in the registers for this device. There is only one set
|
|
* of registers, so we quit if we see some different number.
|
|
*/
|
|
ASSERT(dev != NULL);
|
|
if (dev->devi_nreg == 1) {
|
|
CSR(as) = ((struct aud_79C30_chip *)
|
|
map_regs(dev->devi_reg->reg_addr,
|
|
dev->devi_reg->reg_size,
|
|
dev->devi_reg->reg_bustype));
|
|
ASSERT(CSR(as) != NULL);
|
|
} else {
|
|
(void) printf("audio%d: warning: bad register specification\n",
|
|
dev->devi_unit);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Add the interrupt for this device, so that we get interrupts
|
|
* when they occur. We only expect one hard interrupt address.
|
|
* Set up the level 4 soft interrupt service routine, called when
|
|
* there is real work to do.
|
|
*/
|
|
if (dev->devi_nintr == 1) {
|
|
#if defined(AMD_C_TRAP)
|
|
addintr(dev->devi_intr->int_pri, amd_intr,
|
|
dev->devi_name, dev->devi_unit);
|
|
#else /* !AMD_C_TRAP */
|
|
if (!settrap(dev->devi_intr->int_pri, amd_intr))
|
|
panic("amd: Cannot set level 13 trap vector");
|
|
#endif /* !AMD_C_TRAP */
|
|
addintr(4, amd_intr4, dev->devi_name, dev->devi_unit);
|
|
} else {
|
|
(void) printf("audio%d: warning: bad interrupt specification\n",
|
|
dev->devi_unit);
|
|
return (-1);
|
|
}
|
|
|
|
/* Initialize the audio chip */
|
|
amd_devinit(as);
|
|
|
|
report_dev(dev);
|
|
|
|
#if !defined(STANDALONE)
|
|
/* Register with audioclone device */
|
|
amddev = aclone_register((caddr_t)&audio_79C30_info,
|
|
0, /* slot 0 is reserved for amd */
|
|
10 + AMD_MINOR_RW, /* audio */
|
|
10 + AMD_MINOR_RO, /* audioro */
|
|
10 + AMD_MINOR_CTL); /* audioctl */
|
|
|
|
if (amddev < 0)
|
|
(void) printf("audioamd: could not register with aclone!\n");
|
|
#endif
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/* Device open routine: set device structure ptr and call generic routine */
|
|
/*ARGSUSED*/
|
|
static int
|
|
amd_open(q, dev, flag, sflag)
|
|
queue_t *q;
|
|
dev_t dev;
|
|
int flag;
|
|
int sflag;
|
|
{
|
|
aud_stream_t *as = NULL;
|
|
amd_dev_t *amdp;
|
|
int minornum = minor(dev);
|
|
|
|
/*
|
|
* Necessary for use with audioclone. XXX
|
|
*/
|
|
if (minornum == AMD_MINOR_CTL_OLD) {
|
|
printf("amd_open: old control device minor number %d has changed to %d\n",
|
|
AMD_MINOR_CTL_OLD, AMD_MINOR_CTL);
|
|
u.u_error = ENODEV;
|
|
return (OPENFAIL);
|
|
}
|
|
/*
|
|
* If this is an open from audioclone, normalize both dev
|
|
* and minornum; see call to aclone_register to find out about "10"
|
|
*/
|
|
if (minornum >= 10) {
|
|
minornum -= 10; /* XXX DIC */
|
|
dev = makedev(major(dev), minornum);
|
|
}
|
|
|
|
/*
|
|
* Handle clone device opens
|
|
*/
|
|
if (sflag == CLONEOPEN) {
|
|
switch (minornum) {
|
|
case 0: /* Normal clone open */
|
|
case AMD_MINOR_RW: /* Audio clone open */
|
|
if (flag & FWRITE) {
|
|
dev = makedev(major(dev), AMD_MINOR_RW);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case AMD_MINOR_RO: /* Audio clone open */
|
|
dev = makedev(major(dev), AMD_MINOR_RO);
|
|
break;
|
|
|
|
default:
|
|
u.u_error = ENODEV;
|
|
return (OPENFAIL);
|
|
}
|
|
} else if (minornum == 0) {
|
|
/*
|
|
* Because the system temporarily uses the streamhead
|
|
* corresponding to major,0 during a clone open, and because
|
|
* audio_open() can sleep, audio drivers are not allowed
|
|
* to use major,0 as a valid device number.
|
|
*
|
|
* A sleeping clone open and a non-clone use of maj,0
|
|
* can mess up the reference counts on vnodes/snodes
|
|
*/
|
|
u.u_error = ENODEV;
|
|
return (OPENFAIL);
|
|
}
|
|
|
|
minornum = minor(dev);
|
|
|
|
amdp = &amd_devices[0]; /* XXX - one device */
|
|
if (minornum == amdp->play.as.info.minordev)
|
|
as = &amdp->play.as;
|
|
else if (minornum == amdp->rec.as.info.minordev)
|
|
as = &amdp->rec.as;
|
|
else if (minornum == amdp->control.as.info.minordev)
|
|
as = &amdp->control.as;
|
|
|
|
if (as == NULL) {
|
|
u.u_error = ENODEV;
|
|
return (OPENFAIL);
|
|
}
|
|
|
|
/* Set input buffer size now, in case the value was patched */
|
|
as->record_as->input_size = amd_bsize;
|
|
|
|
if (ISDATASTREAM(as) && ((flag & (FREAD|FWRITE)) == FREAD))
|
|
as = as->record_as;
|
|
|
|
if (audio_open(as, q, flag, sflag) == OPENFAIL)
|
|
return (OPENFAIL);
|
|
|
|
if (ISDATASTREAM(as) && (flag & FREAD))
|
|
audio_process_record(as);
|
|
|
|
return (minornum);
|
|
}
|
|
|
|
|
|
static void
|
|
amd_close(as)
|
|
aud_stream_t *as;
|
|
{
|
|
amd_dev_t *devp;
|
|
struct aud_79C30_chip *chip;
|
|
|
|
ASSERT(as != NULL);
|
|
devp = DEVP(as);
|
|
ASSERT(devp != NULL);
|
|
chip = devp->chip;
|
|
ASSERT(chip != NULL);
|
|
|
|
/* Reset status bits. The device will already have been stopped. */
|
|
if (as == as->play_as) {
|
|
devp->play.samples = 0;
|
|
devp->play.error = FALSE;
|
|
} else {
|
|
devp->rec.samples = 0;
|
|
devp->rec.error = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Reset to u-Law on last close
|
|
*/
|
|
if ((as == as->play_as && as->record_as->readq == NULL) ||
|
|
(as == as->record_as && as->play_as->readq == NULL)) {
|
|
as->play_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
as->record_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR1);
|
|
chip->dr = (AUDIO_MMR1_BITS_u_LAW |
|
|
AUDIO_MMR1_BITS_LOAD_GR |
|
|
AUDIO_MMR1_BITS_LOAD_GER |
|
|
AUDIO_MMR1_BITS_LOAD_GX |
|
|
AUDIO_MMR1_BITS_LOAD_STG);
|
|
}
|
|
|
|
#if defined(AMD_CHIP)
|
|
/* If a user process mucked up the device, reset it when fully closed */
|
|
if (devp->setreg_used &&
|
|
!as->play_as->info.open &&
|
|
!as->record_as->info.open) {
|
|
amd_devinit(as);
|
|
devp->setreg_used = FALSE;
|
|
}
|
|
#endif /* AMD_CHIP */
|
|
|
|
ATRACE(amd_close, 'clos', as);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process ioctls not already handled by the generic audio handler.
|
|
*
|
|
* If AMD_CHIP is defined, we support ioctls that allow user processes
|
|
* to muck about with the device registers.
|
|
*/
|
|
static aud_return_t
|
|
amd_ioctl(as, mp, iocp)
|
|
aud_stream_t *as;
|
|
mblk_t *mp;
|
|
struct iocblk *iocp;
|
|
{
|
|
aud_return_t change;
|
|
int s;
|
|
|
|
change = AUDRETURN_NOCHANGE; /* detect device state change */
|
|
|
|
switch (iocp->ioc_cmd) {
|
|
|
|
case AUDIO_GETDEV:
|
|
{
|
|
int *ip;
|
|
|
|
ip = ((int *)mp->b_cont->b_rptr);
|
|
mp->b_cont->b_wptr += sizeof (int);
|
|
*ip = AUDIO_DEV_AMD;
|
|
iocp->ioc_count = sizeof (int);
|
|
}
|
|
break;
|
|
|
|
#if defined(AMD_CHIP)
|
|
/*
|
|
* The following chip manipulation functions are provided for
|
|
* backward-compatibiliity and ISDN control prototyping.
|
|
* They may be removed in a future release.
|
|
*
|
|
* The audio_ioctl structure contains the register load information:
|
|
* register (one byte)
|
|
* length (one byte)
|
|
* data (0-46 bytes)
|
|
* However, to make the programming interface simpler, there are
|
|
* defines that combine the register and length into a short.
|
|
* See <sbusdev/amd.h> for the gory details.
|
|
*/
|
|
case AUDIOGETREG: {
|
|
/* Read a register from the audio chip */
|
|
int i;
|
|
struct audio_ioctl *aioctl;
|
|
|
|
/* Disallow these ioctls on control streams */
|
|
if (as == as->control_as)
|
|
goto einval;
|
|
|
|
/* The input struct designates the register to be read */
|
|
aioctl = (struct audio_ioctl *)mp->b_cont->b_rptr;
|
|
|
|
s = splaudio();
|
|
CSR(as)->cr = AUDIO_UNPACK_REG(aioctl->control);
|
|
for (i = 0; i < AUDIO_UNPACK_LENGTH(aioctl->control); i++)
|
|
aioctl->data[i] = CSR(as)->dr;
|
|
(void) splx(s);
|
|
|
|
iocp->ioc_count = (char *)&aioctl->data[i] - (char *)aioctl;
|
|
mp->b_cont->b_wptr += iocp->ioc_count;
|
|
break;
|
|
}
|
|
|
|
case AUDIOSETREG: {
|
|
/* Write data to an audio chip register */
|
|
int i;
|
|
struct audio_ioctl *aioctl;
|
|
|
|
/* Disallow these ioctls on control streams */
|
|
if (as == as->control_as)
|
|
goto einval;
|
|
|
|
aioctl = (struct audio_ioctl *)mp->b_cont->b_rptr;
|
|
|
|
s = splaudio();
|
|
CSR(as)->cr = AUDIO_UNPACK_REG(aioctl->control);
|
|
for (i = 0; i < AUDIO_UNPACK_LENGTH(aioctl->control); i++)
|
|
CSR(as)->dr = aioctl->data[i];
|
|
(void) splx(s);
|
|
|
|
/* Note a status change and set flag to reset on close() */
|
|
DEVP(as)->setreg_used = TRUE;
|
|
change = AUDRETURN_CHANGE;
|
|
break;
|
|
}
|
|
#endif /* AMD_CHIP */
|
|
|
|
default:
|
|
einval:
|
|
iocp->ioc_error = EINVAL;
|
|
break;
|
|
}
|
|
return (change);
|
|
}
|
|
|
|
|
|
/*
|
|
* The next routine is used to start reads or writes.
|
|
* If there is a change of state, enable the chip.
|
|
* If there was already i/o active in the desired direction,
|
|
* or if i/o is paused, don't bother enabling the chip.
|
|
*/
|
|
static void
|
|
amd_start(as)
|
|
aud_stream_t *as;
|
|
{
|
|
amd_stream_t *cp;
|
|
int idle;
|
|
int pause;
|
|
|
|
ASSERT(as != NULL);
|
|
if (as == as->play_as) {
|
|
cp = &DEVP(as)->play;
|
|
idle = !(DEVP(as)->rec.active);
|
|
pause = as->play_as->info.pause;
|
|
} else {
|
|
cp = &DEVP(as)->rec;
|
|
idle = !(DEVP(as)->play.active);
|
|
pause = as->record_as->info.pause;
|
|
}
|
|
ASSERT(cp != NULL);
|
|
|
|
/* If already active, paused, or nothing queued to the device, done */
|
|
if (cp->active || pause || (cp->cmdptr == NULL))
|
|
return;
|
|
|
|
cp->active = TRUE;
|
|
|
|
if (idle) {
|
|
ATRACE(amd_start,
|
|
(as == as->play_as ? ATR_STARTPLAY : ATR_STARTREC),
|
|
__LINE__);
|
|
|
|
CSR(as)->cr = AUDIO_UNPACK_REG(AUDIO_INIT_INIT);
|
|
CSR(as)->dr = AUDIO_INIT_BITS_ACTIVE;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The next routine is used to stop reads or writes. All we do is turn
|
|
* off the active bit. If there is currently no i/o active in the other
|
|
* direction, then the interrupt routine will disable the chip.
|
|
*/
|
|
static void
|
|
amd_stop(as)
|
|
aud_stream_t *as;
|
|
{
|
|
int s;
|
|
|
|
ASSERT(as != NULL);
|
|
|
|
s = splaudio(); /* read/reset error flag atomically */
|
|
if (as == as->play_as)
|
|
DEVP(as)->play.active = FALSE;
|
|
else
|
|
DEVP(as)->rec.active = FALSE;
|
|
|
|
ATRACE(amd_stop, (as == as->play_as ? ATR_STOPPLAY : ATR_STOPREC),
|
|
__LINE__);
|
|
|
|
(void) splx(s);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get or set a particular flag value */
|
|
static unsigned int
|
|
amd_setflag(as, op, val)
|
|
aud_stream_t *as;
|
|
enum aud_opflag op;
|
|
unsigned int val;
|
|
{
|
|
amd_stream_t *cp;
|
|
|
|
ASSERT(as != NULL);
|
|
cp = (as == as->play_as) ? &DEVP(as)->play : &DEVP(as)->rec;
|
|
ASSERT(cp != NULL);
|
|
|
|
switch (op) {
|
|
case AUD_ERRORRESET: {
|
|
int s;
|
|
|
|
s = splaudio(); /* read/reset error flag atomically */
|
|
val = cp->error;
|
|
cp->error = FALSE;
|
|
(void) splx(s);
|
|
break;
|
|
}
|
|
|
|
/* GET only */
|
|
case AUD_ACTIVE:
|
|
val = cp->active;
|
|
break;
|
|
}
|
|
return (val);
|
|
}
|
|
|
|
|
|
/* Get or set device-specific information in the audio state structure */
|
|
static aud_return_t
|
|
amd_setinfo(as, mp, iocp)
|
|
aud_stream_t *as;
|
|
mblk_t *mp;
|
|
struct iocblk *iocp;
|
|
{
|
|
aud_stream_t *play_as = as->play_as;
|
|
aud_stream_t *record_as = as->record_as;
|
|
amd_dev_t *devp;
|
|
struct aud_79C30_chip *chip;
|
|
audio_info_t *ip;
|
|
unsigned int sample_rate, channels, precision, encoding;
|
|
int error = 0;
|
|
int s;
|
|
|
|
ASSERT(as != NULL);
|
|
devp = DEVP(as);
|
|
ASSERT(devp != NULL);
|
|
|
|
/* Set device-specific info into device-independent structure */
|
|
play_as->info.samples = devp->play.samples;
|
|
record_as->info.samples = devp->rec.samples;
|
|
play_as->info.active = devp->play.active;
|
|
record_as->info.active = devp->rec.active;
|
|
|
|
/* If getinfo, 'mp' is NULL...we're done */
|
|
if (mp == NULL)
|
|
return (AUDRETURN_NOCHANGE);
|
|
|
|
chip = devp->chip;
|
|
ASSERT(chip != NULL);
|
|
|
|
s = splaudio(); /* load chip registers atomically */
|
|
ip = (audio_info_t *)mp->b_cont->b_rptr;
|
|
|
|
/*
|
|
* If any new value matches the current value, there
|
|
* should be no need to set it again here.
|
|
* However, it's work to detect this so don't bother.
|
|
*
|
|
* Also, if AMD_CHIP is defined, apps may have set things via
|
|
* AUDIOSETREG; it is convenient to allow GETLEVEL/SETLEVEL to
|
|
* reset the gains & outport to what the driver thinks they are.
|
|
*
|
|
* We must return an error if apps try and change the encoding
|
|
* on the control device
|
|
*/
|
|
if (Modify(ip->play.gain))
|
|
play_as->info.gain = amd_play_gain(chip, ip->play.gain);
|
|
|
|
if (Modify(ip->record.gain))
|
|
record_as->info.gain = amd_record_gain(chip, ip->record.gain);
|
|
|
|
if (Modify(ip->monitor_gain)) {
|
|
devp->hwi_state.monitor_gain =
|
|
amd_monitor_gain(chip, ip->monitor_gain);
|
|
}
|
|
|
|
|
|
if (Modify(ip->play.port)) {
|
|
switch (ip->play.port) {
|
|
case AUDIO_SPEAKER:
|
|
case AUDIO_HEADPHONE:
|
|
#if defined(CPU_SUN4M_690)
|
|
/*
|
|
* XXX - Only headphone jack on SPARCserver 600 series,
|
|
* so don't allow any changes from the default value.
|
|
*/
|
|
if (cpu != CPU_SUN4M_690) {
|
|
play_as->info.port =
|
|
amd_outport(chip, ip->play.port);
|
|
} else {
|
|
error = EINVAL;
|
|
}
|
|
#else
|
|
play_as->info.port = amd_outport(chip, ip->play.port);
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the sample counters atomically, returning the old values.
|
|
*/
|
|
if (Modify(ip->play.samples) || Modify(ip->record.samples)) {
|
|
if (play_as->info.open) {
|
|
play_as->info.samples = devp->play.samples;
|
|
if (Modify(ip->play.samples))
|
|
devp->play.samples = ip->play.samples;
|
|
}
|
|
if (record_as->info.open) {
|
|
record_as->info.samples = devp->rec.samples;
|
|
if (Modify(ip->record.samples))
|
|
devp->rec.samples = ip->record.samples;
|
|
}
|
|
|
|
}
|
|
|
|
if (Modify(ip->play.sample_rate))
|
|
sample_rate = ip->play.sample_rate;
|
|
else if (Modify(ip->record.sample_rate))
|
|
sample_rate = ip->record.sample_rate;
|
|
else
|
|
sample_rate = as->info.sample_rate;
|
|
if (Modify(ip->play.channels))
|
|
channels = ip->play.channels;
|
|
else if (Modify(ip->record.channels))
|
|
channels = ip->record.channels;
|
|
else
|
|
channels = as->info.channels;
|
|
if (Modify(ip->play.precision))
|
|
precision = ip->play.precision;
|
|
else if (Modify(ip->record.precision))
|
|
precision = ip->record.precision;
|
|
else
|
|
precision = as->info.precision;
|
|
if (Modify(ip->play.encoding))
|
|
encoding = ip->play.encoding;
|
|
else if (Modify(ip->record.encoding))
|
|
encoding = ip->record.encoding;
|
|
else
|
|
encoding = as->info.encoding;
|
|
|
|
/*
|
|
* If setting to the same existing format, don't do anything. Otherwise,
|
|
* check and see if this is a supported format
|
|
*/
|
|
if ((sample_rate == as->info.sample_rate) &&
|
|
(channels == as->info.channels) &&
|
|
(precision == as->info.precision) &&
|
|
(encoding == as->info.encoding)) {
|
|
|
|
(void) splx(s);
|
|
iocp->ioc_error = error;
|
|
return (AUDRETURN_CHANGE);
|
|
} else if ((sample_rate != 8000) ||
|
|
(channels != 1) ||
|
|
(precision != 8) ||
|
|
((encoding != AUDIO_ENCODING_ULAW) &&
|
|
(encoding != AUDIO_ENCODING_ALAW))) {
|
|
|
|
error = EINVAL;
|
|
(void) splx(s);
|
|
iocp->ioc_error = error;
|
|
return (AUDRETURN_CHANGE);
|
|
}
|
|
if (Modify(ip->play.encoding)) {
|
|
/*
|
|
* If a process wants to modify the play format, another
|
|
* process can not have it open for recording, but it can
|
|
*/
|
|
if (record_as->info.open && play_as->info.open &&
|
|
(record_as->readq != play_as->readq)) {
|
|
error = EBUSY;
|
|
goto playdone;
|
|
}
|
|
/*
|
|
* Do not allow format changes on the control channel
|
|
*/
|
|
if ((as != play_as) && (as != record_as)) {
|
|
error = EINVAL;
|
|
goto playdone;
|
|
}
|
|
switch (ip->play.encoding) {
|
|
case AUDIO_ENCODING_ULAW:
|
|
play_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
record_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
|
|
/* Tell the chip to accept the gain registers */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR1);
|
|
chip->dr = (AUDIO_MMR1_BITS_u_LAW |
|
|
AUDIO_MMR1_BITS_LOAD_GR |
|
|
AUDIO_MMR1_BITS_LOAD_GER |
|
|
AUDIO_MMR1_BITS_LOAD_GX |
|
|
AUDIO_MMR1_BITS_LOAD_STG);
|
|
break;
|
|
|
|
case AUDIO_ENCODING_ALAW:
|
|
play_as->info.encoding = AUDIO_ENCODING_ALAW;
|
|
record_as->info.encoding = AUDIO_ENCODING_ALAW;
|
|
|
|
/* Tell the chip to accept the gain registers */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR1);
|
|
chip->dr = (AUDIO_MMR1_BITS_A_LAW |
|
|
AUDIO_MMR1_BITS_LOAD_GR |
|
|
AUDIO_MMR1_BITS_LOAD_GER |
|
|
AUDIO_MMR1_BITS_LOAD_GX |
|
|
AUDIO_MMR1_BITS_LOAD_STG);
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
playdone: ;
|
|
}
|
|
|
|
if (Modify(ip->record.encoding)) {
|
|
/*
|
|
* If a process wants to modify the recording format, another
|
|
* process can not have it open for playing, but it can
|
|
*/
|
|
if (play_as->info.open && record_as->info.open &&
|
|
(record_as->readq != play_as->readq)) {
|
|
error = EBUSY;
|
|
goto recdone;
|
|
}
|
|
/*
|
|
* Do not allow format changes on the control channel
|
|
*/
|
|
if ((as != play_as) && (as != record_as)) {
|
|
error = EINVAL;
|
|
goto recdone;
|
|
}
|
|
switch (ip->record.encoding) {
|
|
case AUDIO_ENCODING_ULAW:
|
|
record_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
play_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
|
|
/* Tell the chip to accept the gain registers */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR1);
|
|
chip->dr = (AUDIO_MMR1_BITS_u_LAW |
|
|
AUDIO_MMR1_BITS_LOAD_GR |
|
|
AUDIO_MMR1_BITS_LOAD_GER |
|
|
AUDIO_MMR1_BITS_LOAD_GX |
|
|
AUDIO_MMR1_BITS_LOAD_STG);
|
|
break;
|
|
|
|
case AUDIO_ENCODING_ALAW:
|
|
record_as->info.encoding = AUDIO_ENCODING_ALAW;
|
|
play_as->info.encoding = AUDIO_ENCODING_ALAW;
|
|
|
|
/* Tell the chip to accept the gain registers */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR1);
|
|
chip->dr = (AUDIO_MMR1_BITS_A_LAW |
|
|
AUDIO_MMR1_BITS_LOAD_GR |
|
|
AUDIO_MMR1_BITS_LOAD_GER |
|
|
AUDIO_MMR1_BITS_LOAD_GX |
|
|
AUDIO_MMR1_BITS_LOAD_STG);
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
recdone: ;
|
|
}
|
|
(void) splx(s);
|
|
iocp->ioc_error = error;
|
|
return (AUDRETURN_CHANGE);
|
|
}
|
|
|
|
|
|
/*
|
|
* This routine is called whenever a new command is added to the cmd chain.
|
|
* Since the virtual dma controller simply uses the driver's cmd chain,
|
|
* all we have to do is make sure that the virtual controller has the
|
|
* start address right.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static void
|
|
amd_queuecmd(as, cmdp)
|
|
aud_stream_t *as;
|
|
aud_cmd_t *cmdp;
|
|
{
|
|
amd_stream_t *cp;
|
|
|
|
ASSERT(as != NULL);
|
|
ASSERT(cmdp != NULL);
|
|
cp = (amd_stream_t *)as;
|
|
ASSERT(cp != NULL);
|
|
|
|
/*
|
|
* Each AMD command is its own "packet". Setting this here makes
|
|
* the interrupt routine (C and Assemby versions) that much
|
|
* shorter.
|
|
*/
|
|
cmdp->lastfragment = cmdp;
|
|
|
|
/*
|
|
* If the virtual controller command list is NULL, then the
|
|
* interrupt routine is probably disabled. In the event that it
|
|
* is not, setting a new command list below is safe at low
|
|
* priority.
|
|
*/
|
|
if (cp->cmdptr == NULL) {
|
|
cp->cmdptr = cmdp;
|
|
ATRACE(amd_queuecmd,
|
|
(as == as->play_as ? ATR_FIRSTPLAY : ATR_FIRSTREC),
|
|
__LINE__);
|
|
if (!cp->active)
|
|
amd_start(as); /* go, if not paused */
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Flush the device's notion of queued commands.
|
|
*/
|
|
static void
|
|
amd_flushcmd(as)
|
|
aud_stream_t *as;
|
|
{
|
|
ASSERT(as != NULL);
|
|
|
|
if (as == as->play_as)
|
|
DEVP(as)->play.cmdptr = NULL;
|
|
else
|
|
DEVP(as)->rec.cmdptr = NULL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This is the interrupt routine used by the level four interrupt. It is
|
|
* scheduled by the level 13 interrupt routine, simply calls out to the
|
|
* generic audio driver routines to process finished play and record
|
|
* buffers.
|
|
*/
|
|
static int
|
|
amd_intr4()
|
|
{
|
|
/* XXX - for now, there's only one audio device */
|
|
aud_stream_t *as = (aud_stream_t *)&amd_devices[0].play.as;
|
|
aud_stream_t *record_as = as->record_as;
|
|
aud_stream_t *play_as = as->play_as;
|
|
|
|
/* Process Record events: call out if finished cmd block or error */
|
|
if ((record_as->cmdlist.head != NULL &&
|
|
record_as->cmdlist.head->done) ||
|
|
DEVP(as)->rec.error) {
|
|
ATRACE(amd_intr4, 'rint', 0);
|
|
audio_process_record(record_as);
|
|
}
|
|
|
|
/* Process Play events: call out if finished cmd block or error */
|
|
if ((play_as->cmdlist.head != NULL &&
|
|
play_as->cmdlist.head->done) ||
|
|
DEVP(as)->play.error) {
|
|
ATRACE(amd_intr4, 'pint', 0);
|
|
audio_process_play(play_as);
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize the audio chip to a known good state.
|
|
*
|
|
* The audio outputs are usually high impedance. This causes a problem
|
|
* with the current hardware, since the high impedance line picks up lots
|
|
* of noise. The lines are always high impedance if the chip is idle (as
|
|
* on reset or after setting the INIT register to 0). We can lower the
|
|
* impedance of one output, and hence the noise, by making the chip
|
|
* active and selecting that output. However, the other output will be
|
|
* floating, and hence noisy.
|
|
*/
|
|
static void
|
|
amd_devinit(as)
|
|
aud_stream_t *as;
|
|
{
|
|
struct aud_79C30_chip *chip;
|
|
amd_dev_t *devp;
|
|
aud_stream_t *play_as = as->play_as;
|
|
aud_stream_t *record_as = as->record_as;
|
|
|
|
ASSERT(as != NULL);
|
|
|
|
devp = DEVP(as);
|
|
chip = CSR(as);
|
|
ASSERT(devp != NULL);
|
|
ASSERT(chip != NULL);
|
|
|
|
/* Make the chip inactive and turn off the INT pin. */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_INIT_INIT);
|
|
chip->dr = AUDIO_INIT_BITS_ACTIVE | AUDIO_INIT_BITS_INT_DISABLED;
|
|
|
|
/*
|
|
* Set up the multiplexer. We use the Bb port for both reads
|
|
* and writes. We also enable interrupts for the port. Note
|
|
* that we also have to enable the INT pin using the INIT
|
|
* register to get an interrupt.
|
|
*/
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MUX_MCR1);
|
|
chip->dr = AUDIO_MUX_PORT_BA | (AUDIO_MUX_PORT_BB << 4);
|
|
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MUX_MCR2);
|
|
chip->dr = 0;
|
|
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MUX_MCR3);
|
|
chip->dr = 0;
|
|
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MUX_MCR4);
|
|
chip->dr = AUDIO_MUX_MCR4_BITS_INT_ENABLE;
|
|
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_FTGR);
|
|
chip->dr = 0;
|
|
chip->dr = 0;
|
|
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_ATGR);
|
|
chip->dr = 0;
|
|
chip->dr = 0;
|
|
|
|
/* Init the gain registers */
|
|
play_as->info.gain = amd_play_gain(chip, play_as->info.gain);
|
|
record_as->info.gain =
|
|
amd_record_gain(chip, record_as->info.gain);
|
|
|
|
(void) amd_monitor_gain(chip, devp->hwi_state.monitor_gain);
|
|
(void) amd_outport(chip, play_as->info.port);
|
|
|
|
/* Tell the chip to accept the gain registers */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR1);
|
|
chip->dr = AUDIO_MMR1_BITS_LOAD_GR | AUDIO_MMR1_BITS_LOAD_GER |
|
|
AUDIO_MMR1_BITS_LOAD_GX | AUDIO_MMR1_BITS_LOAD_STG;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Set output port to external jack or built-in speaker */
|
|
static unsigned int
|
|
amd_outport(chip, val)
|
|
struct aud_79C30_chip *chip;
|
|
unsigned int val;
|
|
{
|
|
ASSERT(chip != NULL);
|
|
|
|
/* AINB is the analog input port; LS is the built-in speaker */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_MMR2);
|
|
chip->dr = AUDIO_MMR2_BITS_AINB |
|
|
(val == AUDIO_SPEAKER ? AUDIO_MMR2_BITS_LS : 0);
|
|
|
|
return (val);
|
|
}
|
|
|
|
|
|
/*
|
|
* AM79C30 gain coefficient tables
|
|
*
|
|
* The record and monitor levels can range from -18dB to +12dB.
|
|
* The play level is the sum of both gr and ger and can range
|
|
* from -28dB to +30dB. Such low output levels are not particularly
|
|
* useful, however, so the range supported by the driver is -13dB to +30dB.
|
|
*
|
|
* Further, since ger also amplifies the monitor path, it is held at +5dB
|
|
* except at extremely high output levels.
|
|
*
|
|
* The gain tables have been chosen to give the most granularity in
|
|
* the upper half of the dynamic range, since small changes at low
|
|
* levels are virtually indistiguishable. Since the maximum output
|
|
* gains are only needed for extremely quiet recordings, less
|
|
* granularity is provided at the extreme upper end as well.
|
|
*/
|
|
|
|
struct aud_gainset {
|
|
unsigned char coef[2];
|
|
};
|
|
|
|
/* This is the table for record, monitor, and 1st-stage play gains */
|
|
static struct aud_gainset grtab[] = {
|
|
/* Infinite attenuation is treated as a special case */
|
|
{0x08, 0x90}, /* infinite attenuation */
|
|
|
|
{0x7c, 0x8b}, /* -18. dB */
|
|
{0x35, 0x8b}, /* -17. dB */
|
|
{0x24, 0x8b}, /* -16. dB */
|
|
{0x23, 0x91}, /* -15. dB */
|
|
{0x2a, 0x91}, /* -14. dB */
|
|
{0x3b, 0x91}, /* -13. dB */
|
|
{0xf9, 0x91}, /* -12. dB */
|
|
{0xb6, 0x91}, /* -11. dB */
|
|
{0xa4, 0x91}, /* -10. dB */
|
|
{0x32, 0x92}, /* -9. dB */
|
|
{0xaa, 0x92}, /* -8. dB */
|
|
{0xb3, 0x93}, /* -7. dB */
|
|
{0x91, 0x9f}, /* -6. dB */
|
|
{0xf9, 0x9b}, /* -5. dB */
|
|
{0x4a, 0x9a}, /* -4. dB */
|
|
{0xa2, 0xa2}, /* -3. dB */
|
|
{0xa3, 0xaa}, /* -2. dB */
|
|
{0x52, 0xbb}, /* -1. dB */
|
|
{0x08, 0x08}, /* 0. dB */
|
|
{0xb2, 0x4c}, /* 0.5 dB */
|
|
{0xac, 0x3d}, /* 1. dB */
|
|
{0xe5, 0x2a}, /* 1.5 dB */
|
|
{0x33, 0x25}, /* 2. dB */
|
|
{0x22, 0x22}, /* 2.5 dB */
|
|
{0x22, 0x21}, /* 3. dB */
|
|
{0xd3, 0x1f}, /* 3.5 dB */
|
|
{0xa2, 0x12}, /* 4. dB */
|
|
{0x1b, 0x12}, /* 4.5 dB */
|
|
{0x3b, 0x11}, /* 5. dB */
|
|
{0xc3, 0x0b}, /* 5.5 dB */
|
|
{0xf2, 0x10}, /* 6. dB */
|
|
{0xba, 0x03}, /* 6.5 dB */
|
|
{0xca, 0x02}, /* 7. dB */
|
|
{0x1d, 0x02}, /* 7.5 dB */
|
|
{0x5a, 0x01}, /* 8. dB */
|
|
{0x22, 0x01}, /* 8.5 dB */
|
|
{0x12, 0x01}, /* 9. dB */
|
|
{0xec, 0x00}, /* 9.5 dB */
|
|
{0x32, 0x00}, /* 10. dB */
|
|
{0x21, 0x00}, /* 10.5 dB */
|
|
{0x13, 0x00}, /* 11. dB */
|
|
{0x11, 0x00}, /* 11.5 dB */
|
|
{0x0e, 0x00}, /* 12. dB */
|
|
};
|
|
|
|
/*
|
|
* This is the table for 2nd-stage play gain.
|
|
* Note that this gain stage affects the monitor volume also.
|
|
*/
|
|
static struct aud_gainset gertab[] = {
|
|
{0x1f, 0x43}, /* 5. dB */
|
|
{0x1f, 0x33}, /* 5.5 dB */
|
|
{0xdd, 0x40}, /* 6. dB */
|
|
{0xdd, 0x11}, /* 6.5 dB */
|
|
{0x0f, 0x44}, /* 7. dB */
|
|
{0x1f, 0x41}, /* 7.5 dB */
|
|
{0x1f, 0x31}, /* 8. dB */
|
|
{0x20, 0x55}, /* 8.5 dB */
|
|
{0xdd, 0x10}, /* 9. dB */
|
|
{0x11, 0x42}, /* 9.5 dB */
|
|
{0x0f, 0x41}, /* 10. dB */
|
|
{0x1f, 0x11}, /* 10.5 dB */
|
|
{0x0b, 0x60}, /* 11. dB */
|
|
{0xdd, 0x00}, /* 11.5 dB */
|
|
{0x10, 0x42}, /* 12. dB */
|
|
{0x0f, 0x11}, /* 13. dB */
|
|
{0x00, 0x72}, /* 14. dB */
|
|
{0x10, 0x21}, /* 15. dB */
|
|
{0x00, 0x22}, /* 15.9 dB */
|
|
{0x0b, 0x00}, /* 16.9 dB */
|
|
{0x0f, 0x00}, /* 18. dB */
|
|
};
|
|
|
|
#define GR_GAINS ((sizeof (grtab) / sizeof (grtab[0])) - 1)
|
|
#define GER_GAINS (sizeof (gertab) / sizeof (gertab[0]))
|
|
#define PLAY_GAINS (GR_GAINS + GER_GAINS - 1)
|
|
|
|
/*
|
|
* Convert play gain to chip values and load them.
|
|
* Keep the 2nd stage gain at its lowest value if possible, so that monitor
|
|
* gain isn't affected.
|
|
* Return the closest appropriate gain value.
|
|
*/
|
|
static unsigned int
|
|
amd_play_gain(chip, val)
|
|
struct aud_79C30_chip *chip;
|
|
unsigned int val;
|
|
{
|
|
int gr;
|
|
int ger;
|
|
|
|
ASSERT(chip != NULL);
|
|
|
|
ger = 0; /* assume constant 2nd stage gain */
|
|
if (val == 0) {
|
|
gr = -1; /* first gain entry is infinite attenuation */
|
|
} else {
|
|
/* Scale gain range to available values */
|
|
gr = ((val * PLAY_GAINS) + (PLAY_GAINS / 2)) /
|
|
(AUDIO_MAX_GAIN + 1);
|
|
|
|
/* Scale back to full range for return value */
|
|
if (val != AUDIO_MAX_GAIN)
|
|
val = ((gr * AUDIO_MAX_GAIN) + (AUDIO_MAX_GAIN / 2)) /
|
|
PLAY_GAINS;
|
|
|
|
/* If gr is off scale, increase 2nd stage gain */
|
|
if (gr >= GR_GAINS) {
|
|
ger = gr - GR_GAINS + 1;
|
|
gr = GR_GAINS - 1;
|
|
}
|
|
}
|
|
|
|
/* Load output gain registers */
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_GR);
|
|
chip->dr = grtab[++gr].coef[0];
|
|
chip->dr = grtab[gr].coef[1];
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_MAP_GER);
|
|
chip->dr = gertab[ger].coef[0];
|
|
chip->dr = gertab[ger].coef[1];
|
|
return (val);
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert record and monitor gain to chip values and load them.
|
|
* Return the closest appropriate gain value.
|
|
*/
|
|
static unsigned
|
|
amd_gx_gain(chip, val, reg)
|
|
struct aud_79C30_chip *chip;
|
|
unsigned val;
|
|
unsigned reg;
|
|
{
|
|
int gr;
|
|
|
|
ASSERT(chip != NULL);
|
|
|
|
if (val == 0) {
|
|
gr = -1; /* first gain entry is infinite attenuation */
|
|
} else {
|
|
/* Scale gain range to available values */
|
|
gr = ((val * GR_GAINS) + (GR_GAINS / 2)) /
|
|
(AUDIO_MAX_GAIN + 1);
|
|
|
|
/* Scale back to full range for return value */
|
|
if (val != AUDIO_MAX_GAIN)
|
|
val = ((gr * AUDIO_MAX_GAIN) + (AUDIO_MAX_GAIN / 2)) /
|
|
GR_GAINS;
|
|
}
|
|
|
|
/* Load gx or stg registers */
|
|
chip->cr = reg;
|
|
chip->dr = grtab[++gr].coef[0];
|
|
chip->dr = grtab[gr].coef[1];
|
|
|
|
return (val);
|
|
}
|
|
|
|
|
|
#if defined(AMD_C_TRAP)
|
|
|
|
/* Redefine spl calls so that trace macros don't spl() from interrupt handler */
|
|
#define splaudio() 0
|
|
#define splx(X) 0
|
|
|
|
/*
|
|
* Level 13 intr handler implements a pseudo DMA device for the AMD79C30.
|
|
*
|
|
* NOTE: This routine has a number of unreferenced labels corresponding to
|
|
* branch points in the assembly language routine (amd_intr.s).
|
|
*/
|
|
int
|
|
amd_intr()
|
|
{
|
|
amd_dev_t *devp;
|
|
struct aud_79C30_chip *chip;
|
|
struct aud_cmd *cmdp;
|
|
int int_active;
|
|
#define Interrupt 1
|
|
#define Active 2
|
|
|
|
/*
|
|
* Figure out which chip interrupted.
|
|
* Since we only have one chip, we punt and assume device zero.
|
|
*
|
|
* XXX - how would we differentiate between chips??
|
|
*/
|
|
devp = &amd_devices[0];
|
|
chip = devp->chip;
|
|
int_active = chip->ir; /* clear interrupt condition */
|
|
|
|
int_active = 0;
|
|
|
|
/*
|
|
* Process record IO
|
|
*/
|
|
if (devp->rec.active) {
|
|
cmdp = devp->rec.cmdptr;
|
|
|
|
/*
|
|
* A normal buffer must have at least 1 sample of data.
|
|
* A zero length buffer must have had the skip flag set.
|
|
* A skipped (possibly zero length) command block is used
|
|
* to synchronize the audio stream.
|
|
*/
|
|
while (cmdp != NULL && (cmdp->skip || cmdp->done)) {
|
|
cmdp->done = TRUE;
|
|
cmdp = cmdp->next;
|
|
devp->rec.cmdptr = cmdp;
|
|
int_active |= Interrupt;
|
|
}
|
|
|
|
/* Check for flow error */
|
|
if (cmdp == NULL) {
|
|
/* Flow error condition */
|
|
devp->rec.error = TRUE;
|
|
devp->rec.active = FALSE;
|
|
int_active |= Interrupt;
|
|
goto play;
|
|
}
|
|
|
|
/* Transfer record data */
|
|
int_active |= Active; /* note device active */
|
|
devp->rec.samples++; /* bump sample count */
|
|
*cmdp->data++ = chip->bbrb;
|
|
|
|
/* Notify driver of end of buffer condition */
|
|
if (cmdp->data >= cmdp->enddata) {
|
|
cmdp->done = TRUE;
|
|
devp->rec.cmdptr = cmdp->next;
|
|
int_active |= Interrupt;
|
|
}
|
|
}
|
|
|
|
play:
|
|
if (devp->play.active) {
|
|
cmdp = devp->play.cmdptr;
|
|
|
|
/* Ignore null and non-data buffers */
|
|
while (cmdp != NULL && (cmdp->skip || cmdp->done)) {
|
|
cmdp->done = TRUE;
|
|
cmdp = cmdp->next;
|
|
devp->play.cmdptr = cmdp;
|
|
int_active |= Interrupt;
|
|
}
|
|
|
|
/* Check for flow error */
|
|
if (cmdp == NULL) {
|
|
/* Flow error condition */
|
|
devp->play.error = TRUE;
|
|
devp->play.active = FALSE;
|
|
int_active |= Interrupt;
|
|
goto done;
|
|
}
|
|
|
|
/* Transfer play data */
|
|
int_active |= Active;
|
|
devp->play.samples++;
|
|
chip->bbrb = *cmdp->data++;
|
|
|
|
/* Notify driver of end of buffer condition */
|
|
if (cmdp->data >= cmdp->enddata) {
|
|
cmdp->done = TRUE;
|
|
devp->play.cmdptr = cmdp->next;
|
|
int_active |= Interrupt;
|
|
}
|
|
}
|
|
|
|
done:
|
|
/* If no IO is active, shut down device interrupts */
|
|
if (!(int_active & Active)) {
|
|
chip->cr = AUDIO_UNPACK_REG(AUDIO_INIT_INIT);
|
|
chip->dr = AUDIO_INIT_BITS_ACTIVE |
|
|
AUDIO_INIT_BITS_INT_DISABLED;
|
|
}
|
|
|
|
/* Schedule a lower priority interrupt, if necessary */
|
|
if (int_active & Interrupt) {
|
|
#if defined(AUDIO_ASM)
|
|
{
|
|
extern int psr;
|
|
|
|
*(unsigned char *)INTREG_ADDR |= IR_SOFT_INT4;
|
|
psr = 0;
|
|
}
|
|
#else
|
|
{
|
|
extern int set_intreg();
|
|
|
|
set_intreg(IR_SOFT_INT4, TRUE);
|
|
}
|
|
#endif
|
|
}
|
|
return (TRUE);
|
|
}
|
|
#undef splaudio
|
|
#undef splx
|
|
|
|
#endif /* AMD_C_TRAP */
|