2636 lines
65 KiB
C
2636 lines
65 KiB
C
/*
|
|
* Copyright (c) 1993 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#ident "@(#)audio_4231.c 1.1 94/10/31 SMI"
|
|
|
|
/*
|
|
* AUDIO Chip driver - for CS 4231
|
|
*
|
|
* The basic facts:
|
|
* - The digital representation is 8-bit 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.
|
|
*/
|
|
|
|
#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>
|
|
#include <machine/psl.h>
|
|
#ifdef sun4m
|
|
#include <machine/param.h>
|
|
|
|
/* Global variables */
|
|
extern int Audio_debug;
|
|
#endif
|
|
|
|
#include <sun/audioio.h>
|
|
#include <sbusdev/audiovar.h>
|
|
#include <sbusdev/audio_4231.h>
|
|
|
|
|
|
/*
|
|
* Make the C version of the interrupt handler the default except on sun4c
|
|
* machines where the SPARC assembly version can be used
|
|
* XXX later fix.
|
|
*/
|
|
#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 audio_4231_attach();
|
|
static int audio_4231_identify();
|
|
static int audio_4231_open();
|
|
static void audio_4231_close();
|
|
static aud_return_t audio_4231_ioctl();
|
|
static aud_return_t audio_4231_mproto();
|
|
static void audio_4231_start();
|
|
static void audio_4231_stop();
|
|
static unsigned int audio_4231_setflag();
|
|
static aud_return_t audio_4231_setinfo();
|
|
static void audio_4231_queuecmd();
|
|
static void audio_4231_flushcmd();
|
|
static void audio_4231_chipinit();
|
|
static void audio_4231_loopback();
|
|
static unsigned int audio_4231_outport();
|
|
static unsigned int audio_4231_output_muted();
|
|
static unsigned int audio_4231_inport();
|
|
static unsigned int audio_4231_play_gain();
|
|
static unsigned int audio_4231_record_gain();
|
|
static unsigned int audio_4231_monitor_gain();
|
|
static void audio_4231_playintr();
|
|
static void audio_4231_recintr();
|
|
static void audio_4231_pollready();
|
|
extern unsigned int audio_4231_cintr();
|
|
|
|
static void audio_4231_initlist();
|
|
static void audio_4231_insert();
|
|
static void audio_4231_remove();
|
|
static void audio_4231_clear();
|
|
static void audio_4231_samplecalc();
|
|
static unsigned int audio_4231_sampleconv();
|
|
static void audio_4231_recordend();
|
|
static void audio_4231_initcmdp();
|
|
void audio_4231_pollpipe();
|
|
void audio_4231_poll_ppipe();
|
|
void audio_4231_workaround();
|
|
|
|
|
|
/* Local declarations */
|
|
cs_unit_t *cs_units; /* device ctrlr array */
|
|
static unsigned int cs_units_size; /* size of allocated devices array */
|
|
static unsigned int devcount = 0; /* number of devices found */
|
|
int audio_4231_bsize = AUD_CS4231_BSIZE;
|
|
static int counter = 0;
|
|
static unsigned int CS4231_reva;
|
|
|
|
/* External Declarations */
|
|
|
|
extern struct map *dvmamap; /* pointer to DVMA resource map */
|
|
|
|
/*
|
|
* XXX - This driver only supports one CS 4231 device
|
|
*/
|
|
#define MAXUNITS (1)
|
|
|
|
#define RECORD_DIRECTION 1
|
|
#define PLAY_DIRECTION 0
|
|
|
|
#define AUDIO_ENCODING_DVI (104) /* DVI ADPCM PCM XXXXX */
|
|
#define AUDIO_DEV_CS4231 (5) /* should be in audioio.h XXXX */
|
|
#define CS_TIMEOUT 9000000
|
|
#define CS_POLL_TIMEOUT 100000
|
|
|
|
|
|
|
|
/*
|
|
* Declare audio ops vector for CS4231 support routines
|
|
*/
|
|
static struct aud_ops audio_4231_ops = {
|
|
audio_4231_close,
|
|
audio_4231_ioctl,
|
|
audio_4231_start,
|
|
audio_4231_stop,
|
|
audio_4231_setflag,
|
|
audio_4231_setinfo,
|
|
audio_4231_queuecmd,
|
|
audio_4231_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 audiocs_ops = {
|
|
1, /* revision */
|
|
audio_4231_identify, /* identify routine */
|
|
audio_4231_attach, /* attach routine */
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
* Streams declarations
|
|
*/
|
|
|
|
static struct module_info audio_4231_modinfo = {
|
|
AUD_CS4231_IDNUM, /* module ID number */
|
|
AUD_CS4231_NAME, /* module name */
|
|
AUD_CS4231_MINPACKET, /* min packet size accepted */
|
|
AUD_CS4231_MAXPACKET, /* max packet size accepted */
|
|
AUD_CS4231_HIWATER, /* hi-water mark */
|
|
AUD_CS4231_LOWATER, /* lo-water mark */
|
|
};
|
|
|
|
/*
|
|
* Queue information structure for read queue
|
|
*/
|
|
static struct qinit audio_4231_rinit = {
|
|
(int(*)())audio_rput, /* put procedure */
|
|
(int(*)())audio_rsrv, /* service procedure */
|
|
audio_4231_open, /* called on startup */
|
|
(int(*)())audio_close, /* called on finish */
|
|
NULL, /* for 3bnet only */
|
|
&audio_4231_modinfo, /* module information structure */
|
|
NULL, /* module statistics structure */
|
|
};
|
|
|
|
/*
|
|
* Queue information structure for write queue
|
|
*/
|
|
static struct qinit audio_4231_winit = {
|
|
(int(*)())audio_wput, /* put procedure */
|
|
(int(*)())audio_wsrv, /* service procedure */
|
|
NULL, /* called on startup */
|
|
NULL, /* called on finish */
|
|
NULL, /* for 3bnet only */
|
|
&audio_4231_modinfo, /* module information structure */
|
|
NULL, /* module statistics structure */
|
|
};
|
|
|
|
static struct streamtab audio_4231_str_info = {
|
|
&audio_4231_rinit, /* qinit for read side */
|
|
&audio_4231_winit, /* qinit for write side */
|
|
NULL, /* mux qinit for read */
|
|
NULL, /* mux qinit for write */
|
|
/* list of modules to be pushed */
|
|
};
|
|
|
|
|
|
static char *audio4231_modules[] = {
|
|
0, /* no modules defined yet */
|
|
};
|
|
|
|
struct streamtab audio_4231_info = {
|
|
&audio_4231_rinit, /* qinit for read side */
|
|
&audio_4231_winit, /* qinit for write side */
|
|
NULL, /* mux qinit for read */
|
|
NULL, /* mux qinit for write */
|
|
audio4231_modules, /* list of modules to be pushed */
|
|
};
|
|
|
|
static apc_dma_list_t dma_played_list[DMA_LIST_SIZE];
|
|
static apc_dma_list_t dma_recorded_list[DMA_LIST_SIZE];
|
|
|
|
/*
|
|
* Loadable module wrapper
|
|
*/
|
|
#if defined(CS_LOADABLE) || defined(VDDRV)
|
|
#include <sys/conf.h>
|
|
#include <sun/autoconf.h>
|
|
#include <sun/vddrv.h>
|
|
|
|
static struct cdevsw audiocs_cdevsw = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, &audio_4231_info, 0
|
|
};
|
|
|
|
static struct vdldrv audiocs_modldrv = {
|
|
VDMAGIC_DRV, /* Type of module */
|
|
"CS4231 audio driver", /* Descriptive name */
|
|
&audiocs_ops, /* Address of dev_ops */
|
|
NULL, /* bdevsw */
|
|
&audiocs_cdevsw, /* cdevsw */
|
|
0, /* Drv_blockmajor, 0 means let system choose */
|
|
0, /* Drv_charmajor, 0 means let system choose */
|
|
};
|
|
|
|
|
|
/*
|
|
* Called by modload/modunload.
|
|
*/
|
|
int
|
|
audio_4231_init(function_code, vdp, vdi, vds)
|
|
uint function_code;
|
|
struct vddrv *vdp;
|
|
addr_t vdi;
|
|
struct vdstat *vds;
|
|
{
|
|
int s, i;
|
|
#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 *)&audiocs_modldrv;
|
|
|
|
return (0);
|
|
|
|
case VDUNLOAD: { /* Unload module */
|
|
cs_unit_t *unitp;
|
|
unsigned int i;
|
|
int s;
|
|
|
|
#if !defined(STANDALONE)
|
|
/* Unregister our aclone mappings */
|
|
aclone_unregister((caddr_t)&audio_4231_info);
|
|
#endif
|
|
|
|
/* Cannot unload driver if any minor device is open */
|
|
s = splaudio();
|
|
for (i = 0; i < devcount; i++) {
|
|
unitp = &(cs_units[i]);
|
|
if (unitp->output.as.openflag ||
|
|
unitp->input.as.openflag ||
|
|
unitp->control.as.openflag) {
|
|
(void) splx(s);
|
|
return (EBUSY);
|
|
}
|
|
}
|
|
(void) splx(s);
|
|
|
|
/*
|
|
* Remove device from interrupt queues.
|
|
*/
|
|
(void) remintr(unitp->dip->devi_intr->int_pri,
|
|
audio_4231_cintr);
|
|
/*
|
|
* Deallocate structures allocated in attach
|
|
* routine
|
|
*/
|
|
for (i = 0; i < devcount; i++) {
|
|
kmem_free(cs_units[i].output.as.cmdlist.memory,
|
|
(u_int)cs_units[i].output.as.cmdlist.size);
|
|
kmem_free(cs_units[i].input.as.cmdlist.memory,
|
|
(u_int)cs_units[i].input.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
|
|
audio_4231_identify(name)
|
|
char *name;
|
|
{
|
|
|
|
if (strcmp(name, "SUNW,CS4231") == 0) {
|
|
return (TRUE);
|
|
}
|
|
return (FALSE);
|
|
}
|
|
|
|
/*
|
|
* Attach to the device.
|
|
*/
|
|
static int
|
|
audio_4231_attach(dip)
|
|
struct dev_info *dip;
|
|
{
|
|
aud_stream_t *as, *output_as, *input_as;
|
|
cs_unit_t *unitp;
|
|
struct aud_cmd *pool;
|
|
unsigned int instance;
|
|
caddr_t regs;
|
|
char name[16]; /* XXX - A Constant! */
|
|
int i;
|
|
#if !defined(STANDALONE)
|
|
dev_t csdev;
|
|
#endif
|
|
static int cs_spl = 0;
|
|
|
|
ATRACEINIT();
|
|
ATRACEPRINT("audiocs: debugging version of audio driver\n");
|
|
/*
|
|
* Each unit has a 'aud_state_t' that contains generic audio
|
|
* device state information. Also, each unit has a
|
|
* 'cs_unit_t' that contains device-specific data.
|
|
* Allocate storage for them here.
|
|
*/
|
|
if (cs_units == NULL) {
|
|
cs_units_size = MAXUNITS * sizeof (cs_unit_t);
|
|
cs_units = (cs_unit_t *)kmem_zalloc(cs_units_size,
|
|
KMEM_NOSLEEP);
|
|
if (cs_units == NULL)
|
|
return (-1);
|
|
} else {
|
|
/* We only support one audio device at this time */
|
|
(void) printf("audiocs: Can't support multiple audio devs.\n");
|
|
return (-1);
|
|
}
|
|
|
|
unitp = &cs_units[devcount];
|
|
/*
|
|
* 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.
|
|
*/
|
|
dip->devi_unit = 0;
|
|
|
|
unitp->distate.devdata = (void *)&cs_units[dip->devi_unit];
|
|
unitp->distate.audio_spl = ipltospl(dip->devi_intr->int_pri);
|
|
|
|
cs_spl = MAX(cs_spl, unitp->distate.audio_spl);
|
|
|
|
if (devcount > MAXUNITS) {
|
|
(void) printf("audiocs: multiple audio devices?\n");
|
|
return (-1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate command list buffers, initialized below
|
|
*/
|
|
unitp->allocated_size = AUD_CS4231_CMDPOOL * sizeof (aud_cmd_t);
|
|
unitp->allocated_memory = (caddr_t *)kmem_zalloc(unitp->allocated_size,
|
|
KMEM_NOSLEEP);
|
|
if (unitp->allocated_memory == NULL)
|
|
return (-1);
|
|
|
|
unitp->dip = dip;
|
|
unitp->distate.devdata = (caddr_t)unitp;
|
|
unitp->distate.monitor_gain = 0;
|
|
unitp->distate.output_muted = FALSE;
|
|
unitp->distate.ops = &audio_4231_ops;
|
|
unitp->chip = (struct aud_4231_chip *)regs;
|
|
unitp->playcount = 0;
|
|
unitp->recordcount = 0;
|
|
unitp->typ_playlength = 0;
|
|
unitp->recordlastent = 0;
|
|
|
|
/*
|
|
* Set up pointers between audio streams
|
|
*/
|
|
unitp->control.as.control_as = &unitp->control.as;
|
|
unitp->control.as.play_as = &unitp->output.as;
|
|
unitp->control.as.record_as = &unitp->input.as;
|
|
unitp->output.as.control_as = &unitp->control.as;
|
|
unitp->output.as.play_as = &unitp->output.as;
|
|
unitp->output.as.record_as = &unitp->input.as;
|
|
unitp->input.as.control_as = &unitp->control.as;
|
|
unitp->input.as.play_as = &unitp->output.as;
|
|
unitp->input.as.record_as = &unitp->input.as;
|
|
|
|
as = &unitp->control.as;
|
|
output_as = as->play_as;
|
|
input_as = as->record_as;
|
|
|
|
ASSERT(as != NULL);
|
|
ASSERT(output_as != NULL);
|
|
ASSERT(input_as != NULL);
|
|
|
|
/*
|
|
* Initialize the play stream
|
|
*/
|
|
output_as->v = &unitp->distate;
|
|
output_as->type = AUDTYPE_DATA;
|
|
output_as->mode = AUDMODE_AUDIO;
|
|
output_as->info.gain = AUD_CS4231_DEFAULT_PLAYGAIN;
|
|
output_as->info.sample_rate = AUD_CS4231_SAMPLERATE;
|
|
output_as->info.channels = AUD_CS4231_CHANNELS;
|
|
output_as->info.precision = AUD_CS4231_PRECISION;
|
|
output_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
output_as->info.minordev = CS_MINOR_RW;
|
|
output_as->info.balance = AUDIO_MID_BALANCE;
|
|
output_as->input_size = 0;
|
|
|
|
/*
|
|
* Set the default output port according to capabilities
|
|
*/
|
|
output_as->info.avail_ports = AUDIO_SPEAKER |
|
|
AUDIO_HEADPHONE | AUDIO_LINE_OUT;
|
|
output_as->info.port = AUDIO_SPEAKER;
|
|
|
|
/*
|
|
* Initialize the record stream (by copying play stream
|
|
* and correcting some values)
|
|
*/
|
|
input_as->v = &unitp->distate;
|
|
input_as->type = AUDTYPE_DATA;
|
|
input_as->mode = AUDMODE_AUDIO;
|
|
input_as->info = output_as->info;
|
|
input_as->info.gain = AUD_CS4231_DEFAULT_RECGAIN;
|
|
input_as->info.sample_rate = AUD_CS4231_SAMPLERATE;
|
|
input_as->info.channels = AUD_CS4231_CHANNELS;
|
|
input_as->info.precision = AUD_CS4231_PRECISION;
|
|
input_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
input_as->info.minordev = CS_MINOR_RO;
|
|
input_as->info.avail_ports = AUDIO_MICROPHONE |
|
|
AUDIO_LINE_IN | AUDIO_INTERNAL_CD_IN;
|
|
input_as->info.port = AUDIO_MICROPHONE;
|
|
input_as->input_size = audio_4231_bsize;
|
|
|
|
/*
|
|
* Control stream info
|
|
*/
|
|
as->v = &unitp->distate;
|
|
as->type = AUDTYPE_CONTROL;
|
|
as->mode = AUDMODE_NONE;
|
|
as->info.minordev = CS_MINOR_CTL;
|
|
|
|
/*
|
|
* 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 = (aud_cmd_t *)unitp->allocated_memory;
|
|
unitp->input.as.cmdlist.free = NULL;
|
|
unitp->output.as.cmdlist.free = NULL;
|
|
for (i = 0; i < AUD_CS4231_CMDPOOL; i++) {
|
|
struct aud_cmdlist *list;
|
|
|
|
list = (i < AUD_CS4231_RECBUFS) ?
|
|
&unitp->input.as.cmdlist :
|
|
&unitp->output.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(dip != NULL);
|
|
if (dip->devi_nreg == 1) {
|
|
CSR(as) = ((struct aud_4231_chip *)
|
|
map_regs(dip->devi_reg->reg_addr,
|
|
dip->devi_reg->reg_size,
|
|
dip->devi_reg->reg_bustype));
|
|
ASSERT(CSR(as) != NULL);
|
|
} else {
|
|
(void) printf("audiocs%d: warning: bad regs. specification\n",
|
|
dip->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.
|
|
*/
|
|
|
|
if (dip->devi_nintr == 1) {
|
|
addintr(dip->devi_intr->int_pri, audio_4231_cintr,
|
|
dip->devi_name, dip->devi_unit);
|
|
} else {
|
|
(void) printf("audio%d: warning: bad interrupt specification\n",
|
|
dip->devi_unit);
|
|
return (-1);
|
|
}
|
|
|
|
report_dev(dip);
|
|
|
|
#if !defined(STANDALONE)
|
|
/* Register with audioclone device */
|
|
csdev = aclone_register((caddr_t)&audio_4231_info,
|
|
0, /* slot 0 is reserved for cs */
|
|
10 + CS_MINOR_RW, /* audio */
|
|
10 + CS_MINOR_RO, /* audioro */
|
|
10 + CS_MINOR_CTL); /* audioctl */
|
|
|
|
if (csdev < 0)
|
|
(void) printf("audioamd: could not register with aclone!\n");
|
|
#endif
|
|
/*
|
|
* Initialize the audio chip
|
|
*/
|
|
audio_4231_chipinit(unitp);
|
|
devcount++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
audio_4231_open(q, dp, oflag, sflag)
|
|
queue_t *q;
|
|
dev_t dp;
|
|
int oflag;
|
|
int sflag;
|
|
{
|
|
cs_unit_t *unitp;
|
|
aud_stream_t *as = NULL;
|
|
int minornum = minor(dp);
|
|
int status;
|
|
|
|
/*
|
|
* Necessary for use with audioclone.
|
|
*/
|
|
if (minornum == CS_MINOR_CTL_OLD) {
|
|
printf("audiocs: old control minor num %d has changed to %d\n",
|
|
CS_MINOR_CTL_OLD, CS_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 */
|
|
dp = makedev(major(dp), minornum);
|
|
}
|
|
|
|
|
|
/*
|
|
* Handle clone device opens
|
|
*/
|
|
if (sflag == CLONEOPEN) {
|
|
switch (minornum) {
|
|
case 0: /* Normal clone open */
|
|
case CS_MINOR_RW: /* Audio clone open */
|
|
if (oflag & FWRITE) {
|
|
dp = makedev(major(dp), CS_MINOR_RW);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case CS_MINOR_RO: /* Audio clone open */
|
|
dp = makedev(major(dp), CS_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(dp);
|
|
|
|
/*
|
|
* Get address of generic audio status structure
|
|
*/
|
|
unitp = &cs_units[0]; /* only one unit */
|
|
|
|
/*
|
|
* Get the correct audio stream
|
|
*/
|
|
if (minornum == unitp->output.as.info.minordev || minornum == 0)
|
|
as = &unitp->output.as;
|
|
else if (minornum == unitp->input.as.info.minordev)
|
|
as = &unitp->input.as;
|
|
else if (minornum == unitp->control.as.info.minordev)
|
|
as = &unitp->control.as;
|
|
|
|
if (as == NULL) {
|
|
u.u_error = ENODEV;
|
|
return (OPENFAIL);
|
|
}
|
|
|
|
ATRACE(audio_4231_open, 'OPEN', as);
|
|
|
|
/* Set input buffer size now, in case the value was patched */
|
|
as->record_as->input_size = audio_4231_bsize;
|
|
|
|
if (ISDATASTREAM(as) && ((oflag & (FREAD|FWRITE)) == FREAD))
|
|
as = as->record_as;
|
|
|
|
if (audio_open(as, q, oflag, sflag) == OPENFAIL) {
|
|
ATRACE(audio_4231_open, 'FAIL', as);
|
|
return (OPENFAIL);
|
|
}
|
|
/*
|
|
* Reset to 8bit u-law mono (default) on open.
|
|
* This is here for compatibility with the man page
|
|
* interface for open of /dev/audio as described in audio(7).
|
|
*/
|
|
if (as == as->play_as && as->record_as->readq == NULL) {
|
|
as->play_as->info.sample_rate = AUD_CS4231_SAMPLERATE;
|
|
as->play_as->info.channels = AUD_CS4231_CHANNELS;
|
|
as->play_as->info.precision = AUD_CS4231_PRECISION;
|
|
as->play_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
UNITP(as)->hw_output_inited = FALSE;
|
|
UNITP(as)->hw_input_inited = FALSE;
|
|
ATRACE(audio_4231_open, 'PLAY', as);
|
|
} else if (as == as->record_as && as->play_as->readq == NULL) {
|
|
as->record_as->info.sample_rate = AUD_CS4231_SAMPLERATE;
|
|
as->record_as->info.channels = AUD_CS4231_CHANNELS;
|
|
as->record_as->info.precision = AUD_CS4231_PRECISION;
|
|
as->record_as->info.encoding = AUDIO_ENCODING_ULAW;
|
|
UNITP(as)->hw_output_inited = FALSE;
|
|
UNITP(as)->hw_input_inited = FALSE;
|
|
ATRACE(audio_4231_open, 'RECD', as);
|
|
}
|
|
|
|
if (ISDATASTREAM(as) && (oflag & FREAD))
|
|
audio_process_record(as->record_as);
|
|
|
|
ATRACE(audio_4231_open, 'DONE', as);
|
|
return (minornum);
|
|
}
|
|
|
|
|
|
/*
|
|
* Device-specific close routine, called from generic module.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static void
|
|
audio_4231_close(as)
|
|
aud_stream_t *as;
|
|
{
|
|
cs_unit_t *unitp;
|
|
unitp = UNITP(as);
|
|
|
|
/*
|
|
* Reset status bits. The device will already have been stopped.
|
|
*/
|
|
if (as == as->play_as) {
|
|
unitp->output.samples = 0;
|
|
unitp->output.error = FALSE;
|
|
unitp->chip->pioregs.iar = IAR_MCE;
|
|
DELAY(100);
|
|
unitp->chip->pioregs.iar = IAR_MCD;
|
|
audio_4231_pollready();
|
|
} else {
|
|
unitp->input.samples = 0;
|
|
unitp->input.error = FALSE;
|
|
}
|
|
|
|
ATRACE(audio_4231_close, 'CLOS', as);
|
|
}
|
|
|
|
|
|
/*
|
|
* Process ioctls not already handled by the generic audio handler.
|
|
*
|
|
* If AUDIO_CHIP is defined, we support ioctls that allow user processes
|
|
* to muck about with the device registers.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static aud_return_t
|
|
audio_4231_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_CS4231;
|
|
iocp->ioc_count = sizeof (int);
|
|
}
|
|
break;
|
|
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 re-initializing the chip.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static void
|
|
audio_4231_start(as)
|
|
aud_stream_t *as;
|
|
{
|
|
cs_stream_t *css;
|
|
int pause;
|
|
cs_unit_t *unitp;
|
|
|
|
ATRACE(audio_4231_start, ' AS', as);
|
|
unitp = UNITP(as);
|
|
if (as == as->play_as) {
|
|
ATRACE(audio_4231_start, 'OUAS', as);
|
|
css = &UNITP(as)->output;
|
|
} else {
|
|
ATRACE(audio_4231_start, 'INAS', as);
|
|
css = &UNITP(as)->input;
|
|
}
|
|
|
|
pause = as->info.pause;
|
|
|
|
/*
|
|
* If we are paused this must mean that we were paused while
|
|
* playing or recording. In this case we just wasnt to hit
|
|
* the APC_PPAUSE or APC_CPAUSE bits and resume from where
|
|
* we left off. If we are starting or re-starting we just
|
|
* want to start playing as in the normal case.
|
|
*/
|
|
|
|
/* If already active, paused, or nothing queued to the device, done */
|
|
if (css->active || pause || (css->cmdptr == NULL)) {
|
|
ATRACE(audio_4231_start, 'RET ', as);
|
|
return;
|
|
}
|
|
|
|
|
|
#ifdef NOTDEF
|
|
if (pause && (as == as->output_as) && css->active == TRUE) {
|
|
CSR(as)->dmaregs.dmacsr &= ~APC_PPAUSE;
|
|
} else if (pause && (as == as->input_as) && css->active == TRUE) {
|
|
CSR(as)->dmaregs.dmacsr &= ~APC_CPAUSE;
|
|
}
|
|
#endif
|
|
|
|
css->active = TRUE;
|
|
if (!UNITP(as)->hw_output_inited ||
|
|
!UNITP(as)->hw_input_inited) {
|
|
UNITP(as)->chip->pioregs.iar =
|
|
IAR_MCE | PLAY_DATA_FR;
|
|
UNITP(as)->chip->pioregs.idr =
|
|
DEFAULT_DATA_FMAT;
|
|
UNITP(as)->chip->pioregs.iar =
|
|
IAR_MCE | CAPTURE_DFR;
|
|
UNITP(as)->chip->pioregs.idr =
|
|
DEFAULT_DATA_FMAT;
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
UNITP(as)->hw_output_inited = TRUE;
|
|
UNITP(as)->hw_input_inited = TRUE;
|
|
}
|
|
|
|
if (!(pause) && (as == as->play_as)) {
|
|
ATRACE(audio_4231_start, 'IDOU', as);
|
|
audio_4231_initlist((apc_dma_list_t *)
|
|
dma_played_list, UNITP(as));
|
|
/*
|
|
* We must clear off the pause first. If you
|
|
* don't it won't continue from a abort.
|
|
*/
|
|
CSR(as)->dmaregs.dmacsr &= ~APC_PIE;
|
|
CSR(as)->dmaregs.dmacsr &= ~APC_PPAUSE;
|
|
audio_4231_playintr(unitp);
|
|
CSR(as)->dmaregs.dmacsr |= PLAY_SETUP;
|
|
audio_4231_pollready();
|
|
CSR(as)->pioregs.iar = INTERFACE_CR;
|
|
CSR(as)->pioregs.idr |= PEN_ENABLE;
|
|
} else if (!(pause) && (as == as->record_as)) {
|
|
ATRACE(audio_4231_start, 'IDIN', as);
|
|
audio_4231_initlist((apc_dma_list_t *)
|
|
dma_recorded_list, UNITP(as));
|
|
CSR(as)->dmaregs.dmacsr &= ~APC_CIE;
|
|
CSR(as)->dmaregs.dmacsr &= ~APC_CPAUSE;
|
|
audio_4231_recintr(unitp);
|
|
CSR(as)->dmaregs.dmacsr |= CAP_SETUP;
|
|
CSR(as)->pioregs.iar = INTERFACE_CR;
|
|
CSR(as)->pioregs.idr |= CEN_ENABLE;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* The next routine is used to stop reads or writes.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static void
|
|
audio_4231_stop(as)
|
|
aud_stream_t *as;
|
|
{
|
|
int x = 0;
|
|
int s = 0;
|
|
|
|
ATRACE(audio_4231_stop, ' AS', as);
|
|
|
|
|
|
/*
|
|
* For record.
|
|
* You HAVE to make sure that all of the DMA is freed up
|
|
* on the stop and DMA is first stopped in this routine.
|
|
* Otherwise the flow of code will progress in such a way
|
|
* that the dma memory will be freed, the device closed
|
|
* and an intr will come in and scribble on supposedly
|
|
* clean memory (verify_pattern in streams with 0xcafefeed)
|
|
* This causes a panic in allocb...on the subsequent alloc of
|
|
* this block of previously freed memory.
|
|
* The CPAUSE stops the dma and the recordend frees up the
|
|
* dma. We poll for the pipe empty bit rather than handling it
|
|
* in the intr routine because it breaks the code flow since stop and
|
|
* pause share the same functionality.
|
|
*/
|
|
|
|
if (as == as->play_as) {
|
|
UNITP(as)->output.active = FALSE;
|
|
UNITP(as)->chip->dmaregs.dmacsr |= (APC_PPAUSE);
|
|
audio_4231_poll_ppipe(UNITP(as));
|
|
audio_4231_samplecalc(UNITP(as), UNITP(as)->typ_playlength,
|
|
PLAY_DIRECTION);
|
|
audio_4231_clear((apc_dma_list_t *)dma_played_list,
|
|
UNITP(as));
|
|
audio_process_play(&UNITP(as)->output.as);
|
|
UNITP(as)->chip->pioregs.iar = IAR_MCE | INTERFACE_CR;
|
|
UNITP(as)->chip->pioregs.idr &= PEN_DISABLE;
|
|
UNITP(as)->chip->pioregs.iar = IAR_MCE;
|
|
DELAY(100);
|
|
UNITP(as)->chip->pioregs.iar = IAR_MCD;
|
|
audio_4231_pollready();
|
|
} else {
|
|
UNITP(as)->input.active = FALSE;
|
|
CSR(as)->dmaregs.dmacsr |= (APC_CPAUSE);
|
|
audio_4231_pollpipe(UNITP(as));
|
|
audio_4231_recordend(UNITP(as), dma_recorded_list);
|
|
}
|
|
}
|
|
|
|
|
|
/* Get or set a particular flag value */
|
|
|
|
static unsigned int
|
|
audio_4231_setflag(as, op, val)
|
|
aud_stream_t *as;
|
|
enum aud_opflag op;
|
|
unsigned int val;
|
|
{
|
|
cs_stream_t *cp;
|
|
int s = 0;
|
|
|
|
ASSERT(as != NULL);
|
|
cp = (as == as->play_as) ? &UNITP(as)->output : &UNITP(as)->input;
|
|
ASSERT(cp != NULL);
|
|
|
|
switch (op) {
|
|
case AUD_ERRORRESET: {
|
|
int s;
|
|
|
|
/* read/reset error flag atomically */
|
|
s = splaudio();
|
|
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.
|
|
* XXXX NEEDS spls() added.....
|
|
*/
|
|
static aud_return_t
|
|
audio_4231_setinfo(as, mp, iocp)
|
|
aud_stream_t *as;
|
|
mblk_t *mp;
|
|
struct iocblk *iocp;
|
|
{
|
|
cs_unit_t *unitp;
|
|
struct aud_4231_chip *chip;
|
|
audio_info_t *ip;
|
|
unsigned int sample_rate, channels, precision, encoding;
|
|
unsigned int o_sample_rate, o_channels, o_precision, o_encoding;
|
|
unsigned int gain;
|
|
unsigned int capcount, playcount;
|
|
unsigned char balance;
|
|
|
|
unsigned int tmp_bits;
|
|
int error = 0;
|
|
int s = 0;
|
|
|
|
unitp = UNITP(as);
|
|
|
|
/*
|
|
* Set device-specific info into device-independent structure
|
|
*/
|
|
capcount =
|
|
audio_4231_sampleconv(&unitp->input, unitp->chip->dmaregs.dmacc);
|
|
playcount =
|
|
audio_4231_sampleconv(&unitp->output, unitp->chip->dmaregs.dmapc);
|
|
|
|
if (playcount > unitp->output.samples) {
|
|
if (unitp->output.samples > 0) {
|
|
as->play_as->info.samples = unitp->output.samples;
|
|
} else {
|
|
as->play_as->info.samples = 0;
|
|
}
|
|
} else {
|
|
as->play_as->info.samples =
|
|
unitp->output.samples - playcount;
|
|
}
|
|
if (capcount > unitp->input.samples) {
|
|
if (unitp->input.samples > 0) {
|
|
as->record_as->info.samples = unitp->input.samples;
|
|
} else {
|
|
as->record_as->info.samples = 0;
|
|
}
|
|
} else {
|
|
as->record_as->info.samples =
|
|
unitp->input.samples - capcount;
|
|
}
|
|
|
|
|
|
as->play_as->info.active = unitp->output.active;
|
|
as->record_as->info.active = unitp->input.active;
|
|
|
|
/*
|
|
* If getinfo, 'mp' is NULL...we're done
|
|
*/
|
|
if (mp == NULL)
|
|
return (AUDRETURN_NOCHANGE);
|
|
|
|
ip = (audio_info_t *)(void *)mp->b_cont->b_rptr;
|
|
|
|
chip = unitp->chip;
|
|
|
|
s = splaudio();
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (Modify(ip->play.gain) || Modifyc(ip->play.balance)) {
|
|
if (Modify(ip->play.gain))
|
|
gain = ip->play.gain;
|
|
else
|
|
gain = as->play_as->info.gain;
|
|
|
|
if (Modifyc(ip->play.balance))
|
|
balance = ip->play.balance;
|
|
else
|
|
balance = as->play_as->info.balance;
|
|
|
|
as->play_as->info.gain = audio_4231_play_gain(chip,
|
|
gain, balance);
|
|
as->play_as->info.balance = balance;
|
|
}
|
|
|
|
if (Modify(ip->record.gain) || Modifyc(ip->record.balance)) {
|
|
if (Modify(ip->record.gain))
|
|
gain = ip->record.gain;
|
|
else
|
|
gain = as->record_as->info.gain;
|
|
|
|
if (Modifyc(ip->record.balance))
|
|
balance = ip->record.balance;
|
|
else
|
|
balance = as->record_as->info.balance;
|
|
|
|
as->record_as->info.gain = audio_4231_record_gain(chip,
|
|
gain, balance);
|
|
as->record_as->info.balance = balance;
|
|
}
|
|
|
|
/*
|
|
* XXXXXX FIX ME
|
|
if (Modify(ip->record.input_size)) {
|
|
if ((as != as->record_as) &&
|
|
((as == as->play_as) && !(as->openflag & FREAD)) ||
|
|
(ip->record.input_size <= 0) ||
|
|
(ip->record.input_size > AUD_CS4231_MAX_BSIZE)) {
|
|
error = EINVAL;
|
|
} else {
|
|
as->record_as->info.buffer_size =
|
|
ip->record.buffer_size;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (Modify(ip->monitor_gain)) {
|
|
as->v->monitor_gain = audio_4231_monitor_gain(chip,
|
|
ip->monitor_gain);
|
|
}
|
|
|
|
if (Modifyc(ip->output_muted)) {
|
|
as->v->output_muted = audio_4231_output_muted(unitp,
|
|
ip->output_muted);
|
|
}
|
|
|
|
|
|
if (Modify(ip->play.port)) {
|
|
as->play_as->info.port = audio_4231_outport(chip,
|
|
ip->play.port);
|
|
}
|
|
|
|
if (Modify(ip->record.port)) {
|
|
as->record_as->info.port = audio_4231_inport(chip,
|
|
ip->record.port);
|
|
}
|
|
|
|
/*
|
|
* Save the old settings on any error of the folowing 4
|
|
* reset all back to the old and exit.
|
|
* DBRI compatability.
|
|
*/
|
|
|
|
o_sample_rate = as->info.sample_rate;
|
|
o_encoding = as->info.encoding;
|
|
o_precision = as->info.precision;
|
|
o_channels = as->info.channels;
|
|
|
|
|
|
/*
|
|
* Set the sample counters atomically, returning the old values.
|
|
*/
|
|
if (Modify(ip->play.samples) || Modify(ip->record.samples)) {
|
|
if (as->play_as->info.open) {
|
|
as->play_as->info.samples = unitp->output.samples;
|
|
if (Modify(ip->play.samples))
|
|
unitp->output.samples = ip->play.samples;
|
|
}
|
|
|
|
if (as->record_as->info.open) {
|
|
as->record_as->info.samples = unitp->input.samples;
|
|
if (Modify(ip->record.samples))
|
|
unitp->input.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 current format, do not do anything. Otherwise
|
|
* check and see if this is a valid format.
|
|
*/
|
|
if ((sample_rate == as->info.sample_rate) &&
|
|
(channels == as->info.channels) &&
|
|
(precision == as->info.precision) &&
|
|
(encoding == as->info.encoding) &&
|
|
(unitp->hw_output_inited == TRUE ||
|
|
unitp->hw_input_inited == TRUE)) {
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* If we get here we must want to change the data format
|
|
* Changing the data format is done for both the play and
|
|
* record side for now.
|
|
*/
|
|
|
|
switch (sample_rate) {
|
|
case 8000: /* ULAW and ALAW */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_8000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_8000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR8000;
|
|
break;
|
|
case 9600: /* SPEECHIO */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_9600);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_9600);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR9600;
|
|
break;
|
|
case 11025:
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_11025);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_11025);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR11025;
|
|
break;
|
|
case 16000: /* G_722 */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_16000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_16000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR16000;
|
|
break;
|
|
case 18900: /* CDROM_XA_C */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_18900);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_18900);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR18900;
|
|
break;
|
|
case 22050:
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_22050);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_22050);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR22050;
|
|
break;
|
|
case 32000: /* DAT_32 */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_32000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_32000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR32000;
|
|
break;
|
|
case 37800: /* CDROM_XA_AB */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_37800);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_37800);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR37800;
|
|
break;
|
|
case 44100: /* CD_DA */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_44100);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_44100);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR44100;
|
|
break;
|
|
case 48000: /* DAT_48 */
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_48000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr = CHANGE_DFR(tmp_bits, CS4231_DFR_48000);
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
sample_rate = AUD_CS4231_SAMPR48000;
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
} /* switch on sampling rate */
|
|
|
|
if (Modify(ip->play.encoding) ||
|
|
(Modify(ip->record.encoding)) && error != EINVAL) {
|
|
/*
|
|
* If a process wants to modify the play or record format,
|
|
* another process can not have it open for recording.
|
|
*/
|
|
if (as->record_as->info.open &&
|
|
as->play_as->info.open &&
|
|
(as->record_as->readq != as->play_as->readq)) {
|
|
error = EBUSY;
|
|
goto playdone;
|
|
}
|
|
|
|
/*
|
|
* Control stream cannot modify the play format
|
|
*/
|
|
if ((as != as->play_as) && (as != as->record_as)) {
|
|
error = EINVAL;
|
|
goto playdone;
|
|
}
|
|
|
|
switch (encoding) {
|
|
case AUDIO_ENCODING_ULAW:
|
|
if (Modify(channels) && (channels != 1))
|
|
error = EINVAL;
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_ULAW);
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_ULAW);
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
channels = 0x01;
|
|
encoding = AUDIO_ENCODING_ULAW;
|
|
break;
|
|
case AUDIO_ENCODING_ALAW:
|
|
if (Modify(channels) && (channels != 1))
|
|
error = EINVAL;
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_ALAW);
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_ALAW);
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
|
|
channels = 0x01;
|
|
encoding = AUDIO_ENCODING_ALAW;
|
|
break;
|
|
case AUDIO_ENCODING_LINEAR:
|
|
if (Modify(channels) && (channels != 2) &&
|
|
(channels != 1))
|
|
error = EINVAL;
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_LINEARBE);
|
|
tmp_bits = chip->pioregs.idr;
|
|
if (channels == 2) {
|
|
chip->pioregs.idr =
|
|
CS4231_STEREO_ON(tmp_bits);
|
|
} else {
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
}
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_LINEARBE);
|
|
tmp_bits = chip->pioregs.idr;
|
|
if (channels == 2) {
|
|
chip->pioregs.idr =
|
|
CS4231_STEREO_ON(tmp_bits);
|
|
} else {
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
}
|
|
encoding = AUDIO_ENCODING_LINEAR;
|
|
break;
|
|
case AUDIO_ENCODING_DVI:
|
|
/* XXXX REV 2.0 FUTURE SUPPORT HOOK */
|
|
if (Modify(channels) && (channels != 2) &&
|
|
(channels != 1))
|
|
error = EINVAL;
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_ADPCM);
|
|
tmp_bits = chip->pioregs.idr;
|
|
if (channels == 2) {
|
|
chip->pioregs.idr =
|
|
CS4231_STEREO_ON(tmp_bits);
|
|
} else {
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
}
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
tmp_bits = chip->pioregs.idr;
|
|
chip->pioregs.idr =
|
|
CHANGE_ENCODING(tmp_bits, CS4231_DFR_ADPCM);
|
|
tmp_bits = chip->pioregs.idr;
|
|
if (channels == 2) {
|
|
chip->pioregs.idr =
|
|
CS4231_STEREO_ON(tmp_bits);
|
|
} else {
|
|
chip->pioregs.idr =
|
|
CS4231_MONO_ON(tmp_bits);
|
|
}
|
|
encoding = AUDIO_ENCODING_DVI;
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
} /* switch on audio encoding */
|
|
playdone:;
|
|
|
|
}
|
|
|
|
unitp->hw_output_inited = TRUE;
|
|
unitp->hw_input_inited = TRUE;
|
|
|
|
|
|
done:
|
|
/*
|
|
* Update the "real" info structure (and others) accordingly
|
|
*/
|
|
|
|
if (error == EINVAL) {
|
|
sample_rate = o_sample_rate;
|
|
encoding = o_encoding;
|
|
precision = o_precision;
|
|
channels = o_channels;
|
|
}
|
|
|
|
/*
|
|
* one last chance to make sure that we have something
|
|
* working. Set to defaults if one of the 4 horsemen
|
|
* is zero.
|
|
*/
|
|
|
|
if (sample_rate == 0 || encoding == 0 || precision == 0 ||
|
|
channels == 0) {
|
|
sample_rate = AUD_CS4231_SAMPLERATE;
|
|
channels = AUD_CS4231_CHANNELS;
|
|
precision = AUD_CS4231_PRECISION;
|
|
encoding = AUDIO_ENCODING_ULAW;
|
|
unitp->hw_output_inited = FALSE;
|
|
unitp->hw_input_inited = FALSE;
|
|
}
|
|
|
|
ip->play.sample_rate = ip->record.sample_rate = sample_rate;
|
|
ip->play.channels = ip->record.channels = channels;
|
|
ip->play.precision = ip->record.precision = precision;
|
|
ip->play.encoding = ip->record.encoding = encoding;
|
|
|
|
as->record_as->info.sample_rate = sample_rate;
|
|
as->record_as->info.channels = channels;
|
|
as->record_as->info.precision = precision;
|
|
as->record_as->info.encoding = encoding;
|
|
|
|
as->play_as->info.sample_rate = sample_rate;
|
|
as->play_as->info.channels = channels;
|
|
as->play_as->info.precision = precision;
|
|
as->play_as->info.encoding = encoding;
|
|
|
|
as->control_as->info.sample_rate = sample_rate;
|
|
as->control_as->info.channels = channels;
|
|
as->control_as->info.precision = precision;
|
|
as->control_as->info.encoding = encoding;
|
|
|
|
chip->pioregs.iar = IAR_MCD;
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
|
|
iocp->ioc_error = error;
|
|
|
|
(void) splx(s);
|
|
return (AUDRETURN_CHANGE);
|
|
} /* end of setinfo */
|
|
|
|
|
|
static void
|
|
audio_4231_queuecmd(as, cmdp)
|
|
aud_stream_t *as;
|
|
aud_cmd_t *cmdp;
|
|
{
|
|
cs_stream_t *css;
|
|
|
|
ATRACE(audio_4231_queuecmd, ' AS', as);
|
|
ATRACE(audio_4231_queuecmd, ' CMD', cmdp);
|
|
|
|
if (as == as->play_as)
|
|
css = &UNITP(as)->output;
|
|
else
|
|
css = &UNITP(as)->input;
|
|
|
|
/*
|
|
* This device doesn't do packets, so make each buffer its own
|
|
* packet.
|
|
*/
|
|
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 (css->cmdptr == NULL && !css->active) {
|
|
ATRACE(audio_4231_queuecmd, 'NULL', as->cmdlist.head);
|
|
css->cmdptr = as->cmdlist.head;
|
|
if (!css->active)
|
|
audio_4231_start(as); /* go, if not paused */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Flush the device's notion of queued commands.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static void
|
|
audio_4231_flushcmd(as)
|
|
aud_stream_t *as;
|
|
{
|
|
ATRACE(audio_4231_flushcmd, 'SA ', as);
|
|
DELAY(250);
|
|
if (as == as->play_as)
|
|
UNITP(as)->output.cmdptr = NULL;
|
|
else
|
|
UNITP(as)->input.cmdptr = NULL;
|
|
}
|
|
|
|
/*
|
|
* Initialize the audio chip to a known good state.
|
|
*/
|
|
static void
|
|
audio_4231_chipinit(unitp)
|
|
cs_unit_t *unitp;
|
|
{
|
|
struct aud_4231_chip *chip;
|
|
|
|
chip = unitp->chip;
|
|
ASSERT(chip != NULL);
|
|
|
|
/*
|
|
* The APC has a bug where the reset is not done
|
|
* until you do the next pio to the APC. This
|
|
* next write to the CSR causes the posted reset to
|
|
* happen.
|
|
*/
|
|
unitp->chip->dmaregs.dmacsr = APC_RESET;
|
|
unitp->chip->dmaregs.dmacsr = 0x00;
|
|
unitp->chip->dmaregs.dmacsr |= APC_CODEC_PDN;
|
|
DELAY(20);
|
|
unitp->chip->dmaregs.dmacsr &= ~(APC_CODEC_PDN);
|
|
chip->pioregs.iar |= IAR_MCE;
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | MISC_IR; /* Reg. 12 */
|
|
chip->pioregs.idr = MISC_IR_MODE2;
|
|
chip->pioregs.iar = IAR_MCE | PLAY_DATA_FR; /* Reg. 8 */
|
|
chip->pioregs.idr = DEFAULT_DATA_FMAT;
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
chip->pioregs.iar = IAR_MCE | CAPTURE_DFR;
|
|
chip->pioregs.idr = DEFAULT_DATA_FMAT;
|
|
DELAY(100); /* chip bug workaround */
|
|
audio_4231_pollready();
|
|
DELAY(1000); /* chip bug */
|
|
|
|
chip->pioregs.iar = VERSION_R;
|
|
|
|
if (chip->pioregs.idr & CS4231A)
|
|
CS4231_reva = TRUE;
|
|
else
|
|
CS4231_reva = FALSE;
|
|
|
|
/* Turn on the Output Level Bit to be 2.8 Vpp */
|
|
chip->pioregs.iar = IAR_MCE | ALT_FEA_EN1R;
|
|
chip->pioregs.idr = (char)OLB_ENABLE; /* cast for lint */
|
|
|
|
/* Turn on the hi pass filter */
|
|
chip->pioregs.iar = IAR_MCE | ALT_FEA_EN2R;
|
|
if (CS4231_reva)
|
|
chip->pioregs.idr = (HPF_ON | XTALE_ON);
|
|
else
|
|
chip->pioregs.idr = (HPF_ON);
|
|
|
|
chip->pioregs.iar = IAR_MCE | MONO_IOCR;
|
|
chip->pioregs.idr = 0x00;
|
|
|
|
|
|
/* Init the play and Record gain registers */
|
|
|
|
unitp->output.as.info.gain = audio_4231_play_gain(chip,
|
|
AUD_CS4231_DEFAULT_PLAYGAIN, AUDIO_MID_BALANCE);
|
|
unitp->input.as.info.gain = audio_4231_record_gain(chip,
|
|
AUD_CS4231_DEFAULT_RECGAIN, AUDIO_MID_BALANCE);
|
|
unitp->input.as.info.port = audio_4231_inport(chip,
|
|
AUDIO_MICROPHONE);
|
|
unitp->output.as.info.port = audio_4231_outport(chip,
|
|
AUDIO_SPEAKER);
|
|
unitp->distate.monitor_gain = audio_4231_monitor_gain(chip,
|
|
LOOPB_OFF);
|
|
|
|
chip->pioregs.iar = (u_char)IAR_MCD;
|
|
audio_4231_pollready();
|
|
chip->pioregs.iar = IAR_MCE | INTERFACE_CR;
|
|
chip->pioregs.idr &= ACAL_DISABLE;
|
|
chip->pioregs.iar = (u_char)IAR_MCD;
|
|
audio_4231_pollready();
|
|
|
|
unitp->distate.output_muted = audio_4231_output_muted(unitp, 0x0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Set or clear internal loopback for diagnostic purposes.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static void
|
|
audio_4231_loopback(unitp, loop)
|
|
cs_unit_t *unitp;
|
|
unsigned int loop;
|
|
{
|
|
|
|
unitp->chip->pioregs.iar = LOOPB_CR;
|
|
unitp->chip->pioregs.idr = (loop ? LOOPB_ON: 0);
|
|
}
|
|
|
|
static unsigned int
|
|
audio_4231_output_muted(unitp, val)
|
|
cs_unit_t *unitp;
|
|
unsigned int val;
|
|
{
|
|
/*
|
|
* Just do the mute on Index 6 & 7 R&L output.
|
|
*/
|
|
if (val) {
|
|
unitp->chip->pioregs.iar = L_OUTPUT_CR;
|
|
unitp->chip->pioregs.idr |= OUTCR_MUTE;
|
|
unitp->chip->pioregs.iar = R_OUTPUT_CR;
|
|
unitp->chip->pioregs.idr |= OUTCR_MUTE;
|
|
unitp->distate.output_muted = TRUE;
|
|
} else {
|
|
|
|
unitp->chip->pioregs.iar = L_OUTPUT_CR;
|
|
unitp->chip->pioregs.idr &= OUTCR_UNMUTE;
|
|
unitp->chip->pioregs.iar = R_OUTPUT_CR;
|
|
unitp->chip->pioregs.idr &= OUTCR_UNMUTE;
|
|
unitp->distate.output_muted = FALSE;
|
|
}
|
|
|
|
return (unitp->distate.output_muted);
|
|
|
|
}
|
|
|
|
static unsigned int
|
|
audio_4231_inport(chip, val)
|
|
struct aud_4231_chip *chip;
|
|
unsigned int val;
|
|
|
|
{
|
|
|
|
unsigned int ret_val;
|
|
|
|
|
|
/*
|
|
* In the 4231 we can have line, MIC or CDROM_IN enabled at
|
|
* one time. We cannot have a mix of all. MIC is mic on
|
|
* Index 0, LINE and CDROM (AUX1) are on Index 0 and 1 through
|
|
* the 4231 mux.
|
|
*/
|
|
ret_val = 0;
|
|
|
|
if (val & AUDIO_INTERNAL_CD_IN) {
|
|
chip->pioregs.iar = L_INPUT_CR;
|
|
chip->pioregs.idr =
|
|
CDROM_ENABLE(chip->pioregs.idr);
|
|
chip->pioregs.iar = R_INPUT_CR;
|
|
chip->pioregs.idr =
|
|
CDROM_ENABLE(chip->pioregs.idr);
|
|
ret_val = AUDIO_INTERNAL_CD_IN;
|
|
}
|
|
if ((val & AUDIO_LINE_IN)) {
|
|
chip->pioregs.iar = L_INPUT_CR;
|
|
chip->pioregs.idr =
|
|
LINE_ENABLE(chip->pioregs.idr);
|
|
chip->pioregs.iar = R_INPUT_CR;
|
|
chip->pioregs.idr =
|
|
LINE_ENABLE(chip->pioregs.idr);
|
|
ret_val = AUDIO_LINE_IN;
|
|
} else if (val & AUDIO_MICROPHONE) {
|
|
chip->pioregs.iar = L_INPUT_CR;
|
|
chip->pioregs.idr =
|
|
MIC_ENABLE(chip->pioregs.idr);
|
|
chip->pioregs.iar = R_INPUT_CR;
|
|
chip->pioregs.idr =
|
|
MIC_ENABLE(chip->pioregs.idr);
|
|
ret_val = AUDIO_MICROPHONE;
|
|
}
|
|
|
|
return (ret_val);
|
|
}
|
|
/*
|
|
*
|
|
*/
|
|
static unsigned int
|
|
audio_4231_outport(chip, val)
|
|
struct aud_4231_chip *chip;
|
|
unsigned int val;
|
|
{
|
|
unsigned int ret_val;
|
|
|
|
/*
|
|
* Disable everything then selectively enable it.
|
|
*/
|
|
|
|
ret_val = 0;
|
|
chip->pioregs.iar = MONO_IOCR;
|
|
chip->pioregs.idr |= MONOIOCR_SPKRMUTE;
|
|
chip->pioregs.iar = PIN_CR;
|
|
chip->pioregs.idr |= PINCR_LINE_MUTE;
|
|
chip->pioregs.idr |= PINCR_HDPH_MUTE;
|
|
|
|
if (val & AUDIO_SPEAKER) {
|
|
chip->pioregs.iar = MONO_IOCR;
|
|
chip->pioregs.idr &= ~MONOIOCR_SPKRMUTE;
|
|
ret_val |= AUDIO_SPEAKER;
|
|
}
|
|
if (val & AUDIO_HEADPHONE) {
|
|
chip->pioregs.iar = PIN_CR;
|
|
chip->pioregs.idr &= ~PINCR_HDPH_MUTE;
|
|
ret_val |= AUDIO_HEADPHONE;
|
|
}
|
|
|
|
if (val & AUDIO_LINE_OUT) {
|
|
chip->pioregs.iar = PIN_CR;
|
|
chip->pioregs.idr &= ~PINCR_LINE_MUTE;
|
|
ret_val |= AUDIO_LINE_OUT;
|
|
}
|
|
|
|
return (ret_val);
|
|
}
|
|
|
|
static unsigned int
|
|
audio_4231_monitor_gain(chip, val)
|
|
struct aud_4231_chip *chip;
|
|
unsigned int val;
|
|
{
|
|
int aten;
|
|
|
|
aten = AUD_CS4231_MON_MAX_ATEN -
|
|
(val * (AUD_CS4231_MON_MAX_ATEN + 1) /
|
|
(AUDIO_MAX_GAIN + 1));
|
|
|
|
/*
|
|
* Normal monitor registers are the index 13. Line monitor for
|
|
* now can be registers 18 and 19. Which are actually MIX to
|
|
* OUT directly We don't use these for now 8/3/93.
|
|
*/
|
|
|
|
chip->pioregs.iar = LOOPB_CR;
|
|
if (aten >= AUD_CS4231_MON_MAX_ATEN) {
|
|
chip->pioregs.idr = LOOPB_OFF;
|
|
} else {
|
|
|
|
/*
|
|
* Loop Back enable
|
|
* is in bit 0, 1 is reserved, thus the shift 2.
|
|
* all other aten and gains are in the low order
|
|
* bits, this one has to be differnt and be in the
|
|
* high order bits sigh...
|
|
*/
|
|
chip->pioregs.idr = ((aten << 2) | LOOPB_ON);
|
|
}
|
|
|
|
/*
|
|
* We end up returning a value slightly different than the one
|
|
* passed in - *most* applications expect this.
|
|
*/
|
|
return ((val == AUDIO_MAX_GAIN) ? AUDIO_MAX_GAIN :
|
|
((AUD_CS4231_MAX_DEV_ATEN - aten) * (AUDIO_MAX_GAIN + 1) /
|
|
(AUD_CS4231_MAX_DEV_ATEN + 1)));
|
|
}
|
|
|
|
/*
|
|
* Convert play gain to chip values and load them.
|
|
* Return the closest appropriate gain value.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static unsigned int
|
|
audio_4231_play_gain(chip, val, balance)
|
|
struct aud_4231_chip *chip;
|
|
unsigned int val;
|
|
unsigned char balance;
|
|
{
|
|
|
|
unsigned int tmp_val, r, l;
|
|
unsigned int la, ra;
|
|
unsigned char old_gain;
|
|
|
|
|
|
r = l = val;
|
|
if (balance < AUDIO_MID_BALANCE) {
|
|
r = MAX(0, (int)(val -
|
|
((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT)));
|
|
} else if (balance > AUDIO_MID_BALANCE) {
|
|
l = MAX(0, (int)(val -
|
|
((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT)));
|
|
}
|
|
|
|
if (l == 0) {
|
|
la = AUD_CS4231_MAX_DEV_ATEN;
|
|
} else {
|
|
la = AUD_CS4231_MAX_ATEN -
|
|
(l * (AUD_CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1));
|
|
}
|
|
if (r == 0) {
|
|
ra = AUD_CS4231_MAX_DEV_ATEN;
|
|
} else {
|
|
ra = AUD_CS4231_MAX_ATEN -
|
|
(r * (AUD_CS4231_MAX_ATEN + 1) / (AUDIO_MAX_GAIN + 1));
|
|
}
|
|
|
|
/* Load output gain registers */
|
|
|
|
|
|
chip->pioregs.iar = L_OUTPUT_CR;
|
|
old_gain = chip->pioregs.idr;
|
|
chip->pioregs.idr = GAIN_SET(old_gain, la);
|
|
chip->pioregs.iar = R_OUTPUT_CR;
|
|
old_gain = chip->pioregs.idr;
|
|
chip->pioregs.idr = GAIN_SET(old_gain, ra);
|
|
|
|
if ((val == 0) || (val == AUDIO_MAX_GAIN)) {
|
|
tmp_val = val;
|
|
} else {
|
|
if (l == val) {
|
|
tmp_val = ((AUD_CS4231_MAX_ATEN - la) *
|
|
(AUDIO_MAX_GAIN + 1) /
|
|
(AUD_CS4231_MAX_ATEN + 1));
|
|
} else if (r == val) {
|
|
tmp_val = ((AUD_CS4231_MAX_ATEN - ra) *
|
|
(AUDIO_MAX_GAIN + 1) /
|
|
(AUD_CS4231_MAX_ATEN + 1));
|
|
}
|
|
}
|
|
return (tmp_val);
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert record gain to chip values and load them.
|
|
* Return the closest appropriate gain value.
|
|
* Must be called with UNITP lock held.
|
|
*/
|
|
static unsigned int
|
|
audio_4231_record_gain(chip, val, balance)
|
|
struct aud_4231_chip *chip;
|
|
unsigned int val;
|
|
unsigned char balance;
|
|
{
|
|
|
|
unsigned int tmp_val, r, l;
|
|
unsigned int lg, rg;
|
|
|
|
r = l = val;
|
|
tmp_val = 0;
|
|
|
|
if (balance < AUDIO_MID_BALANCE) {
|
|
r = MAX(0, (int)(val -
|
|
((AUDIO_MID_BALANCE - balance) << AUDIO_BALANCE_SHIFT)));
|
|
} else if (balance > AUDIO_MID_BALANCE) {
|
|
l = MAX(0, (int)(val -
|
|
((balance - AUDIO_MID_BALANCE) << AUDIO_BALANCE_SHIFT)));
|
|
}
|
|
lg = l * (AUD_CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
|
|
rg = r * (AUD_CS4231_MAX_GAIN + 1) / (AUDIO_MAX_GAIN + 1);
|
|
|
|
/* Load input gain registers */
|
|
chip->pioregs.iar = L_INPUT_CR;
|
|
tmp_val = chip->pioregs.idr;
|
|
chip->pioregs.idr = RECGAIN_SET(tmp_val, lg);
|
|
|
|
chip->pioregs.iar = R_INPUT_CR;
|
|
tmp_val = chip->pioregs.idr;
|
|
chip->pioregs.idr = RECGAIN_SET(tmp_val, rg);
|
|
|
|
/*
|
|
* We end up returning a value slightly different than the one
|
|
* passed in - *most* applications expect this.
|
|
*/
|
|
if (l == val) {
|
|
if (l == 0) {
|
|
tmp_val = 0;
|
|
} else {
|
|
tmp_val = ((lg + 1) * AUDIO_MAX_GAIN) /
|
|
(AUD_CS4231_MAX_GAIN + 1);
|
|
}
|
|
} else if (r == val) {
|
|
if (r == 0) {
|
|
tmp_val = 0;
|
|
} else {
|
|
|
|
tmp_val = ((rg + 1) * AUDIO_MAX_GAIN) /
|
|
(AUD_CS4231_MAX_GAIN + 1);
|
|
}
|
|
}
|
|
|
|
return (tmp_val);
|
|
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Common interrupt routine. vectors to play of record.
|
|
*/
|
|
unsigned int
|
|
audio_4231_cintr()
|
|
{
|
|
cs_unit_t *unitp;
|
|
struct aud_4231_chip *chip;
|
|
long dmacsr, rc;
|
|
|
|
/* Acquire spin lock */
|
|
|
|
/*
|
|
* Figure out which chip interrupted.
|
|
* Since we only have one chip, we punt and assume device zero.
|
|
*/
|
|
unitp = &cs_units[0];
|
|
|
|
rc = 0;
|
|
|
|
chip = unitp->chip;
|
|
dmacsr = chip->dmaregs.dmacsr; /* read and store the APC csr */
|
|
|
|
chip->dmaregs.dmacsr = dmacsr; /* clear all possible ints */
|
|
|
|
ATRACE(audio_4231_cintr, 'RSCD', dmacsr);
|
|
|
|
if ((dmacsr & APC_CMI) && (unitp->input.active != TRUE)) {
|
|
ATRACE(audio_4231_cintr, 'PCON', dmacsr);
|
|
unitp->input.active = FALSE;
|
|
rc = 1;
|
|
}
|
|
|
|
if ((dmacsr & APC_PMI) && (unitp->output.active != TRUE)) {
|
|
ATRACE(audio_4231_cintr, 'LPON', dmacsr);
|
|
/*
|
|
* We want to pause here rather than in the playintr routine
|
|
*/
|
|
unitp->chip->dmaregs.dmacsr |= (APC_PPAUSE);
|
|
unitp->chip->pioregs.iar = INTERFACE_CR;
|
|
unitp->chip->pioregs.idr &= PEN_DISABLE;
|
|
|
|
audio_4231_workaround(unitp);
|
|
|
|
audio_4231_samplecalc(unitp, unitp->typ_playlength,
|
|
PLAY_DIRECTION);
|
|
audio_4231_clear((apc_dma_list_t *)dma_played_list,
|
|
unitp);
|
|
unitp->output.active = FALSE;
|
|
unitp->output.cmdptr = NULL;
|
|
rc = 1;
|
|
}
|
|
|
|
if (dmacsr & APC_PI) {
|
|
if (dmacsr & APC_PD) {
|
|
unitp->output.samples +=
|
|
audio_4231_sampleconv(&unitp->output,
|
|
unitp->typ_playlength);
|
|
audio_4231_playintr(unitp);
|
|
}
|
|
|
|
rc = 1;
|
|
|
|
}
|
|
|
|
if (dmacsr & APC_CI) {
|
|
if (dmacsr & APC_CD) {
|
|
unitp->input.samples +=
|
|
audio_4231_sampleconv(&unitp->input,
|
|
unitp->typ_reclength);
|
|
audio_4231_recintr(unitp);
|
|
}
|
|
|
|
rc = 1;
|
|
}
|
|
|
|
/*
|
|
* If we get a bus error make a valiant attempt to continue.
|
|
* rather than just hang the audio process.
|
|
*/
|
|
if (dmacsr & APC_EI) {
|
|
if ((dmacsr & APC_PMI)) {
|
|
ATRACE(audio_4231_cintr, 'ERR ', dmacsr);
|
|
unitp->chip->dmaregs.dmacsr |= (APC_PPAUSE);
|
|
unitp->chip->pioregs.iar = INTERFACE_CR;
|
|
unitp->chip->pioregs.idr &= PEN_DISABLE;
|
|
|
|
audio_4231_workaround(unitp);
|
|
|
|
audio_4231_samplecalc(unitp, unitp->typ_playlength,
|
|
PLAY_DIRECTION);
|
|
audio_4231_clear((apc_dma_list_t *)dma_played_list,
|
|
unitp);
|
|
unitp->output.active = FALSE;
|
|
unitp->output.cmdptr = NULL;
|
|
}
|
|
|
|
if ((dmacsr & APC_CMI)) {
|
|
unitp->input.active = FALSE;
|
|
unitp->input.cmdptr = NULL;
|
|
}
|
|
|
|
rc = 1;
|
|
}
|
|
return (rc);
|
|
}
|
|
|
|
static void
|
|
audio_4231_playintr(unitp)
|
|
cs_unit_t *unitp;
|
|
{
|
|
aud_cmd_t *cmdp;
|
|
cs_stream_t *ds;
|
|
caddr_t buf_dma_handle;
|
|
unsigned int length;
|
|
int lastcount = 0;
|
|
int need_processing = 0;
|
|
|
|
ds = &unitp->output; /* Get cs stream pointer */
|
|
|
|
lastcount = (unitp->playcount % DMA_LIST_SIZE);
|
|
if (lastcount > 0)
|
|
lastcount--;
|
|
else
|
|
lastcount = DMA_LIST_SIZE - 1;
|
|
ATRACE(audio_4231_playintr, 'tsal', lastcount);
|
|
ATRACE(audio_4231_playintr, 'yalp', unitp->playcount);
|
|
|
|
cmdp = unitp->output.cmdptr;
|
|
|
|
if (cmdp == NULL) {
|
|
ATRACE(audio_4231_playintr, 'NuLL', cmdp);
|
|
unitp->output.active = FALSE;
|
|
goto done;
|
|
}
|
|
ATRACE(audio_4231_playintr, 'Pcmd', cmdp);
|
|
|
|
if (cmdp == dma_played_list[lastcount].cmdp) {
|
|
ATRACE(audio_4231_playintr, 'emas', cmdp);
|
|
if (cmdp->next != NULL) {
|
|
cmdp = cmdp->next;
|
|
unitp->output.cmdptr = cmdp;
|
|
unitp->output.active = TRUE;
|
|
ATRACE(audio_4231_playintr, 'dmcn', cmdp);
|
|
} else {
|
|
ATRACE(audio_4231_playintr, 'Null', cmdp);
|
|
unitp->output.active = FALSE;
|
|
unitp->output.cmdptr = NULL;
|
|
goto done;
|
|
}
|
|
|
|
}
|
|
|
|
if (unitp->output.active) {
|
|
unitp->output.error = FALSE;
|
|
ATRACE(audio_4231_playintr, ' DMC', unitp->output.cmdptr);
|
|
|
|
/*
|
|
* Ignore null and non-data buffers
|
|
*/
|
|
while (cmdp != NULL && (cmdp->skip || cmdp->done)) {
|
|
cmdp->done = TRUE;
|
|
ATRACE(audio_4231_playintr, 'SCDM', cmdp);
|
|
need_processing++;
|
|
cmdp = cmdp->next;
|
|
unitp->output.cmdptr = cmdp;
|
|
}
|
|
|
|
/*
|
|
* Check for flow error EOF??
|
|
*/
|
|
if (cmdp == NULL) {
|
|
/* Flow error condition */
|
|
unitp->output.error = TRUE;
|
|
unitp->output.active = FALSE;
|
|
need_processing++;
|
|
ATRACE(audio_4231_playintr, 'LLUN', cmdp);
|
|
goto done;
|
|
}
|
|
|
|
if (unitp->output.cmdptr->skip ||
|
|
unitp->output.cmdptr->done) {
|
|
ATRACE(audio_4231_playintr, 'piks', cmdp);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Transfer play data
|
|
*/
|
|
/*
|
|
* Setup for DMA transfers to the buffer from the device
|
|
*/
|
|
length = cmdp->enddata - cmdp->data;
|
|
|
|
if (cmdp->data == NULL || length == NULL ||
|
|
length > audio_4231_bsize) {
|
|
cmdp->skip = TRUE;
|
|
need_processing++;
|
|
goto done;
|
|
}
|
|
ATRACE(audio_4231_playintr, 'LNGT', length);
|
|
|
|
buf_dma_handle = (caddr_t)mb_nbmapalloc((struct map *)dvmamap,
|
|
(caddr_t)cmdp->data,
|
|
length, MDR_BIGSBUS|MB_CANTWAIT,
|
|
(func_t)0, (caddr_t)0);
|
|
|
|
if (buf_dma_handle == (caddr_t)0) {
|
|
cmdp->skip = TRUE;
|
|
printf("DMA_SETUP failed in play!\n");
|
|
return;
|
|
}
|
|
|
|
if (buf_dma_handle) {
|
|
unitp->chip->dmaregs.dmapnva =
|
|
(u_long)buf_dma_handle;
|
|
unitp->chip->dmaregs.dmapnc =
|
|
length;
|
|
ATRACE(audio_4231_playintr, 'DMCP',
|
|
unitp->output.cmdptr);
|
|
audio_4231_insert(cmdp, buf_dma_handle,
|
|
unitp->playcount,
|
|
dma_played_list, unitp);
|
|
unitp->playcount++;
|
|
ATRACE(audio_4231_playintr, 'PCNT', counter);
|
|
counter++;
|
|
|
|
if (cmdp->next != NULL) {
|
|
unitp->output.cmdptr = cmdp->next;
|
|
}
|
|
ATRACE(audio_4231_playintr, 'DMCN',
|
|
unitp->output.cmdptr);
|
|
unitp->typ_playlength = length;
|
|
} else {
|
|
printf("apc audio: NULL DMA handle!\n");
|
|
}
|
|
|
|
} /* END OF PLAY */
|
|
|
|
done:
|
|
/*
|
|
* If no IO is active, shut down device interrupts and
|
|
* dma from the dma engine. As for the codec we are going
|
|
* to get another interrupt from the dma engine telling us
|
|
* that the fifo's have emptied and we can shut down the
|
|
* codec for capturing. We'll just handle that in the
|
|
* common interrupt handler. We also will do the final sample
|
|
* count there.
|
|
*/
|
|
|
|
audio_4231_remove(unitp->playcount, dma_played_list, unitp, FALSE);
|
|
if (need_processing)
|
|
audio_xmit_garbage_collect(&unitp->output.as);
|
|
}
|
|
|
|
static void
|
|
audio_4231_recintr(unitp)
|
|
cs_unit_t *unitp;
|
|
{
|
|
aud_cmd_t *cmdp;
|
|
cs_stream_t *ds;
|
|
caddr_t buf_dma_handle;
|
|
unsigned int length;
|
|
int e;
|
|
int int_active = 0;
|
|
|
|
#define Interrupt 1
|
|
#define Active 2
|
|
|
|
ds = &unitp->input; /* Get cs stream pointer */
|
|
|
|
ATRACE(audio_4231_recintr, 'LOCk', &unitp->input);
|
|
|
|
/* General end of record condition */
|
|
if (ds->active != TRUE) {
|
|
ATRACE(audio_4231_recintr, 'RREA', &unitp->input);
|
|
int_active |= Interrupt;
|
|
goto done;
|
|
}
|
|
|
|
if (unitp->input.active) {
|
|
cmdp = unitp->input.cmdptr;
|
|
|
|
/*
|
|
* Ignore null and non-data buffers
|
|
*/
|
|
while (cmdp != NULL && (cmdp->skip || cmdp->done)) {
|
|
cmdp->done = TRUE;
|
|
cmdp = cmdp->next;
|
|
unitp->input.cmdptr = cmdp;
|
|
}
|
|
|
|
/*
|
|
* Check for flow error EOF??
|
|
*/
|
|
if (cmdp == NULL) {
|
|
/* Flow error condition */
|
|
unitp->input.error = TRUE;
|
|
unitp->input.active = FALSE;
|
|
int_active |= Interrupt;
|
|
ATRACE(audio_4231_recintr, 'LLUN', cmdp);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Setup for DMA transfers to the buffer from the device
|
|
*/
|
|
length = cmdp->enddata - cmdp->data;
|
|
|
|
if (cmdp->data == NULL || length == NULL ||
|
|
length > audio_4231_bsize) {
|
|
cmdp->skip = TRUE;
|
|
goto done;
|
|
}
|
|
|
|
buf_dma_handle = (caddr_t)mb_nbmapalloc((struct map *)dvmamap,
|
|
(caddr_t)cmdp->data,
|
|
length, MDR_BIGSBUS|MB_CANTWAIT,
|
|
(func_t)0, (caddr_t)0);
|
|
|
|
if (buf_dma_handle == (caddr_t)0) {
|
|
cmdp->skip = TRUE;
|
|
printf("DMA_SETUP failed in record!\n");
|
|
return;
|
|
}
|
|
|
|
if (buf_dma_handle) {
|
|
audio_4231_initcmdp(cmdp,
|
|
unitp->input.as.info.encoding);
|
|
unitp->chip->dmaregs.dmacnva =
|
|
(u_long)buf_dma_handle;
|
|
unitp->chip->dmaregs.dmacnc = length;
|
|
audio_4231_insert(cmdp, buf_dma_handle,
|
|
unitp->recordcount,
|
|
dma_recorded_list,
|
|
unitp);
|
|
if (unitp->recordcount < 1)
|
|
cmdp->data = cmdp->enddata;
|
|
unitp->recordcount++;
|
|
int_active |= Active;
|
|
if (cmdp->next != NULL) {
|
|
unitp->input.cmdptr = cmdp->next;
|
|
} else {
|
|
cmdp->skip = TRUE;
|
|
}
|
|
unitp->typ_reclength = length;
|
|
} else {
|
|
printf("apc audio: NULL DMA handle!\n");
|
|
}
|
|
|
|
} /* END OF RECORD */
|
|
|
|
done:
|
|
/*
|
|
* If no IO is active, shut down device interrupts and
|
|
* dma from the dma engine. As for the codec we are going
|
|
* to get another interrupt from the dma engine telling us
|
|
* that the fifo's have emptied and we can shut down the
|
|
* codec for capturing. We'll just handle that in the
|
|
* common interrupt handler.
|
|
*/
|
|
if (!(int_active & Active)) {
|
|
unitp->chip->dmaregs.dmacsr |= (APC_CPAUSE);
|
|
audio_4231_pollpipe(unitp);
|
|
audio_4231_recordend(unitp,
|
|
dma_recorded_list);
|
|
} else {
|
|
audio_4231_remove(unitp->recordcount,
|
|
dma_recorded_list, unitp, FALSE);
|
|
audio_process_record(&unitp->input.as);
|
|
}
|
|
} /* END OF RECORD */
|
|
|
|
|
|
static void
|
|
audio_4231_initlist(dma_list, unitp)
|
|
apc_dma_list_t *dma_list;
|
|
cs_unit_t *unitp;
|
|
{
|
|
int i;
|
|
int s = 0;
|
|
|
|
s = splaudio();
|
|
for (i = 0; i < DMA_LIST_SIZE; i ++) {
|
|
dma_list[i].cmdp = (aud_cmd_t *)NULL;
|
|
dma_list[i].buf_dma_handle = (caddr_t)NULL;
|
|
}
|
|
if (dma_list == dma_recorded_list) {
|
|
unitp->recordcount = 0;
|
|
} else {
|
|
unitp->playcount = 0;
|
|
}
|
|
(void) splx(s);
|
|
}
|
|
|
|
static void
|
|
audio_4231_insert(cmdp, buf_dma_handle, count, dma_list, unitp)
|
|
aud_cmd_t *cmdp;
|
|
caddr_t buf_dma_handle;
|
|
unsigned int count;
|
|
apc_dma_list_t *dma_list;
|
|
cs_unit_t *unitp;
|
|
{
|
|
int s;
|
|
|
|
s = splaudio();
|
|
count %= DMA_LIST_SIZE;
|
|
if (dma_list[count].cmdp == (aud_cmd_t *)NULL) {
|
|
dma_list[count].cmdp = cmdp;
|
|
dma_list[count].buf_dma_handle = buf_dma_handle;
|
|
if (dma_list == dma_recorded_list) {
|
|
unitp->recordlastent = count;
|
|
}
|
|
} else {
|
|
printf("apc audio: insert dma handle failed!\n");
|
|
}
|
|
(void) splx(s);
|
|
}
|
|
|
|
static void
|
|
audio_4231_remove(count, dma_list, unitp, clear)
|
|
unsigned int count;
|
|
apc_dma_list_t *dma_list;
|
|
cs_unit_t *unitp;
|
|
unsigned int clear;
|
|
{
|
|
int s;
|
|
|
|
s = splaudio();
|
|
count = ((count - 3) % DMA_LIST_SIZE);
|
|
if (dma_list[count].cmdp != (aud_cmd_t *)NULL) {
|
|
if (dma_list == dma_recorded_list) {
|
|
dma_list[count].cmdp->data =
|
|
dma_list[count].cmdp->enddata;
|
|
}
|
|
dma_list[count].cmdp->done = TRUE;
|
|
|
|
if (dma_list == dma_recorded_list) {
|
|
audio_process_record(&unitp->input.as);
|
|
} else {
|
|
if (!clear)
|
|
audio_process_play(&unitp->output.as);
|
|
else
|
|
audio_xmit_garbage_collect(&unitp->output.as);
|
|
}
|
|
|
|
dma_list[count].cmdp = (aud_cmd_t *)NULL;
|
|
if (dma_list[count].buf_dma_handle != 0) {
|
|
mb_mapfree(dvmamap,
|
|
(int *)&dma_list[count].buf_dma_handle);
|
|
}
|
|
dma_list[count].buf_dma_handle = NULL;
|
|
}
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* Called on a stop condition to free up all of the dma queued
|
|
*/
|
|
static void
|
|
audio_4231_clear(dma_list, unitp)
|
|
apc_dma_list_t *dma_list;
|
|
cs_unit_t *unitp;
|
|
{
|
|
int i, s;
|
|
|
|
ATRACE(audio_4231_clear, 'RLCu', unitp);
|
|
for (i = 3; i < (DMA_LIST_SIZE + 3); i++) {
|
|
audio_4231_remove(i, dma_list, unitp, FALSE);
|
|
}
|
|
if (dma_list == dma_recorded_list) {
|
|
unitp->recordcount = 0;
|
|
} else {
|
|
unitp->playcount = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
audio_4231_pollready()
|
|
{
|
|
cs_unit_t *unitp;
|
|
register unsigned int x = 0;
|
|
int s = 0;
|
|
|
|
unitp = &cs_units[0];
|
|
|
|
/*
|
|
* Wait to see if chip is out of mode change enable
|
|
*/
|
|
while (unitp->chip->pioregs.iar == IAR_NOTREADY &&
|
|
x <= CS_TIMEOUT) {
|
|
x++;
|
|
}
|
|
|
|
x = 0;
|
|
|
|
/*
|
|
* Wait to see if chip has done the autocalibrate
|
|
*/
|
|
unitp->chip->pioregs.iar = TEST_IR;
|
|
|
|
while (unitp->chip->pioregs.idr == AUTOCAL_INPROGRESS &&
|
|
x <= CS_TIMEOUT) {
|
|
x++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
audio_4231_samplecalc(unitp, dma_len, direction)
|
|
cs_unit_t *unitp;
|
|
unsigned int dma_len;
|
|
unsigned int direction;
|
|
{
|
|
unsigned int samples, ncount, ccount = 0;
|
|
|
|
/* 1 is recording, 0 is playing XXX Better way??? */
|
|
|
|
if (direction) {
|
|
dma_len = audio_4231_sampleconv(&unitp->input, dma_len);
|
|
samples = unitp->input.samples;
|
|
ncount = audio_4231_sampleconv(&unitp->input,
|
|
unitp->chip->dmaregs.dmacnc);
|
|
ccount = audio_4231_sampleconv(&unitp->input,
|
|
unitp->chip->dmaregs.dmacc);
|
|
unitp->input.samples =
|
|
((samples - ncount) + (dma_len - ccount));
|
|
} else {
|
|
dma_len = audio_4231_sampleconv(&unitp->output, dma_len);
|
|
samples = unitp->output.samples;
|
|
ncount = audio_4231_sampleconv(&unitp->output,
|
|
unitp->chip->dmaregs.dmapnc);
|
|
ccount = audio_4231_sampleconv(&unitp->output,
|
|
unitp->chip->dmaregs.dmapc);
|
|
unitp->output.samples =
|
|
((samples - ncount) + (dma_len - ccount));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Converts byte counts to sample counts
|
|
*/
|
|
static unsigned int
|
|
audio_4231_sampleconv(stream, length)
|
|
cs_stream_t *stream;
|
|
unsigned int length;
|
|
{
|
|
|
|
unsigned int samples;
|
|
|
|
if (stream->as.info.channels == 2) {
|
|
samples = (length/2);
|
|
} else {
|
|
samples = length;
|
|
}
|
|
if (stream->as.info.encoding == AUDIO_ENCODING_LINEAR) {
|
|
samples = samples/2;
|
|
}
|
|
return (samples);
|
|
}
|
|
|
|
/*
|
|
* This routine is used to adjust the ending record cmdp->data
|
|
* since it is set in the intr routine we need to look it up
|
|
* in the circular buffer, mark it as done and adjust the
|
|
* cmdp->data point based on the current count in the capture
|
|
* count. Once this is done call audio_process_input() and
|
|
* also call audio_4231_clear() to free up all of the dma_handles.
|
|
*/
|
|
static void
|
|
audio_4231_recordend(unitp, dma_list)
|
|
cs_unit_t *unitp;
|
|
apc_dma_list_t *dma_list;
|
|
{
|
|
|
|
unsigned int count, capcount, ncapcount, recend;
|
|
int i, s;
|
|
|
|
s = splaudio();
|
|
count = unitp->recordlastent;
|
|
ATRACE(audio_4231_recordend, 'STAL', count);
|
|
capcount = unitp->chip->dmaregs.dmacc;
|
|
ncapcount = unitp->chip->dmaregs.dmacnc;
|
|
|
|
recend = ncapcount - capcount;
|
|
if (dma_list[count].cmdp != (aud_cmd_t *)NULL) {
|
|
dma_list[count].cmdp->data =
|
|
(dma_list[count].cmdp->enddata - recend);
|
|
dma_list[count].cmdp->done = TRUE;
|
|
audio_process_record(&unitp->input.as);
|
|
|
|
}
|
|
|
|
|
|
for (i = 0; i < DMA_LIST_SIZE; i++) {
|
|
if (dma_list[i].buf_dma_handle != 0) {
|
|
if (dma_list[i].buf_dma_handle != 0) {
|
|
mb_mapfree(dvmamap,
|
|
(int *)&dma_list[i].buf_dma_handle);
|
|
} else {
|
|
call_debug("Null handlep");
|
|
}
|
|
dma_list[i].cmdp = (aud_cmd_t *)NULL;
|
|
dma_list[i].buf_dma_handle = (caddr_t)NULL;
|
|
}
|
|
}
|
|
|
|
unitp->recordcount = 0;
|
|
(void) splx(s);
|
|
}
|
|
|
|
|
|
/*
|
|
* XXXXX this is a way gross hack to prevent the static from
|
|
* being played at the end of the record. Until the *real*
|
|
* cause can be found this will at least silence the
|
|
* extra data.
|
|
*/
|
|
static void
|
|
audio_4231_initcmdp(cmdp, format)
|
|
aud_cmd_t *cmdp;
|
|
unsigned int format;
|
|
{
|
|
|
|
int i;
|
|
unsigned int zerosample = 0;
|
|
|
|
switch (format) {
|
|
case AUDIO_ENCODING_ULAW:
|
|
zerosample = 0xff; /* silence for ulaw format */
|
|
break;
|
|
case AUDIO_ENCODING_ALAW:
|
|
zerosample = 0xd5; /* zerosample for alaw */
|
|
break;
|
|
case AUDIO_ENCODING_LINEAR:
|
|
zerosample = 0x00; /* zerosample for linear */
|
|
break;
|
|
}
|
|
ATRACE(audio_4231_initcmdp, 'PDMC', cmdp);
|
|
ATRACE(audio_4231_initcmdp, 'TAMF', format);
|
|
|
|
for (; cmdp->data <= cmdp->enddata; ) {
|
|
*cmdp->data++ = zerosample;
|
|
}
|
|
}
|
|
|
|
void
|
|
audio_4231_pollpipe(unitp)
|
|
cs_unit_t *unitp;
|
|
{
|
|
|
|
int x = 0;
|
|
|
|
while (!(unitp->chip->dmaregs.dmacsr & APC_CM) &&
|
|
x <= CS_POLL_TIMEOUT) {
|
|
x++;
|
|
}
|
|
|
|
unitp->chip->dmaregs.dmacsr | = APC_CMI;
|
|
}
|
|
|
|
void
|
|
audio_4231_poll_ppipe(unitp)
|
|
cs_unit_t *unitp;
|
|
{
|
|
|
|
int x = 0;
|
|
|
|
while (!(unitp->chip->dmaregs.dmacsr & APC_PM) &&
|
|
x <= CS_POLL_TIMEOUT) {
|
|
x++;
|
|
}
|
|
|
|
unitp->chip->dmaregs.dmacsr | = APC_PMI;
|
|
}
|
|
|
|
void
|
|
audio_4231_workaround(unitp)
|
|
cs_unit_t *unitp;
|
|
{
|
|
/*
|
|
* This workaround is so that the 4231 will run a logical
|
|
* zero sample through the DAC when playback is disabled.
|
|
* Otherwise there can be a "zipper" noise when adjusting
|
|
* the play gain at idle.
|
|
*/
|
|
if (!CS4231_reva) {
|
|
unitp->chip->pioregs.iar = IAR_MCE;
|
|
DELAY(100);
|
|
unitp->chip->pioregs.iar = (u_char)IAR_MCD;
|
|
DELAY(100);
|
|
audio_4231_pollready();
|
|
DELAY(1000);
|
|
}
|
|
}
|