audiosolo.c revision f9ead4a57883f3ef04ef20d83cc47987d98c0687
/*
* Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (C) 4Front Technologies 1996-2008.
*/
/*
* NB: The Solo-1 is a bit schizophrenic compared to most devices.
* It has two separate DMA engines for PCM data. The first can do
* either capture or playback, and supports various Sound Blaster
* compatibility features. The second is dedicated to playback. The
* two engines have very little in common when it comes to programming
* them.
*
* We configure engine 1 for record, and engine 2 for playback. Both
* are configured for 48 kHz stereo 16-bit signed PCM.
*/
/*
* ESS Solo-1 only implements the low 24-bits on Audio1, and requires
* 64KB alignment. For Audio2, it implements the full 32-bit address
* space, but requires a 1MB address boundary. Audio1 is used for
* recording, and Audio2 is used for playback.
*/
static struct ddi_dma_attr dma_attr_audio1 = {
DMA_ATTR_VERSION, /* dma_attr_version */
0x0, /* dma_attr_addr_lo */
0x00ffffffU, /* dma_attr_addr_hi */
0xffff, /* dma_attr_count_max */
0x10000, /* dma_attr_align */
0x7f, /* dma_attr_burstsizes */
0x4, /* dma_attr_minxfer */
0xffff, /* dma_attr_maxxfer */
0xffff, /* dma_attr_seg */
0x1, /* dma_attr_sgllen */
0x1, /* dma_attr_granular */
0 /* dma_attr_flags */
};
static struct ddi_dma_attr dma_attr_audio2 = {
DMA_ATTR_VERSION, /* dma_attr_version */
0x0, /* dma_attr_addr_lo */
0xffffffffU, /* dma_attr_addr_hi */
0xfff0, /* dma_attr_count_max */
0x100000, /* dma_attr_align */
0x7f, /* dma_attr_burstsizes */
0x4, /* dma_attr_minxfer */
0xfff0, /* dma_attr_maxxfer */
0xffff, /* dma_attr_seg */
0x1, /* dma_attr_sgllen */
0x1, /* dma_attr_granular */
0 /* dma_attr_flags */
};
static ddi_device_acc_attr_t acc_attr = {
};
static ddi_device_acc_attr_t buf_attr = {
};
/*
* For the sake of simplicity, this driver fixes a few parameters with
* constants. If you want these values to be tunable, upgrade to a
* nicer and newer device. This is all tuned for 100 Hz (10
* millisecs) latency.
*/
#define SOLO_RATE 48000
#define SOLO_INTRS 100
#define SOLO_NFRAGS 8
#define SOLO_NCHAN 2
#define SOLO_SAMPSZ 2
#define INPUT_MIC 0
#define INPUT_LINE 1
#define INPUT_CD 2
#define INPUT_AUX 3
#define INPUT_MONO 4
#define DRVNAME "audiosolo"
static const char *solo_insrcs[] = {
AUDIO_PORT_AUX2IN, /* this is really mono-in */
};
typedef struct solo_regs {
} solo_regs_t;
typedef struct solo_engine {
bool started;
int syncdir;
int format;
bool swapped;
void (*start)(struct solo_engine *);
void (*stop)(struct solo_engine *);
void (*update)(struct solo_engine *);
typedef enum {
CTL_FRONT = 0,
CTL_NUM, /* must be last */
typedef struct solo_ctrl {
} solo_ctrl_t;
typedef struct solo_dev {
bool suspended;
/*
* Audio engines
*/
/*
* Controls.
*/
/*
* Mapped registers
*/
} solo_dev_t;
/*
* Common code for the pcm function
*
* solo_cmd write a single byte to the CMD port.
* solo_cmd1 write a CMD + 1 byte arg
* ess_get_byte returns a single byte from the DSP data port
*
* solo_write is actually solo_cmd1
* solo_read access ext. regs via solo_cmd(0xc0, reg) followed by solo_get_byte
*/
static bool
{
}
static bool
{
int i;
for (i = 0; i < 1000; i++) {
if (solo_dspready(dev)) {
return (true);
}
if (i > 10)
}
return (false);
}
static bool
{
}
static void
{
}
}
static void
{
drv_usecwait(10);
drv_usecwait(10);
}
static uint8_t
{
drv_usecwait(10);
drv_usecwait(10);
return (val);
}
static uint8_t
{
for (int i = 1000; i > 0; i--) {
else
drv_usecwait(20);
}
return (0xff);
}
static void
{
}
static uint8_t
{
return (solo_get_byte(dev));
}
return (0xff);
}
static bool
{
drv_usecwait(100);
return (false); /* Sorry */
}
return (true);
}
static uint_t
{
return (rv);
}
if (status & 0x20) {
/* ack the interrupt */
}
if (status & 0x10) {
/* ack the interrupt */
}
if (cons) {
}
if (prod) {
}
return (rv);
}
static uint8_t
{
uint32_t l, r;
r = value & 0xff;
l = (l * 15) / 100;
r = (r * 15) / 100;
return ((uint8_t)((l << 4) | (r)));
}
static void
{
uint32_t v;
/*
* We disable hardware volume control (i.e. async updates to volume).
* We could in theory support this, but making it work right can be
* tricky, and we doubt it is widely used.
*/
/* master volume has 6 bits per channel, bit 6 indicates mute */
/* left */
v = v ? (v * 63) / 100 : 64;
/* right */
v = v ? (v * 63) / 100 : 64;
v = v | (v << 4);
/*
* The Solo-1 has dual stereo mixers (one for input and one for output),
* with separate volume controls for each.
*/
v = v | (v << 4);
} else {
}
v = v | (v << 4);
v = (v * 7) / 100;
/* record-what-you-hear mode */
} else {
/* use record mixer */
}
}
static int
{
return (EINVAL);
return (0);
}
static int
{
val &= 0xff;
if (val > 100)
return (EINVAL);
return (0);
}
static int
{
uint8_t l;
uint8_t r;
r = val & 0xff;
if ((l > 100) || (r > 100))
return (EINVAL);
return (0);
}
static int
{
return (0);
}
static int
{
return (0);
}
static void
{
switch (num) {
case CTL_VOLUME:
desc.acd_minvalue = 0;
fn = solo_set_mono;
break;
case CTL_FRONT:
desc.acd_minvalue = 0;
break;
case CTL_SPEAKER:
desc.acd_minvalue = 0;
fn = solo_set_mono;
break;
case CTL_MIC:
desc.acd_minvalue = 0;
break;
case CTL_LINE:
desc.acd_minvalue = 0;
break;
case CTL_CD:
desc.acd_minvalue = 0;
break;
case CTL_AUX:
desc.acd_minvalue = 0;
break;
case CTL_MONO:
desc.acd_minvalue = 0;
fn = solo_set_mono;
break;
case CTL_RECSRC:
for (int i = 0; solo_insrcs[i]; i++) {
}
break;
case CTL_MONSRC:
for (int i = 0; solo_insrcs[i]; i++) {
}
break;
case CTL_MICBOOST:
desc.acd_minvalue = 0;
fn = solo_set_bool;
break;
case CTL_LOOPBACK:
desc.acd_minvalue = 0;
fn = solo_set_bool;
break;
case CTL_RECGAIN:
desc.acd_minvalue = 0;
break;
}
}
static bool
{
return (true);
}
/* utility functions for ESS */
static uint8_t
solo_calcfilter(int spd)
{
int cutoff;
}
static void
{
/*
* During recording, this register is known to give back
* garbage if it's not quiescent while being read. This hack
* attempts to work around it.
*/
} else {
}
}
static void
{
int len;
uint32_t v;
/* sample rate - 48 kHz */
/* filter cutoff */
/* setup fifo for signed 16-bit stereo */
v = v | (v << 4);
/* transfer length low, high */
/* autoinit, dma dir, go for it */
}
static void
{
/* NB: We might be in quiesce, without a lock held */
}
static void
{
e->count += n;
}
static void
{
int len;
uint32_t v;
/* program transfer type */
/* sample rate - 48 kHz */
/* transfer length low & high */
/* enable irq, set signed 16-bit stereo format */
/* this crazy initialization appears to help with fifo weirdness */
/* start the engine running */
drv_usecwait(10);
v = v | (v << 4);
}
static void
{
/* NB: We might be in quiesce, without a lock held */
}
/*
* Audio entry points.
*/
static int
solo_format(void *arg)
{
solo_engine_t *e = arg;
return (e->format);
}
static int
solo_channels(void *arg)
{
return (SOLO_NCHAN);
}
static int
{
return (SOLO_RATE);
}
static void
{
solo_engine_t *e = arg;
if (e->swapped) {
} else {
}
*incr = 2;
}
static void
{
solo_engine_t *e = arg;
}
static uint64_t
solo_count(void *arg)
{
solo_engine_t *e = arg;
e->update(e);
return (count);
}
static int
{
solo_engine_t *e = arg;
/* NB: For simplicity, we just fix the interrupt rate at 100 Hz */
*ffr = SOLO_FRAGFR;
*nfr = SOLO_NFRAGS;
e->started = false;
e->count = 0;
return (0);
}
void
solo_close(void *arg)
{
solo_engine_t *e = arg;
e->stop(e);
e->started = false;
}
static int
solo_start(void *arg)
{
solo_engine_t *e = arg;
if (!e->started) {
e->start(e);
e->started = true;
}
return (0);
}
static void
{
solo_engine_t *e = arg;
if (e->started) {
e->stop(e);
e->started = false;
}
}
static audio_engine_ops_t solo_engine_ops = {
NULL,
NULL,
};
static void
{
}
}
}
}
}
/* release play resources */
}
/* release record resources */
}
}
}
static bool
{
int actual;
(actual != 1)) {
return (false);
}
return (false);
}
NULL) != DDI_SUCCESS) {
return (false);
}
return (true);
}
static bool
{
/* map registers */
return (false);
}
return (false);
}
return (false);
}
return (true);
}
#define ESS_PCI_LEGACYCONTROL 0x40
#define ESS_PCI_CONFIG 0x50
#define ESS_PCI_DDMACONTROL 0x60
static bool
{
/*
* Legacy audio register -- disable legacy audio. We also
* arrange for 16-bit I/O address decoding.
*/
/* this version disables the MPU, FM synthesis (Adlib), and Game Port */
/*
* Note that Solo-1 uses I/O space for all BARs, and hardwires
* the upper 32-bits to zero.
*/
data |= 1;
/*
* Make sure that legacy IRQ and DRQ are disbled. We disable most
* other legacy features too.
*/
if (!solo_reset_dsp(dev))
return (false);
/* enable extended mode */
/*
* This sets Audio 2 (playback) to use its own independent
* rate control, and gives us 48 kHz compatible divisors. It
* also bypasses the switched capacitor filter.
*/
/* irq control */
/* drq control */
return (true);
}
static bool
{
unsigned ccnt;
unsigned caps;
unsigned dflags;
const char *desc;
solo_engine_t *e;
switch (engno) {
case 1: /* record */
desc = "record";
dattr = &dma_attr_audio1;
e->update = solo_aud1_update;
e->start = solo_aud1_start;
e->stop = solo_aud1_stop;
e->format = AUDIO_FORMAT_S16_BE;
e->swapped = true;
break;
case 2: /* playback */
desc = "playback";
dattr = &dma_attr_audio2;
e->syncdir = DDI_DMA_SYNC_FORDEV;
e->update = solo_aud2_update;
e->start = solo_aud2_start;
e->stop = solo_aud2_stop;
e->format = AUDIO_FORMAT_S16_LE;
e->swapped = false;
break;
default:
return (false);
}
&e->dmah) != DDI_SUCCESS) {
return (false);
}
return (false);
}
/* ensure that the buffer is zeroed out properly */
return (false);
}
e->paddr = c.dmac_address;
/*
* Allocate and configure audio engine.
*/
return (false);
}
audio_engine_set_private(e->engine, e);
return (true);
}
static int
{
/* play */
/* record */
return (DDI_SUCCESS);
}
static int
{
solo_engine_t *e;
if (!solo_init_hw(dev)) {
/* yikes! */
return (DDI_SUCCESS);
}
/* record - audio 1 */
if (e->started) {
e->start(e);
}
/* play - audio 2 */
if (e->started) {
e->start(e);
}
if (cons)
if (prod)
return (DDI_SUCCESS);
}
static int
{
goto no;
goto no;
}
if ((!solo_map_registers(dev)) ||
(!solo_setup_interrupts(dev)) ||
(!solo_add_controls(dev)) ||
(!solo_init_hw(dev))) {
goto no;
}
"unable to register with audio framework");
goto no;
}
return (DDI_SUCCESS);
no:
return (DDI_FAILURE);
}
static int
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (solo_attach(dip));
case DDI_RESUME:
return (DDI_FAILURE);
}
return (solo_resume(dev));
default:
return (DDI_FAILURE);
}
}
static int
{
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
return (solo_detach(dev));
case DDI_SUSPEND:
return (solo_suspend(dev));
default:
return (DDI_FAILURE);
}
}
static int
{
solo_setmixer(dev, 0, 0);
return (0);
}
struct dev_ops solo_dev_ops = {
DEVO_REV, /* rev */
0, /* refcnt */
NULL, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
solo_ddi_attach, /* attach */
solo_ddi_detach, /* detach */
nodev, /* reset */
NULL, /* cb_ops */
NULL, /* bus_ops */
NULL, /* power */
solo_quiesce, /* quiesce */
};
static struct modldrv solo_modldrv = {
&mod_driverops, /* drv_modops */
"ESS Solo-1 Audio", /* linkinfo */
&solo_dev_ops, /* dev_ops */
};
static struct modlinkage modlinkage = {
{ &solo_modldrv, NULL }
};
int
_init(void)
{
int rv;
}
return (rv);
}
int
_fini(void)
{
int rv;
}
return (rv);
}
int
{
}